Handle a condition when the result of getting configured networks returns null am: 3abc7f0561
am: 3b17189719

Change-Id: I6f2c96f8ef75e97e5df867a6122ee94b0060d0ad
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..2673295
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,7 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+
+[Builtin Hooks]
+commit_msg_bug_field = true
+commit_msg_changeid_field = true
+commit_msg_test_field = true
diff --git a/libwifi_hal/Android.mk b/libwifi_hal/Android.mk
new file mode 100644
index 0000000..1179a09
--- /dev/null
+++ b/libwifi_hal/Android.mk
@@ -0,0 +1,140 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+wifi_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
+
+# Some devices use a different path (e.g. devices with broadcom WiFi parts).
+ifdef WIFI_DRIVER_FW_PATH_PARAM
+wifi_hal_cflags += -DWIFI_DRIVER_FW_PATH_PARAM=\"$(WIFI_DRIVER_FW_PATH_PARAM)\"
+else
+wifi_hal_cflags += -DWIFI_DRIVER_FW_PATH_PARAM=\"/sys/module/wlan/parameters/fwpath\"
+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_VENDOR_MODULE := true
+LOCAL_CFLAGS := $(wifi_hal_cflags)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_SHARED_LIBRARIES := libbase
+LOCAL_HEADER_LIBRARIES := libcutils_headers
+LOCAL_SRC_FILES := wifi_hal_common.cpp
+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_VENDOR_MODULE := true
+LOCAL_CFLAGS := $(wifi_hal_cflags)
+LOCAL_SRC_FILES := wifi_hal_fallback.cpp
+LOCAL_HEADER_LIBRARIES := libhardware_legacy_headers
+include $(BUILD_STATIC_LIBRARY)
+
+# Pick a vendor provided HAL implementation library.
+# ============================================================
+LIB_WIFI_HAL := libwifi-hal-fallback
+VENDOR_LOCAL_SHARED_LIBRARIES :=
+ifeq ($(BOARD_WLAN_DEVICE), bcmdhd)
+  LIB_WIFI_HAL := libwifi-hal-bcm
+else ifeq ($(BOARD_WLAN_DEVICE), qcwcn)
+  LIB_WIFI_HAL := libwifi-hal-qcom
+  VENDOR_LOCAL_SHARED_LIBRARIES := libcld80211
+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_PROPRIETARY_MODULE := true
+LOCAL_CFLAGS := $(wifi_hal_cflags)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+    $(LOCAL_PATH)/include
+LOCAL_EXPORT_HEADER_LIBRARY_HEADERS := libhardware_legacy_headers
+LOCAL_HEADER_LIBRARIES := libhardware_legacy_headers
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libcutils \
+    liblog \
+    libnl \
+    libutils \
+    $(VENDOR_LOCAL_SHARED_LIBRARIES)
+LOCAL_SRC_FILES := \
+    driver_tool.cpp \
+    hal_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)
diff --git a/libwifi_hal/CleanSpec.mk b/libwifi_hal/CleanSpec.mk
new file mode 100644
index 0000000..d7a50f6
--- /dev/null
+++ b/libwifi_hal/CleanSpec.mk
@@ -0,0 +1,52 @@
+# -*- mode: makefile -*-
+#  Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list.  These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+#     $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+#     $(call add-clean-step, rm -rf $(OUT_DIR)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list.  E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+#
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/obj/SHARED_LIBRARIES/libdvm*)
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libwifi-hal*)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib64/libwifi-hal*)
diff --git a/libwifi_hal/driver_tool.cpp b/libwifi_hal/driver_tool.cpp
new file mode 100644
index 0000000..3089ee0
--- /dev/null
+++ b/libwifi_hal/driver_tool.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 <android-base/logging.h>
+#include <private/android_filesystem_config.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::TakeOwnershipOfFirmwareReload() {
+  if (!wifi_get_fw_path(kFirmwareModeSta) &&
+      !wifi_get_fw_path(kFirmwareModeAp) &&
+      !wifi_get_fw_path(kFirmwareModeP2p)) {
+    return true;  // HAL doesn't think we need to load firmware for any mode.
+  }
+
+  if (chown(WIFI_DRIVER_FW_PATH_PARAM, AID_WIFI, AID_WIFI) != 0) {
+    PLOG(ERROR) << "Error changing ownership of '" << WIFI_DRIVER_FW_PATH_PARAM
+                << "' to wifi:wifi";
+    return false;
+  }
+
+  return true;
+}
+
+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::IsFirmwareModeChangeNeeded(int mode) {
+  return (wifi_get_fw_path(mode) != nullptr);
+}
+
+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/hal_tool.cpp b/libwifi_hal/hal_tool.cpp
new file mode 100644
index 0000000..76b57aa
--- /dev/null
+++ b/libwifi_hal/hal_tool.cpp
@@ -0,0 +1,578 @@
+/*
+ * 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/hal_tool.h"
+
+#include <android-base/logging.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_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_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)) {
+    LOG(ERROR) << "Can not initialize the basic function pointer table";
+    return false;
+  }
+
+  if (init_wifi_vendor_hal_func_table(hal_fn) != WIFI_SUCCESS) {
+    LOG(ERROR) << "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_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..a27641b
--- /dev/null
+++ b/libwifi_hal/include/wifi_hal/driver_tool.h
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+  // Change the owner of the firmware reload path to wifi:wifi if
+  // firmware reload is supported.
+  static bool TakeOwnershipOfFirmwareReload();
+
+  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();
+
+  // Check if we need to invoke |ChangeFirmwareMode| to configure
+  // the firmware for the provided mode.
+  // |mode| is one of the kFirmwareMode* constants defined above.
+  // Returns true if needed, and false otherwise.
+  virtual bool IsFirmwareModeChangeNeeded(int mode);
+
+  // 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/include/wifi_hal/hal_tool.h b/libwifi_hal/include/wifi_hal/hal_tool.h
new file mode 100644
index 0000000..b25893e
--- /dev/null
+++ b/libwifi_hal/include/wifi_hal/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_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..04e5925
--- /dev/null
+++ b/libwifi_hal/wifi_hal_common.cpp
@@ -0,0 +1,224 @@
+/*
+ * 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"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <cutils/misc.h>
+#include <cutils/properties.h>
+
+extern "C" int init_module(void *, unsigned long, const char *);
+extern "C" 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_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)
+    PLOG(DEBUG) << "Unable to unload driver module '" << modname << "'";
+  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) {
+    PLOG(ERROR) << "Failed to open driver state control param";
+    return -1;
+  }
+  len = strlen(state) + 1;
+  if (TEMP_FAILURE_RETRY(write(fd, state, len)) != len) {
+    PLOG(ERROR) << "Failed to write driver state control param";
+    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) {
+    PLOG(WARNING) << "Could not open " << MODULE_FILE;
+    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;
+#endif
+
+#ifdef 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() {
+  if (!is_wifi_driver_loaded()) {
+    return 0;
+  }
+  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) {
+    PLOG(ERROR) << "Failed to open wlan fw path param";
+    return -1;
+  }
+  len = strlen(fwpath) + 1;
+  if (TEMP_FAILURE_RETRY(write(fd, fwpath, len)) != len) {
+    PLOG(ERROR) << "Failed to write wlan fw path param";
+    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..fd0fff4
--- /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..5541867
--- /dev/null
+++ b/libwifi_system/Android.mk
@@ -0,0 +1,76 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+wifi_system_cflags := \
+    -Wall \
+    -Werror \
+    -Wextra \
+    -Winit-self \
+    -Wno-unused-function \
+    -Wno-unused-parameter \
+    -Wshadow \
+    -Wunused-variable \
+    -Wwrite-strings
+
+# Device independent wifi system logic.
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libwifi-system
+LOCAL_CFLAGS := $(wifi_system_cflags)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libbase
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libcrypto \
+    libcutils
+
+LOCAL_SRC_FILES := \
+    hostapd_manager.cpp \
+    interface_tool.cpp \
+    supplicant_manager.cpp
+include $(BUILD_SHARED_LIBRARY)
+
+# Test utilities (e.g. mock classes) for libwifi-system
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libwifi-system-test
+LOCAL_CFLAGS := $(wifi_system_cflags)
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/include \
+    $(LOCAL_PATH)/testlib/include
+LOCAL_STATIC_LIBRARIES := libgmock
+LOCAL_EXPORT_C_INCLUDE_DIRS := \
+    $(LOCAL_PATH)/include \
+    $(LOCAL_PATH)/testlib/include
+include $(BUILD_STATIC_LIBRARY)
+
+
+# Unit tests for libwifi-system
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libwifi-system_tests
+LOCAL_CPPFLAGS := $(wificond_cpp_flags)
+LOCAL_SRC_FILES := \
+    tests/main.cpp \
+    tests/hostapd_manager_unittest.cpp
+LOCAL_STATIC_LIBRARIES := \
+    libgmock \
+    libgtest
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libwifi-system
+include $(BUILD_NATIVE_TEST)
diff --git a/libwifi_system/hostapd_manager.cpp b/libwifi_system/hostapd_manager.cpp
new file mode 100644
index 0000000..68184e9
--- /dev/null
+++ b/libwifi_system/hostapd_manager.cpp
@@ -0,0 +1,198 @@
+/*
+ * 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/parseint.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/supplicant_manager.h"
+
+using android::base::ParseInt;
+using android::base::ReadFileToString;
+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 (!SupplicantManager::EnsureEntropyFileExists()) {
+    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";
+  return true;
+}
+
+bool HostapdManager::StopHostapd() {
+  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";
+  return true;
+}
+
+bool HostapdManager::WriteHostapdConfig(const string& config) {
+  if (!WriteStringToFile(config, kHostapdConfigFilePath,
+                         S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
+                         AID_WIFI, AID_WIFI)) {
+    int error = errno;
+    LOG(ERROR) << "Cannot write hostapd config to \""
+               << kHostapdConfigFilePath << "\": " << strerror(error);
+    struct stat st;
+    int result = stat(kHostapdConfigFilePath, &st);
+    if (result == 0) {
+      LOG(ERROR) << "hostapd config file uid: "<< st.st_uid << ", gid: " << st.st_gid
+                 << ", mode: " << st.st_mode;
+    } else {
+      LOG(ERROR) << "Error calling stat() on hostapd config file: " << strerror(errno);
+    }
+    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/ctrl\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/hostapd_manager.h b/libwifi_system/include/wifi_system/hostapd_manager.h
new file mode 100644
index 0000000..4852670
--- /dev/null
+++ b/libwifi_system/include/wifi_system/hostapd_manager.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#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();
+
+  // 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:
+  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..aabdd9a
--- /dev/null
+++ b/libwifi_system/include/wifi_system/interface_tool.h
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+  // Get the interface state of |if_name|.
+  // Returns true iff the interface is up.
+  virtual bool GetUpState(const char* if_name);
+
+  // 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/supplicant_manager.h b/libwifi_system/include/wifi_system/supplicant_manager.h
new file mode 100644
index 0000000..e6a5789
--- /dev/null
+++ b/libwifi_system/include/wifi_system/supplicant_manager.h
@@ -0,0 +1,51 @@
+/*
+ * 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_SUPPLICANT_MANAGER_H
+#define ANDROID_WIFI_SYSTEM_SUPPLICANT_MANAGER_H
+
+#include <android-base/macros.h>
+
+namespace android {
+namespace wifi_system {
+
+class SupplicantManager {
+ public:
+  SupplicantManager() = default;
+  virtual ~SupplicantManager() = default;
+
+  // Request that supplicant be started.
+  // Returns true on success.
+  virtual bool StartSupplicant();
+
+  // Request that a running instance of supplicant be stopped.
+  // Returns true on success.
+  virtual bool StopSupplicant();
+
+  // Returns true iff supplicant is still running.
+  virtual bool IsSupplicantRunning();
+
+  // Returns true iff supplicant entropy file exists.
+  static bool EnsureEntropyFileExists();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SupplicantManager);
+};  // class SupplicantManager
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_SUPPLICANT_MANAGER_H
diff --git a/libwifi_system/interface_tool.cpp b/libwifi_system/interface_tool.cpp
new file mode 100644
index 0000000..f0d40ef
--- /dev/null
+++ b/libwifi_system/interface_tool.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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/logging.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace wifi_system {
+namespace {
+
+const char kWlan0InterfaceName[] = "wlan0";
+
+bool GetIfState(const char* if_name, int sock, struct ifreq* ifr) {
+  memset(ifr, 0, sizeof(*ifr));
+  if (strlcpy(ifr->ifr_name, if_name, sizeof(ifr->ifr_name)) >=
+      sizeof(ifr->ifr_name)) {
+    LOG(ERROR) << "Interface name is too long: " << if_name;
+    return false;
+  }
+
+  if (TEMP_FAILURE_RETRY(ioctl(sock, SIOCGIFFLAGS, ifr)) != 0) {
+    LOG(ERROR) << "Could not read interface state for " << if_name
+               << " (" << strerror(errno) << ")";
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+bool InterfaceTool::GetUpState(const char* if_name) {
+  base::unique_fd sock(socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+  if (sock.get() < 0) {
+    LOG(ERROR) << "Failed to open socket to set up/down state ("
+               << strerror(errno) << ")";
+    return false;
+  }
+
+  struct ifreq ifr;
+  if (!GetIfState(if_name, sock.get(), &ifr)) {
+    return false;  // logging done internally
+  }
+
+  return ifr.ifr_flags & IFF_UP;
+}
+
+bool InterfaceTool::SetUpState(const char* if_name, bool request_up) {
+  base::unique_fd sock(socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0));
+  if (sock.get() < 0) {
+    LOG(ERROR) << "Failed to open socket to set up/down state ("
+               << strerror(errno) << ")";
+    return false;
+  }
+
+  struct ifreq ifr;
+  if (!GetIfState(if_name, sock.get(), &ifr)) {
+    return false;  // logging done internally
+  }
+
+  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) {
+    LOG(ERROR) << "Could not set interface flags for " << if_name
+               << " (" << strerror(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/supplicant_manager.cpp b/libwifi_system/supplicant_manager.cpp
new file mode 100644
index 0000000..28010ec
--- /dev/null
+++ b/libwifi_system/supplicant_manager.cpp
@@ -0,0 +1,267 @@
+/*
+ * 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/supplicant_manager.h"
+
+#include <android-base/logging.h>
+#include <cutils/properties.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+// This ugliness is necessary to access internal implementation details
+// of the property subsystem.
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
+
+namespace android {
+namespace wifi_system {
+namespace {
+
+const char kSupplicantInitProperty[] = "init.svc.wpa_supplicant";
+const char kSupplicantConfigTemplatePath[] =
+    "/etc/wifi/wpa_supplicant.conf";
+const char kSupplicantConfigFile[] = "/data/misc/wifi/wpa_supplicant.conf";
+const char kP2pConfigFile[] = "/data/misc/wifi/p2p_supplicant.conf";
+const char kSupplicantServiceName[] = "wpa_supplicant";
+constexpr mode_t kConfigFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
+
+const char kWiFiEntropyFile[] = "/data/misc/wifi/entropy.bin";
+
+const unsigned char kDummyKey[21] = {0x02, 0x11, 0xbe, 0x33, 0x43, 0x35, 0x68,
+                                     0x47, 0x84, 0x99, 0xa9, 0x2b, 0x1c, 0xd3,
+                                     0xee, 0xff, 0xf1, 0xe2, 0xf3, 0xf4, 0xf5};
+
+int ensure_config_file_exists(const char* config_file) {
+  char buf[2048];
+  int srcfd, destfd;
+  int nread;
+  int ret;
+  std::string templatePath;
+
+  ret = access(config_file, R_OK | W_OK);
+  if ((ret == 0) || (errno == EACCES)) {
+    if ((ret != 0) && (chmod(config_file, kConfigFileMode) != 0)) {
+      LOG(ERROR) << "Cannot set RW to \"" << config_file << "\": "
+                 << strerror(errno);
+      return false;
+    }
+    return true;
+  } else if (errno != ENOENT) {
+    LOG(ERROR) << "Cannot access \"" << config_file << "\": "
+               << strerror(errno);
+    return false;
+  }
+
+  std::string configPathSystem =
+      std::string("/system") + std::string(kSupplicantConfigTemplatePath);
+  std::string configPathVendor =
+      std::string("/vendor") + std::string(kSupplicantConfigTemplatePath);
+  srcfd = TEMP_FAILURE_RETRY(open(configPathSystem.c_str(), O_RDONLY));
+  templatePath = configPathSystem;
+  if (srcfd < 0) {
+    int errnoSystem = errno;
+    srcfd = TEMP_FAILURE_RETRY(open(configPathVendor.c_str(), O_RDONLY));
+    templatePath = configPathVendor;
+    if (srcfd < 0) {
+      int errnoVendor = errno;
+      LOG(ERROR) << "Cannot open \"" << configPathSystem << "\": "
+                 << strerror(errnoSystem);
+      LOG(ERROR) << "Cannot open \"" << configPathVendor << "\": "
+                 << strerror(errnoVendor);
+      return false;
+    }
+  }
+
+  destfd = TEMP_FAILURE_RETRY(open(config_file,
+                                   O_CREAT | O_RDWR,
+                                   kConfigFileMode));
+  if (destfd < 0) {
+    close(srcfd);
+    LOG(ERROR) << "Cannot create \"" << config_file << "\": "
+               << strerror(errno);
+    return false;
+  }
+
+  while ((nread = TEMP_FAILURE_RETRY(read(srcfd, buf, sizeof(buf)))) != 0) {
+    if (nread < 0) {
+      LOG(ERROR) << "Error reading \"" << templatePath
+                 << "\": " << strerror(errno);
+      close(srcfd);
+      close(destfd);
+      unlink(config_file);
+      return false;
+    }
+    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, kConfigFileMode) < 0) {
+    LOG(ERROR) << "Error changing permissions of " << config_file
+               << " to 0660: " << strerror(errno);
+    unlink(config_file);
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+bool SupplicantManager::StartSupplicant() {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+  int count = 200; /* wait at most 20 seconds for completion */
+  const prop_info* pi;
+  unsigned serial = 0;
+
+  /* Check whether already running */
+  if (property_get(kSupplicantInitProperty, supp_status, NULL) &&
+      strcmp(supp_status, "running") == 0) {
+    return true;
+  }
+
+  /* Before starting the daemon, make sure its config file exists */
+  if (ensure_config_file_exists(kSupplicantConfigFile) < 0) {
+    LOG(ERROR) << "Wi-Fi will not be enabled";
+    return false;
+  }
+
+  /*
+   * Some devices have another configuration file for the p2p interface.
+   * However, not all devices have this, and we'll let it slide if it
+   * is missing.  For devices that do expect this file to exist,
+   * supplicant will refuse to start and emit a good error message.
+   * No need to check for it here.
+   */
+  (void)ensure_config_file_exists(kP2pConfigFile);
+
+  if (!EnsureEntropyFileExists()) {
+    LOG(ERROR) << "Wi-Fi entropy file was not created";
+  }
+
+  /*
+   * 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(kSupplicantInitProperty);
+  if (pi != NULL) {
+    serial = __system_property_serial(pi);
+  }
+
+  property_set("ctl.start", kSupplicantServiceName);
+  sched_yield();
+
+  while (count-- > 0) {
+    if (pi == NULL) {
+      pi = __system_property_find(kSupplicantInitProperty);
+    }
+    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 true;
+        } else if (strcmp(supp_status, "stopped") == 0) {
+          return false;
+        }
+      }
+    }
+    usleep(100000);
+  }
+  return false;
+}
+
+bool SupplicantManager::StopSupplicant() {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+  int count = 50; /* wait at most 5 seconds for completion */
+
+  /* Check whether supplicant already stopped */
+  if (property_get(kSupplicantInitProperty, supp_status, NULL) &&
+      strcmp(supp_status, "stopped") == 0) {
+    return true;
+  }
+
+  property_set("ctl.stop", kSupplicantServiceName);
+  sched_yield();
+
+  while (count-- > 0) {
+    if (property_get(kSupplicantInitProperty, supp_status, NULL)) {
+      if (strcmp(supp_status, "stopped") == 0) return true;
+    }
+    usleep(100000);
+  }
+  LOG(ERROR) << "Failed to stop supplicant";
+  return false;
+}
+
+bool SupplicantManager::IsSupplicantRunning() {
+  char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
+  if (property_get(kSupplicantInitProperty, supp_status, NULL)) {
+    return strcmp(supp_status, "running") == 0;
+  }
+  return false;  // Failed to read service status from init.
+}
+
+bool SupplicantManager::EnsureEntropyFileExists() {
+  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)) {
+      PLOG(ERROR) << "Cannot set RW to " << kWiFiEntropyFile;
+      return false;
+    }
+    return true;
+  }
+  destfd = TEMP_FAILURE_RETRY(open(kWiFiEntropyFile, O_CREAT | O_RDWR, 0660));
+  if (destfd < 0) {
+    PLOG(ERROR) << "Cannot create " << kWiFiEntropyFile;
+    return false;
+  }
+
+  if (TEMP_FAILURE_RETRY(write(destfd, kDummyKey, sizeof(kDummyKey))) !=
+      sizeof(kDummyKey)) {
+    PLOG(ERROR) << "Error writing " << kWiFiEntropyFile;
+    close(destfd);
+    return false;
+  }
+  close(destfd);
+
+  /* chmod is needed because open() didn't set permisions properly */
+  if (chmod(kWiFiEntropyFile, 0660) < 0) {
+    PLOG(ERROR) << "Error changing permissions of " << kWiFiEntropyFile
+                << " to 0600 ";
+    unlink(kWiFiEntropyFile);
+    return false;
+  }
+
+  return true;
+}
+
+}  // 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_hostapd_manager.h b/libwifi_system/testlib/include/wifi_system_test/mock_hostapd_manager.h
new file mode 100644
index 0000000..6dbeafe
--- /dev/null
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_hostapd_manager.h
@@ -0,0 +1,46 @@
+/*
+ * 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_HOSTAPD_MANAGER_H
+#define ANDROID_WIFI_SYSTEM_TEST_MOCK_HOSTAPD_MANAGER_H
+
+#include <wifi_system/hostapd_manager.h>
+
+namespace android {
+namespace wifi_system {
+
+class MockHostapdManager : public HostapdManager {
+ public:
+  ~MockHostapdManager() override = default;
+
+  MOCK_METHOD0(StartHostapd, bool());
+  MOCK_METHOD0(IsHostapdRunning, bool());
+  MOCK_METHOD0(StopHostapd, bool());
+  MOCK_METHOD6(CreateHostapdConfig, std::string(
+      const std::string& interface_name,
+      const std::vector<uint8_t> ssid,
+      bool is_hidden,
+      int channel,
+      EncryptionType encryption,
+      const std::vector<uint8_t> passphrase));
+  MOCK_METHOD1(WriteHostapdConfig, bool(const std::string& config_file));
+
+};  // class MockHostapdManager
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_TEST_MOCK_HOSTAPD_MANAGER_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..b9926c9
--- /dev/null
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_interface_tool.h
@@ -0,0 +1,38 @@
+/*
+ * 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_METHOD1(GetUpState, bool(const char* if_name));
+  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/testlib/include/wifi_system_test/mock_supplicant_manager.h b/libwifi_system/testlib/include/wifi_system_test/mock_supplicant_manager.h
new file mode 100644
index 0000000..01d604f
--- /dev/null
+++ b/libwifi_system/testlib/include/wifi_system_test/mock_supplicant_manager.h
@@ -0,0 +1,38 @@
+/*
+ * 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_SUPPLICANT_MANAGER_H
+#define ANDROID_WIFI_SYSTEM_TEST_MOCK_SUPPLICANT_MANAGER_H
+
+#include <wifi_system/supplicant_manager.h>
+
+namespace android {
+namespace wifi_system {
+
+class MockSupplicantManager : public SupplicantManager {
+ public:
+  ~MockSupplicantManager() override = default;
+
+  MOCK_METHOD0(StartSupplicant, bool());
+  MOCK_METHOD0(StopSupplicant, bool());
+  MOCK_METHOD0(IsSupplicantRunning, bool());
+
+};  // class MockSupplicantManager
+
+}  // namespace wifi_system
+}  // namespace android
+
+#endif  // ANDROID_WIFI_SYSTEM_TEST_MOCK_SUPPLICANT_MANAGER_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..048d473
--- /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/ctrl\n" \
+    "ssid2=68656c6c6f" "6973" "6974" "6d65\n" \
+    "channel=2\n" \
+    "ieee80211n=1\n" \
+    "hw_mode=g\n"
+
+// If you generate your config file with both the test ssid
+// and the test passphrase, you'll get this line in the config.
+#define CONFIG_PSK_LINE \
+    "wpa_psk=dffa36815281e5a6eca1910f254717fa2528681335e3bbec5966d2aa9221a66e\n"
+
+#define CONFIG_WPA_SUFFIX \
+    "wpa=3\n" \
+    "wpa_pairwise=TKIP CCMP\n" \
+    CONFIG_PSK_LINE
+
+#define CONFIG_WPA2_SUFFIX \
+    "wpa=2\n" \
+    "rsn_pairwise=CCMP\n" \
+    CONFIG_PSK_LINE
+
+const char kExpectedOpenConfig[] =
+  CONFIG_COMMON_PREFIX
+  "ignore_broadcast_ssid=0\n"
+  "wowlan_triggers=any\n";
+
+const char kExpectedWpaConfig[] =
+    CONFIG_COMMON_PREFIX
+    "ignore_broadcast_ssid=0\n"
+    "wowlan_triggers=any\n"
+    CONFIG_WPA_SUFFIX;
+
+const char kExpectedWpa2Config[] =
+    CONFIG_COMMON_PREFIX
+    "ignore_broadcast_ssid=0\n"
+    "wowlan_triggers=any\n"
+    CONFIG_WPA2_SUFFIX;
+
+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/service/Android.mk b/service/Android.mk
index 8201acf..b6288be 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -16,129 +16,58 @@
 
 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 += \
+	liblog \
 	libnativehelper \
 	libcutils \
 	libutils \
-	libhardware \
-	libhardware_legacy \
-	libnl \
 	libdl
 
-LOCAL_STATIC_LIBRARIES += libwifi-hal-stub
-LOCAL_STATIC_LIBRARIES += $(LIB_WIFI_HAL)
-
 LOCAL_SRC_FILES := \
 	jni/com_android_server_wifi_WifiNative.cpp \
 	jni/jni_helper.cpp
 
-ifdef INCLUDE_NAN_FEATURE
-LOCAL_SRC_FILES += \
-	jni/com_android_server_wifi_nan_WifiNanNative.cpp
-endif
-
 LOCAL_MODULE := libwifi-service
-# b/22172328
-LOCAL_CLANG := false
 
 include $(BUILD_SHARED_LIBRARY)
 
 # 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-logtags-files-under, java) \
-	$(call all-proto-files-under, proto)
+	$(call all-Iaidl-files-under, $(wificond_aidl_rel_path)) \
+	$(call all-logtags-files-under, java)
 
-ifndef INCLUDE_NAN_FEATURE
-LOCAL_SRC_FILES := $(filter-out $(call all-java-files-under, \
-          java/com/android/server/wifi/nan),$(LOCAL_SRC_FILES))
-endif
-
-LOCAL_JAVA_LIBRARIES := bouncycastle conscrypt services
+LOCAL_JAVA_LIBRARIES := \
+	android.hidl.manager-V1.0-java \
+	bouncycastle \
+	conscrypt \
+	jsr305 \
+	services
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	android.hardware.wifi-V1.0-java \
+	android.hardware.wifi.supplicant-V1.0-java
 LOCAL_REQUIRED_MODULES := services
 LOCAL_MODULE_TAGS :=
 LOCAL_MODULE := wifi-service
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_INIT_RC := wifi-events.rc
 
 ifeq ($(EMMA_INSTRUMENT_FRAMEWORK),true)
 LOCAL_EMMA_INSTRUMENT := true
@@ -148,4 +77,4 @@
 
 include $(BUILD_JAVA_LIBRARY)
 
-endif
+endif  # !TARGET_BUILD_PDK
diff --git a/service/java/com/android/server/wifi/ActiveModeManager.java b/service/java/com/android/server/wifi/ActiveModeManager.java
new file mode 100644
index 0000000..00ae7b3
--- /dev/null
+++ b/service/java/com/android/server/wifi/ActiveModeManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Base class for available WiFi operating modes.
+ *
+ * Currently supported modes include Client, ScanOnly and SoftAp.
+ */
+public interface ActiveModeManager {
+    String TAG = "ActiveModeManager";
+
+    /**
+     * Method used to start the Manager for a given Wifi operational mode.
+     */
+    void start();
+
+    /**
+     * Method used to stop the Manager for a give Wifi operational mode.
+     */
+    void stop();
+}
diff --git a/service/java/com/android/server/wifi/BaseWifiDiagnostics.java b/service/java/com/android/server/wifi/BaseWifiDiagnostics.java
new file mode 100644
index 0000000..54fff68
--- /dev/null
+++ b/service/java/com/android/server/wifi/BaseWifiDiagnostics.java
@@ -0,0 +1,60 @@
+
+package com.android.server.wifi;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ *
+ */
+public class BaseWifiDiagnostics {
+    public static final byte CONNECTION_EVENT_STARTED = 0;
+    public static final byte CONNECTION_EVENT_SUCCEEDED = 1;
+    public static final byte CONNECTION_EVENT_FAILED = 2;
+
+    protected final WifiNative mWifiNative;
+
+    protected String mFirmwareVersion;
+    protected String mDriverVersion;
+    protected int mSupportedFeatureSet;
+
+    public BaseWifiDiagnostics(WifiNative wifiNative) {
+        mWifiNative = wifiNative;
+    }
+
+    public synchronized void startLogging(boolean verboseEnabled) {
+        mFirmwareVersion = mWifiNative.getFirmwareVersion();
+        mDriverVersion = mWifiNative.getDriverVersion();
+        mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet();
+    }
+
+    public synchronized void startPacketLog() { }
+
+    public synchronized void stopPacketLog() { }
+
+    public synchronized void stopLogging() { }
+
+    /**
+     * Inform the diagnostics module of a connection event.
+     * @param connectionId A strictly increasing, non-negative, connection identifier
+     * @param event The type of connection event (see CONNECTION_EVENT_* constants)
+     */
+    synchronized void reportConnectionEvent(long connectionId, byte event) {}
+
+    public synchronized void captureBugReportData(int reason) { }
+
+    public synchronized void captureAlertData(int errorCode, byte[] alertData) { }
+
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        dump(pw);
+        pw.println("*** firmware logging disabled, no debug data ****");
+        pw.println("set config_wifi_enable_wifi_firmware_debugging to enable");
+    }
+
+    protected synchronized void dump(PrintWriter pw) {
+        pw.println("Chipset information :-----------------------------------------------");
+        pw.println("FW Version is: " + mFirmwareVersion);
+        pw.println("Driver Version is: " + mDriverVersion);
+        pw.println("Supported Feature set: " + mSupportedFeatureSet);
+    }
+}
\ No newline at end of file
diff --git a/service/java/com/android/server/wifi/BaseWifiLogger.java b/service/java/com/android/server/wifi/BaseWifiLogger.java
deleted file mode 100644
index 43139d2..0000000
--- a/service/java/com/android/server/wifi/BaseWifiLogger.java
+++ /dev/null
@@ -1,49 +0,0 @@
-
-package com.android.server.wifi;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- *
- */
-public class BaseWifiLogger {
-
-    protected String mFirmwareVersion;
-    protected String mDriverVersion;
-    protected int mSupportedFeatureSet;
-
-    public BaseWifiLogger() { }
-
-    public synchronized void startLogging(boolean verboseEnabled) {
-        WifiNative wifiNative = WifiNative.getWlanNativeInterface();
-        mFirmwareVersion = wifiNative.getFirmwareVersion();
-        mDriverVersion = wifiNative.getDriverVersion();
-        mSupportedFeatureSet = wifiNative.getSupportedLoggerFeatureSet();
-    }
-
-    public synchronized void startPacketLog() { }
-
-    public synchronized void stopPacketLog() { }
-
-    public synchronized void stopLogging() { }
-
-    synchronized void reportConnectionFailure() {}
-
-    public synchronized void captureBugReportData(int reason) { }
-
-    public synchronized void captureAlertData(int errorCode, byte[] alertData) { }
-
-    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        dump(pw);
-        pw.println("*** firmware logging disabled, no debug data ****");
-        pw.println("set config_wifi_enable_wifi_firmware_debugging to enable");
-    }
-
-    protected synchronized void dump(PrintWriter pw) {
-        pw.println("Chipset information :-----------------------------------------------");
-        pw.println("FW Version is: " + mFirmwareVersion);
-        pw.println("Driver Version is: " + mDriverVersion);
-        pw.println("Supported Feature set: " + mSupportedFeatureSet);
-    }
-}
\ No newline at end of file
diff --git a/service/java/com/android/server/wifi/ByteBufferReader.java b/service/java/com/android/server/wifi/ByteBufferReader.java
new file mode 100644
index 0000000..91598af
--- /dev/null
+++ b/service/java/com/android/server/wifi/ByteBufferReader.java
@@ -0,0 +1,98 @@
+/*
+ * 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.Wifi
+ */
+
+package com.android.server.wifi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+/**
+ * Utility class for reading generic data (e.g. various length integer, string) from ByteBuffer.
+ */
+public class ByteBufferReader {
+    @VisibleForTesting
+    public static final int MINIMUM_INTEGER_SIZE = Byte.BYTES;
+
+    @VisibleForTesting
+    public static final int MAXIMUM_INTEGER_SIZE = Long.BYTES;
+
+    /**
+     * Read an integer value from a buffer.
+     *
+     * @param payload The buffer to read from
+     * @param byteOrder Byte order of the buffer
+     * @param size The number of bytes to read from the buffer
+     * @return The integer value
+     * @throws BufferUnderflowException
+     * @throws IllegalArgumentException
+     */
+    public static long readInteger(ByteBuffer payload, ByteOrder byteOrder, int size) {
+        if (size < MINIMUM_INTEGER_SIZE || size > MAXIMUM_INTEGER_SIZE) {
+            throw new IllegalArgumentException("Invalid size " + size);
+        }
+
+        // Read the necessary bytes.
+        byte[] octets = new byte[size];
+        payload.get(octets);
+
+        // Format the value based on byte order.
+        long value = 0;
+        if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
+            for (int n = octets.length - 1; n >= 0; n--) {
+                value = (value << Byte.SIZE) | (octets[n] & 0xFF);
+            }
+        } else {
+            for (byte octet : octets) {
+                value = (value << Byte.SIZE) | (octet & 0xFF);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Read a string from a buffer. An empty String will be returned for a String with 0 length.
+     *
+     * @param payload The buffer to read from
+     * @param length Number of bytes to read from the buffer
+     * @param charset The character set of the string
+     * @return {@link String}
+     * @throws BufferUnderflowException
+     * @throws NegativeArraySizeException
+     */
+    public static String readString(ByteBuffer payload, int length, Charset charset) {
+        byte[] octets = new byte[length];
+        payload.get(octets);
+        return new String(octets, charset);
+    }
+
+    /**
+     * Read a string from a buffer where the string value is preceded by the length of the string
+     * (1 byte) in the buffer.
+     *
+     * @param payload The buffer to read from
+     * @param charset The character set of the string
+     * @return {@link String}
+     * @throws BufferUnderflowException
+     */
+    public static String readStringWithByteLength(ByteBuffer payload, Charset charset) {
+        int length = payload.get() & 0xFF;
+        return readString(payload, length, charset);
+    }
+}
diff --git a/service/java/com/android/server/wifi/ClientModeManager.java b/service/java/com/android/server/wifi/ClientModeManager.java
new file mode 100644
index 0000000..7ab33dd
--- /dev/null
+++ b/service/java/com/android/server/wifi/ClientModeManager.java
@@ -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.
+ */
+
+package com.android.server.wifi;
+
+/**
+ * Manager WiFi in Client Mode where we connect to configured networks.
+ */
+public class ClientModeManager implements ActiveModeManager {
+
+    private static final String TAG = "ClientModeManager";
+
+    ClientModeManager() {
+    }
+
+    /**
+     * Start client mode.
+     */
+    public void start() {
+
+    }
+
+    /**
+     * Disconnect from any currently connected networks and stop client mode.
+     */
+    public void stop() {
+
+    }
+}
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/ConfigurationMap.java b/service/java/com/android/server/wifi/ConfigurationMap.java
index a94ff0d..fc85f64 100644
--- a/service/java/com/android/server/wifi/ConfigurationMap.java
+++ b/service/java/com/android/server/wifi/ConfigurationMap.java
@@ -16,15 +16,9 @@
 
 public class ConfigurationMap {
     private final Map<Integer, WifiConfiguration> mPerID = new HashMap<>();
-    private final Map<Integer, WifiConfiguration> mPerConfigKey = new HashMap<>();
 
     private final Map<Integer, WifiConfiguration> mPerIDForCurrentUser = new HashMap<>();
     private final Map<String, WifiConfiguration> mPerFQDNForCurrentUser = new HashMap<>();
-    /**
-     * List of all hidden networks in the current user's configuration.
-     * Use this list as a param for directed scanning .
-     */
-    private final Set<Integer> mHiddenNetworkIdsForCurrentUser = new HashSet<>();
 
     private final UserManager mUserManager;
 
@@ -37,16 +31,12 @@
     // RW methods:
     public WifiConfiguration put(WifiConfiguration config) {
         final WifiConfiguration current = mPerID.put(config.networkId, config);
-        mPerConfigKey.put(config.configKey().hashCode(), config);   // This is ridiculous...
         if (WifiConfigurationUtil.isVisibleToAnyProfile(config,
                 mUserManager.getProfiles(mCurrentUserId))) {
             mPerIDForCurrentUser.put(config.networkId, config);
             if (config.FQDN != null && config.FQDN.length() > 0) {
                 mPerFQDNForCurrentUser.put(config.FQDN, config);
             }
-            if (config.hiddenSSID) {
-                mHiddenNetworkIdsForCurrentUser.add(config.networkId);
-            }
         }
         return current;
     }
@@ -56,7 +46,6 @@
         if (config == null) {
             return null;
         }
-        mPerConfigKey.remove(config.configKey().hashCode());
 
         mPerIDForCurrentUser.remove(netID);
         Iterator<Map.Entry<String, WifiConfiguration>> entries =
@@ -67,52 +56,22 @@
                 break;
             }
         }
-        mHiddenNetworkIdsForCurrentUser.remove(netID);
         return config;
     }
 
     public void clear() {
         mPerID.clear();
-        mPerConfigKey.clear();
         mPerIDForCurrentUser.clear();
         mPerFQDNForCurrentUser.clear();
-        mHiddenNetworkIdsForCurrentUser.clear();
     }
 
     /**
-     * Handles the switch to a different foreground user:
-     * - Hides private network configurations belonging to the previous foreground user
-     * - Reveals private network configurations belonging to the new foreground user
+     * Sets the new foreground user ID.
      *
      * @param userId the id of the new foreground user
-     * @return a list of {@link WifiConfiguration}s that became hidden because of the user switch
      */
-    public List<WifiConfiguration> handleUserSwitch(int userId) {
-        mPerIDForCurrentUser.clear();
-        mPerFQDNForCurrentUser.clear();
-        mHiddenNetworkIdsForCurrentUser.clear();
-
-        final List<UserInfo> previousUserProfiles = mUserManager.getProfiles(mCurrentUserId);
+    public void setNewUser(int userId) {
         mCurrentUserId = userId;
-        final List<UserInfo> currentUserProfiles = mUserManager.getProfiles(mCurrentUserId);
-
-        final List<WifiConfiguration> hiddenConfigurations = new ArrayList<>();
-        for (Map.Entry<Integer, WifiConfiguration> entry : mPerID.entrySet()) {
-            final WifiConfiguration config = entry.getValue();
-            if (WifiConfigurationUtil.isVisibleToAnyProfile(config, currentUserProfiles)) {
-                mPerIDForCurrentUser.put(entry.getKey(), config);
-                if (config.FQDN != null && config.FQDN.length() > 0) {
-                    mPerFQDNForCurrentUser.put(config.FQDN, config);
-                }
-                if (config.hiddenSSID) {
-                    mHiddenNetworkIdsForCurrentUser.add(config.networkId);
-                }
-            } else if (WifiConfigurationUtil.isVisibleToAnyProfile(config, previousUserProfiles)) {
-                hiddenConfigurations.add(config);
-            }
-        }
-
-        return hiddenConfigurations;
     }
 
     // RO methods:
@@ -148,10 +107,6 @@
         return null;
     }
 
-    public WifiConfiguration getByConfigKeyIDForAllUsers(int id) {
-        return mPerConfigKey.get(id);
-    }
-
     public Collection<WifiConfiguration> getEnabledNetworksForCurrentUser() {
         List<WifiConfiguration> list = new ArrayList<>();
         for (WifiConfiguration config : mPerIDForCurrentUser.values()) {
@@ -178,8 +133,4 @@
     public Collection<WifiConfiguration> valuesForCurrentUser() {
         return mPerIDForCurrentUser.values();
     }
-
-    public Set<Integer> getHiddenNetworkIdsForCurrentUser() {
-        return mHiddenNetworkIdsForCurrentUser;
-    }
 }
diff --git a/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java b/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java
new file mode 100644
index 0000000..201a132
--- /dev/null
+++ b/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import com.android.server.wifi.util.XmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class performs serialization and parsing of XML data block that contain the list of
+ * deleted ephemeral SSIDs (XML block data inside <DeletedEphemeralSSIDList> tag).
+ */
+public class DeletedEphemeralSsidsStoreData implements WifiConfigStore.StoreData {
+    private static final String XML_TAG_SECTION_HEADER_DELETED_EPHEMERAL_SSID_LIST =
+            "DeletedEphemeralSSIDList";
+    private static final String XML_TAG_SSID_LIST = "SSIDList";
+
+    private Set<String> mSsidList;
+
+    DeletedEphemeralSsidsStoreData() {}
+
+    @Override
+    public void serializeData(XmlSerializer out, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Share data not supported");
+        }
+        if (mSsidList != null) {
+            XmlUtil.writeNextValue(out, XML_TAG_SSID_LIST, mSsidList);
+        }
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Share data not supported");
+        }
+
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            String[] valueName = new String[1];
+            Object value = XmlUtil.readCurrentValue(in, valueName);
+            if (valueName[0] == null) {
+                throw new XmlPullParserException("Missing value name");
+            }
+            switch (valueName[0]) {
+                case XML_TAG_SSID_LIST:
+                    mSsidList = (Set<String>) value;
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown tag under "
+                            + XML_TAG_SECTION_HEADER_DELETED_EPHEMERAL_SSID_LIST
+                            + ": " + valueName[0]);
+            }
+        }
+    }
+
+    @Override
+    public void resetData(boolean shared) {
+        if (!shared) {
+            mSsidList = null;
+        }
+    }
+
+    @Override
+    public String getName() {
+        return XML_TAG_SECTION_HEADER_DELETED_EPHEMERAL_SSID_LIST;
+    }
+
+    @Override
+    public boolean supportShareData() {
+        return false;
+    }
+
+    /**
+     * An empty set will be returned for null SSID list.
+     *
+     * @return Set of SSIDs
+     */
+    public Set<String> getSsidList() {
+        if (mSsidList == null) {
+            return new HashSet<String>();
+        }
+        return mSsidList;
+    }
+
+    public void setSsidList(Set<String> ssidList) {
+        mSsidList = ssidList;
+    }
+}
+
diff --git a/service/java/com/android/server/wifi/DummyLogMessage.java b/service/java/com/android/server/wifi/DummyLogMessage.java
new file mode 100644
index 0000000..7eafcc7
--- /dev/null
+++ b/service/java/com/android/server/wifi/DummyLogMessage.java
@@ -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.
+ */
+
+package com.android.server.wifi;
+
+/** LogMessage implementation that does nothing. */
+public class DummyLogMessage implements WifiLog.LogMessage {
+    @Override
+    public WifiLog.LogMessage r(String value) {
+        return this;
+    }
+
+    @Override
+    public WifiLog.LogMessage c(String value) {
+        return this;
+    }
+
+    @Override
+    public WifiLog.LogMessage c(long value) {
+        return this;
+    }
+
+    @Override
+    public WifiLog.LogMessage c(char value) {
+        return this;
+    }
+
+    @Override
+    public WifiLog.LogMessage c(boolean value) {
+        return this;
+    }
+
+    @Override
+    public void flush() {
+        // Nothing to do.
+    }
+}
diff --git a/service/java/com/android/server/wifi/FakeWifiLog.java b/service/java/com/android/server/wifi/FakeWifiLog.java
new file mode 100644
index 0000000..a58d77f
--- /dev/null
+++ b/service/java/com/android/server/wifi/FakeWifiLog.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+/** WifiLog implementation that does nothing. */
+public class FakeWifiLog implements WifiLog {
+    private static final DummyLogMessage sDummyLogMessage = new DummyLogMessage();
+
+    // New-style methods.
+    @Override
+    public LogMessage err(String format) {
+        return sDummyLogMessage;
+    }
+
+    @Override
+    public LogMessage warn(String format) {
+        return sDummyLogMessage;
+    }
+
+    @Override
+    public LogMessage info(String format) {
+        return sDummyLogMessage;
+    }
+
+    @Override
+    public LogMessage trace(String format) {
+        return sDummyLogMessage;
+    }
+
+    @Override
+    public LogMessage dump(String format) {
+        return sDummyLogMessage;
+    }
+
+    @Override
+    public void eC(String msg) {
+        // Do nothing.
+    }
+
+    @Override
+    public void wC(String msg) {
+        // Do nothing.
+    }
+
+    @Override
+    public void iC(String msg) {
+        // Do nothing.
+    }
+
+    @Override
+    public void tC(String msg) {
+        // Do nothing.
+    }
+
+    // Legacy methods.
+    @Override
+    public void e(String msg) {
+        // Do nothing.
+    }
+
+    @Override
+    public void w(String msg) {
+        // Do nothing.
+    }
+
+    @Override
+    public void i(String msg) {
+        // Do nothing.
+    }
+
+    @Override
+    public void d(String msg) {
+        // Do nothing.
+    }
+
+    @Override
+    public void v(String msg) {
+        // Do nothing.
+    }
+}
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index cb03f7a..ba114df 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -16,27 +16,26 @@
 
 package com.android.server.wifi;
 
+import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.net.ConnectivityManager;
+import android.database.ContentObserver;
 import android.net.TrafficStats;
+import android.net.Uri;
 import android.net.ip.IpManager;
-import android.net.wifi.IWifiScanner;
-import android.net.wifi.WifiScanner;
+import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.provider.Settings;
-import android.security.KeyStore;
 import android.telephony.CarrierConfigManager;
 
-import java.util.ArrayList;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.wifi.util.WifiAsyncChannel;
 
 /**
  * This class allows overriding objects with mocks to write unit tests
@@ -44,16 +43,6 @@
 public class FrameworkFacade {
     public static final String TAG = "FrameworkFacade";
 
-    public BaseWifiLogger makeBaseLogger() {
-        return new BaseWifiLogger();
-    }
-
-    public BaseWifiLogger makeRealLogger(
-            Context context, WifiStateMachine stateMachine, WifiNative wifiNative,
-            BuildProperties buildProperties) {
-        return new WifiLogger(context, stateMachine, wifiNative, buildProperties);
-    }
-
     public boolean setIntegerSetting(Context context, String name, int def) {
         return Settings.Global.putInt(context.getContentResolver(), name, def);
     }
@@ -74,13 +63,31 @@
         return Settings.Global.getString(context.getContentResolver(), name);
     }
 
+    /**
+     * Helper method for classes to register a ContentObserver
+     * {@see ContentResolver#registerContentObserver(Uri,boolean,ContentObserver)}.
+     *
+     * @param context
+     * @param uri
+     * @param notifyForDescendants
+     * @param contentObserver
+     */
+    public void registerContentObserver(Context context, Uri uri,
+            boolean notifyForDescendants, ContentObserver contentObserver) {
+        context.getContentResolver().registerContentObserver(uri, notifyForDescendants,
+                contentObserver);
+    }
+
     public IBinder getService(String serviceName) {
         return ServiceManager.getService(serviceName);
     }
 
-    public WifiScanner makeWifiScanner(Context context, Looper looper) {
-        return new WifiScanner(context, IWifiScanner.Stub.asInterface(
-                        getService(Context.WIFI_SCANNING_SERVICE)), looper);
+    /**
+     * Returns the battery stats interface
+     * @return IBatteryStats BatteryStats service interface
+     */
+    public IBatteryStats getBatteryService() {
+        return IBatteryStats.Stub.asInterface(getService(BatteryStats.SERVICE_NAME));
     }
 
     public PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags) {
@@ -89,7 +96,7 @@
 
     public SupplicantStateTracker makeSupplicantStateTracker(Context context,
             WifiConfigManager configManager, Handler handler) {
-        return new SupplicantStateTracker(context, configManager, handler);
+        return new SupplicantStateTracker(context, configManager, this, handler);
     }
 
     public boolean getConfigWiFiDisableInECBM(Context context) {
@@ -128,28 +135,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
@@ -160,10 +145,31 @@
         return AppGlobals.getPackageManager().checkUidPermission(permName, uid);
     }
 
-    public WifiConfigManager makeWifiConfigManager(Context context, WifiNative wifiNative,
-            FrameworkFacade frameworkFacade, Clock clock, UserManager userManager,
-            KeyStore keyStore) {
-        return new WifiConfigManager(context, wifiNative, frameworkFacade, clock, userManager,
-                keyStore);
+    /**
+     * Create a new instance of WifiAsyncChannel
+     * @param tag String corresponding to the service creating the channel
+     * @return WifiAsyncChannel object created
+     */
+    public WifiAsyncChannel makeWifiAsyncChannel(String tag) {
+        return new WifiAsyncChannel(tag);
+    }
+
+    /**
+     * Check if the device will be restarting after decrypting during boot by calling {@link
+     * StorageManager.inCryptKeeperBounce}.
+     * @return true if the device will restart, false otherwise
+     */
+    public boolean inStorageManagerCryptKeeperBounce() {
+        return StorageManager.inCryptKeeperBounce();
+    }
+
+    /**
+     * Check if the provided uid is the app in the foreground.
+     * @param uid the uid to check
+     * @return true if the app is in the foreground, false otherwise
+     * @throws RemoteException
+     */
+    public boolean isAppForeground(int uid) throws RemoteException {
+        return ActivityManager.getService().isAppForeground(uid);
     }
 }
diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java
new file mode 100644
index 0000000..a5e1273
--- /dev/null
+++ b/service/java/com/android/server/wifi/HalDeviceManager.java
@@ -0,0 +1,1950 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.hardware.wifi.V1_0.IWifi;
+import android.hardware.wifi.V1_0.IWifiApIface;
+import android.hardware.wifi.V1_0.IWifiChip;
+import android.hardware.wifi.V1_0.IWifiChipEventCallback;
+import android.hardware.wifi.V1_0.IWifiEventCallback;
+import android.hardware.wifi.V1_0.IWifiIface;
+import android.hardware.wifi.V1_0.IWifiNanIface;
+import android.hardware.wifi.V1_0.IWifiP2pIface;
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.IWifiStaIface;
+import android.hardware.wifi.V1_0.IfaceType;
+import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.Handler;
+import android.os.HwRemoteBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.MutableBoolean;
+import android.util.MutableInt;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handles device management through the HAL (HIDL) interface.
+ */
+public class HalDeviceManager {
+    private static final String TAG = "HalDeviceManager";
+    private static final boolean DBG = false;
+
+    private static final int START_HAL_RETRY_INTERVAL_MS = 20;
+    // Number of attempts a start() is re-tried. A value of 0 means no retries after a single
+    // attempt.
+    @VisibleForTesting
+    public static final int START_HAL_RETRY_TIMES = 3;
+    @VisibleForTesting
+    public static final String HAL_INSTANCE_NAME = "default";
+
+    // public API
+    public HalDeviceManager() {
+        mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashSet<>());
+        mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashSet<>());
+        mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashSet<>());
+        mInterfaceAvailableForRequestListeners.put(IfaceType.NAN, new HashSet<>());
+    }
+
+    /**
+     * Actually starts the HalDeviceManager: separate from constructor since may want to phase
+     * at a later time.
+     *
+     * TODO: if decide that no need for separating construction from initialization (e.g. both are
+     * done at injector) then move to constructor.
+     */
+    public void initialize() {
+        initializeInternal();
+    }
+
+    /**
+     * Register a ManagerStatusListener to get information about the status of the manager. Use the
+     * isReady() and isStarted() methods to check status immediately after registration and when
+     * triggered.
+     *
+     * It is safe to re-register the same callback object - duplicates are detected and only a
+     * single copy kept.
+     *
+     * @param listener ManagerStatusListener listener object.
+     * @param looper Looper on which to dispatch listener. Null implies current looper.
+     */
+    public void registerStatusListener(ManagerStatusListener listener, Looper looper) {
+        synchronized (mLock) {
+            if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
+                    looper == null ? Looper.myLooper() : looper))) {
+                Log.w(TAG, "registerStatusListener: duplicate registration ignored");
+            }
+        }
+    }
+
+    /**
+     * Returns whether the vendor HAL is supported on this device or not.
+     */
+    public boolean isSupported() {
+        return isSupportedInternal();
+    }
+
+    /**
+     * Returns the current status of the HalDeviceManager: whether or not it is ready to execute
+     * commands. A return of 'false' indicates that the HAL service (IWifi) is not available. Use
+     * the registerStatusListener() to listener for status changes.
+     */
+    public boolean isReady() {
+        return mWifi != null;
+    }
+
+    /**
+     * Returns the current status of Wi-Fi: started (true) or stopped (false).
+     *
+     * Note: direct call to HIDL.
+     */
+    public boolean isStarted() {
+        return isWifiStarted();
+    }
+
+    /**
+     * Attempts to start Wi-Fi (using HIDL). Returns the success (true) or failure (false) or
+     * the start operation. Will also dispatch any registered ManagerStatusCallback.onStart() on
+     * success.
+     *
+     * Note: direct call to HIDL.
+     */
+    public boolean start() {
+        return startWifi();
+    }
+
+    /**
+     * Stops Wi-Fi. Will also dispatch any registeredManagerStatusCallback.onStop().
+     *
+     * Note: direct call to HIDL - failure is not-expected.
+     */
+    public void stop() {
+        stopWifi();
+    }
+
+    /**
+     * HAL device manager status change listener.
+     */
+    public interface ManagerStatusListener {
+        /**
+         * Indicates that the status of the HalDeviceManager has changed. Use isReady() and
+         * isStarted() to obtain status information.
+         */
+        void onStatusChanged();
+    }
+
+    /**
+     * Return the set of supported interface types across all Wi-Fi chips on the device.
+     *
+     * @return A set of IfaceTypes constants (possibly empty, e.g. on error).
+     */
+    Set<Integer> getSupportedIfaceTypes() {
+        return getSupportedIfaceTypesInternal(null);
+    }
+
+    /**
+     * Return the set of supported interface types for the specified Wi-Fi chip.
+     *
+     * @return A set of IfaceTypes constants  (possibly empty, e.g. on error).
+     */
+    Set<Integer> getSupportedIfaceTypes(IWifiChip chip) {
+        return getSupportedIfaceTypesInternal(chip);
+    }
+
+    // interface-specific behavior
+
+    /**
+     * Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
+     * needed and permitted by priority.
+     *
+     * @param destroyedListener Optional (nullable) listener to call when the allocated interface
+     *                          is removed. Will only be registered and used if an interface is
+     *                          created successfully.
+     * @param looper The looper on which to dispatch the listener. A null value indicates the
+     *               current thread.
+     * @return A newly created interface - or null if the interface could not be created.
+     */
+    public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
+            Looper looper) {
+        return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, looper);
+    }
+
+    /**
+     * Create AP interface if possible (see createStaIface doc).
+     */
+    public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
+            Looper looper) {
+        return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, looper);
+    }
+
+    /**
+     * Create P2P interface if possible (see createStaIface doc).
+     */
+    public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
+            Looper looper) {
+        return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, looper);
+    }
+
+    /**
+     * Create NAN interface if possible (see createStaIface doc).
+     */
+    public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
+            Looper looper) {
+        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, looper);
+    }
+
+    /**
+     * Removes (releases/destroys) the given interface. Will trigger any registered
+     * InterfaceDestroyedListeners and possibly some InterfaceAvailableForRequestListeners if we
+     * can potentially create some other interfaces as a result of removing this interface.
+     */
+    public boolean removeIface(IWifiIface iface) {
+        boolean success = removeIfaceInternal(iface);
+        dispatchAvailableForRequestListeners();
+        return success;
+    }
+
+    /**
+     * Returns the IWifiChip corresponding to the specified interface (or null on error).
+     *
+     * Note: clients must not perform chip mode changes or interface management (create/delete)
+     * operations on IWifiChip directly. However, they can use the IWifiChip interface to perform
+     * other functions - e.g. calling the debug/trace methods.
+     */
+    public IWifiChip getChip(IWifiIface iface) {
+        if (DBG) Log.d(TAG, "getChip: iface(name)=" + getName(iface));
+
+        synchronized (mLock) {
+            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(iface);
+            if (cacheEntry == null) {
+                Log.e(TAG, "getChip: no entry for iface(name)=" + getName(iface));
+                return null;
+            }
+
+            return cacheEntry.chip;
+        }
+    }
+
+    /**
+     * Register an InterfaceDestroyedListener to the specified iface - returns true on success
+     * and false on failure. This listener is in addition to the one registered when the interface
+     * was created - allowing non-creators to monitor interface status.
+     *
+     * Listener called-back on the specified looper - or on the current looper if a null is passed.
+     */
+    public boolean registerDestroyedListener(IWifiIface iface,
+            InterfaceDestroyedListener destroyedListener,
+            Looper looper) {
+        if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + getName(iface));
+
+        synchronized (mLock) {
+            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(iface);
+            if (cacheEntry == null) {
+                Log.e(TAG, "registerDestroyedListener: no entry for iface(name)="
+                        + getName(iface));
+                return false;
+            }
+
+            return cacheEntry.destroyedListeners.add(
+                    new InterfaceDestroyedListenerProxy(destroyedListener,
+                            looper == null ? Looper.myLooper() : looper));
+        }
+    }
+
+    /**
+     * Register a listener to be called when an interface of the specified type could be requested.
+     * No guarantees are provided (some other entity could request it first). The listener is
+     * active from registration until unregistration - using
+     * unregisterInterfaceAvailableForRequestListener().
+     *
+     * Only a single instance of a listener will be registered (even if the specified looper is
+     * different).
+     *
+     * Note that if it is possible to create the specified interface type at registration time
+     * then the callback will be triggered immediately.
+     *
+     * @param ifaceType The interface type (IfaceType) to be monitored.
+     * @param listener Listener to call when an interface of the requested
+     *                 type could be created
+     * @param looper The looper on which to dispatch the listener. A null value indicates the
+     *               current thread.
+     */
+    public void registerInterfaceAvailableForRequestListener(int ifaceType,
+            InterfaceAvailableForRequestListener listener, Looper looper) {
+        mInterfaceAvailableForRequestListeners.get(ifaceType).add(
+                new InterfaceAvailableForRequestListenerProxy(listener,
+                        looper == null ? Looper.myLooper() : looper));
+
+        WifiChipInfo[] chipInfos = getAllChipInfo();
+        if (chipInfos == null) {
+            Log.e(TAG,
+                    "registerInterfaceAvailableForRequestListener: no chip info found - but "
+                            + "possibly registered pre-started - ignoring");
+            return;
+        }
+        dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
+    }
+
+    /**
+     * Unregisters a listener registered with registerInterfaceAvailableForRequestListener().
+     */
+    public void unregisterInterfaceAvailableForRequestListener(
+            int ifaceType,
+            InterfaceAvailableForRequestListener listener) {
+        Iterator<InterfaceAvailableForRequestListenerProxy> it =
+                mInterfaceAvailableForRequestListeners.get(ifaceType).iterator();
+        while (it.hasNext()) {
+            if (it.next().mListener == listener) {
+                it.remove();
+                return;
+            }
+        }
+    }
+
+    /**
+     * Return the name of the input interface or null on error.
+     */
+    public static String getName(IWifiIface iface) {
+        if (iface == null) {
+            return "<null>";
+        }
+
+        Mutable<String> nameResp = new Mutable<>();
+        try {
+            iface.getName((WifiStatus status, String name) -> {
+                if (status.code == WifiStatusCode.SUCCESS) {
+                    nameResp.value = name;
+                } else {
+                    Log.e(TAG, "Error on getName: " + statusString(status));
+                }
+            });
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception on getName: " + e);
+        }
+
+        return nameResp.value;
+    }
+
+    /**
+     * Called when interface is destroyed.
+     */
+    public interface InterfaceDestroyedListener {
+        /**
+         * Called for every interface on which registered when destroyed - whether
+         * destroyed by releaseIface() or through chip mode change or through Wi-Fi
+         * going down.
+         *
+         * Can be registered when the interface is requested with createXxxIface() - will
+         * only be valid if the interface creation was successful - i.e. a non-null was returned.
+         */
+        void onDestroyed();
+    }
+
+    /**
+     * Called when an interface type is possibly available for creation.
+     */
+    public interface InterfaceAvailableForRequestListener {
+        /**
+         * Registered when an interface type could be requested. Registered with
+         * registerInterfaceAvailableForRequestListener() and unregistered with
+         * unregisterInterfaceAvailableForRequestListener().
+         */
+        void onAvailableForRequest();
+    }
+
+    /**
+     * Creates a IWifiRttController corresponding to the input interface. A direct match to the
+     * IWifiChip.createRttController() method.
+     *
+     * Returns the created IWifiRttController or a null on error.
+     */
+    public IWifiRttController createRttController(IWifiIface boundIface) {
+        if (DBG) Log.d(TAG, "createRttController: boundIface(name)=" + getName(boundIface));
+        synchronized (mLock) {
+            if (mWifi == null) {
+                Log.e(TAG, "createRttController: null IWifi -- boundIface(name)="
+                        + getName(boundIface));
+                return null;
+            }
+
+            IWifiChip chip = getChip(boundIface);
+            if (chip == null) {
+                Log.e(TAG, "createRttController: null IWifiChip -- boundIface(name)="
+                        + getName(boundIface));
+                return null;
+            }
+
+            Mutable<IWifiRttController> rttResp = new Mutable<>();
+            try {
+                chip.createRttController(boundIface,
+                        (WifiStatus status, IWifiRttController rtt) -> {
+                            if (status.code == WifiStatusCode.SUCCESS) {
+                                rttResp.value = rtt;
+                            } else {
+                                Log.e(TAG, "IWifiChip.createRttController failed: " + statusString(
+                                        status));
+                            }
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "IWifiChip.createRttController exception: " + e);
+            }
+
+            return rttResp.value;
+        }
+    }
+
+    // internal state
+
+    /* This "PRIORITY" is not for deciding interface elimination (that is controlled by
+     * allowedToDeleteIfaceTypeForRequestedType. This priority is used for:
+     * - Comparing 2 configuration options
+     * - Order of dispatch of available for request listeners
+     */
+    private static final int[] IFACE_TYPES_BY_PRIORITY =
+            {IfaceType.AP, IfaceType.STA, IfaceType.P2P, IfaceType.NAN};
+
+    private final Object mLock = new Object();
+
+    private IServiceManager mServiceManager;
+    private IWifi mWifi;
+    private final WifiEventCallback mWifiEventCallback = new WifiEventCallback();
+    private final Set<ManagerStatusListenerProxy> mManagerStatusListeners = new HashSet<>();
+    private final SparseArray<Set<InterfaceAvailableForRequestListenerProxy>>
+            mInterfaceAvailableForRequestListeners = new SparseArray<>();
+
+    /*
+     * This is the only place where we cache HIDL information in this manager. Necessary since
+     * we need to keep a list of registered destroyed listeners. Will be validated regularly
+     * in getAllChipInfoAndValidateCache().
+     */
+    private final Map<IWifiIface, InterfaceCacheEntry> mInterfaceInfoCache = new HashMap<>();
+
+    private class InterfaceCacheEntry {
+        public IWifiChip chip;
+        public int chipId;
+        public String name;
+        public int type;
+        public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{name=").append(name).append(", type=").append(type)
+                    .append(", destroyedListeners.size()=").append(destroyedListeners.size())
+                    .append("}");
+            return sb.toString();
+        }
+    }
+
+    private class WifiIfaceInfo {
+        public String name;
+        public IWifiIface iface;
+    }
+
+    private class WifiChipInfo {
+        public IWifiChip chip;
+        public int chipId;
+        public ArrayList<IWifiChip.ChipMode> availableModes;
+        public boolean currentModeIdValid;
+        public int currentModeId;
+        public WifiIfaceInfo[][] ifaces = new WifiIfaceInfo[IFACE_TYPES_BY_PRIORITY.length][];
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{chipId=").append(chipId).append(", availableModes=").append(availableModes)
+                    .append(", currentModeIdValid=").append(currentModeIdValid)
+                    .append(", currentModeId=").append(currentModeId);
+            for (int type: IFACE_TYPES_BY_PRIORITY) {
+                sb.append(", ifaces[" + type + "].length=").append(ifaces[type].length);
+            }
+            sb.append(")");
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Wrapper function to access the HIDL services. Created to be mockable in unit-tests.
+     */
+    protected IWifi getWifiServiceMockable() {
+        try {
+            return IWifi.getService();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception getting IWifi service: " + e);
+            return null;
+        }
+    }
+
+    protected IServiceManager getServiceManagerMockable() {
+        try {
+            return IServiceManager.getService();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception getting IServiceManager: " + e);
+            return null;
+        }
+    }
+
+    // internal implementation
+
+    private void initializeInternal() {
+        initIServiceManagerIfNecessary();
+    }
+
+    private void teardownInternal() {
+        managerStatusListenerDispatch();
+        dispatchAllDestroyedListeners();
+        mInterfaceAvailableForRequestListeners.get(IfaceType.STA).clear();
+        mInterfaceAvailableForRequestListeners.get(IfaceType.AP).clear();
+        mInterfaceAvailableForRequestListeners.get(IfaceType.P2P).clear();
+        mInterfaceAvailableForRequestListeners.get(IfaceType.NAN).clear();
+    }
+
+    private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
+            cookie -> {
+                Log.wtf(TAG, "IServiceManager died: cookie=" + cookie);
+                synchronized (mLock) {
+                    mServiceManager = null;
+                    // theoretically can call initServiceManager again here - but
+                    // there's no point since most likely system is going to reboot
+                }
+            };
+
+    private final IServiceNotification mServiceNotificationCallback =
+            new IServiceNotification.Stub() {
+                @Override
+                public void onRegistration(String fqName, String name,
+                                           boolean preexisting) {
+                    Log.d(TAG, "IWifi registration notification: fqName=" + fqName
+                            + ", name=" + name + ", preexisting=" + preexisting);
+                    mWifi = null; // get rid of old copy!
+                    initIWifiIfNecessary();
+                    stopWifi(); // just in case
+                }
+            };
+
+    /**
+     * Failures of IServiceManager are most likely system breaking in any case. Behavior here
+     * will be to WTF and continue.
+     */
+    private void initIServiceManagerIfNecessary() {
+        if (DBG) Log.d(TAG, "initIServiceManagerIfNecessary");
+
+        synchronized (mLock) {
+            if (mServiceManager != null) {
+                return;
+            }
+
+            mServiceManager = getServiceManagerMockable();
+            if (mServiceManager == null) {
+                Log.wtf(TAG, "Failed to get IServiceManager instance");
+            } else {
+                try {
+                    if (!mServiceManager.linkToDeath(
+                            mServiceManagerDeathRecipient, /* don't care */ 0)) {
+                        Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
+                        mServiceManager = null;
+                        return;
+                    }
+
+                    if (!mServiceManager.registerForNotifications(IWifi.kInterfaceName, "",
+                            mServiceNotificationCallback)) {
+                        Log.wtf(TAG, "Failed to register a listener for IWifi service");
+                        mServiceManager = null;
+                    }
+                } catch (RemoteException e) {
+                    Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
+                    mServiceManager = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Uses the IServiceManager to query if the vendor HAL is present in the VINTF for the device
+     * or not.
+     * @return true if supported, false otherwise.
+     */
+    private boolean isSupportedInternal() {
+        if (DBG) Log.d(TAG, "isSupportedInternal");
+
+        synchronized (mLock) {
+            if (mServiceManager == null) {
+                Log.e(TAG, "isSupported: called but mServiceManager is null!?");
+                return false;
+            }
+            try {
+                return (mServiceManager.getTransport(IWifi.kInterfaceName, HAL_INSTANCE_NAME)
+                        != IServiceManager.Transport.EMPTY);
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
+                return false;
+            }
+        }
+    }
+
+    private final HwRemoteBinder.DeathRecipient mIWifiDeathRecipient =
+            cookie -> {
+                Log.e(TAG, "IWifi HAL service died! Have a listener for it ... cookie=" + cookie);
+                synchronized (mLock) { // prevents race condition with surrounding method
+                    mWifi = null;
+                    teardownInternal();
+                    // don't restart: wait for registration notification
+                }
+            };
+
+    /**
+     * Initialize IWifi and register death listener and event callback.
+     *
+     * - It is possible that IWifi is not ready - we have a listener on IServiceManager for it.
+     * - It is not expected that any of the registrations will fail. Possible indication that
+     *   service died after we obtained a handle to it.
+     *
+     * Here and elsewhere we assume that death listener will do the right thing!
+    */
+    private void initIWifiIfNecessary() {
+        if (DBG) Log.d(TAG, "initIWifiIfNecessary");
+
+        synchronized (mLock) {
+            if (mWifi != null) {
+                return;
+            }
+
+            try {
+                mWifi = getWifiServiceMockable();
+                if (mWifi == null) {
+                    Log.e(TAG, "IWifi not (yet) available - but have a listener for it ...");
+                    return;
+                }
+
+                if (!mWifi.linkToDeath(mIWifiDeathRecipient, /* don't care */ 0)) {
+                    Log.e(TAG, "Error on linkToDeath on IWifi - will retry later");
+                    return;
+                }
+
+                WifiStatus status = mWifi.registerEventCallback(mWifiEventCallback);
+                if (status.code != WifiStatusCode.SUCCESS) {
+                    Log.e(TAG, "IWifi.registerEventCallback failed: " + statusString(status));
+                    mWifi = null;
+                    return;
+                }
+                managerStatusListenerDispatch();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception while operating on IWifi: " + e);
+            }
+        }
+    }
+
+    /**
+     * Registers event listeners on all IWifiChips after a successful start: DEBUG only!
+     *
+     * We don't need the listeners since any callbacks are just confirmation of status codes we
+     * obtain directly from mode changes or interface creation/deletion.
+     *
+     * Relies (to the degree we care) on the service removing all listeners when Wi-Fi is stopped.
+     */
+    private void initIWifiChipDebugListeners() {
+        if (DBG) Log.d(TAG, "initIWifiChipDebugListeners");
+
+        if (!DBG) {
+            return;
+        }
+
+        synchronized (mLock) {
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
+
+                // get all chip IDs
+                mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
+                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        chipIdsResp.value = chipIds;
+                    } else {
+                        Log.e(TAG, "getChipIds failed: " + statusString(status));
+                    }
+                });
+                if (!statusOk.value) {
+                    return;
+                }
+
+                if (DBG) Log.d(TAG, "getChipIds=" + chipIdsResp.value);
+                if (chipIdsResp.value.size() == 0) {
+                    Log.e(TAG, "Should have at least 1 chip!");
+                    return;
+                }
+
+                // register a callback for each chip
+                Mutable<IWifiChip> chipResp = new Mutable<>();
+                for (Integer chipId: chipIdsResp.value) {
+                    mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
+                        statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                        if (statusOk.value) {
+                            chipResp.value = chip;
+                        } else {
+                            Log.e(TAG, "getChip failed: " + statusString(status));
+                        }
+                    });
+                    if (!statusOk.value) {
+                        continue; // still try next one?
+                    }
+
+                    WifiStatus status = chipResp.value.registerEventCallback(
+                            new IWifiChipEventCallback.Stub() {
+                                @Override
+                                public void onChipReconfigured(int modeId) throws RemoteException {
+                                    Log.d(TAG, "onChipReconfigured: modeId=" + modeId);
+                                }
+
+                                @Override
+                                public void onChipReconfigureFailure(WifiStatus status)
+                                        throws RemoteException {
+                                    Log.d(TAG, "onChipReconfigureFailure: status=" + statusString(
+                                            status));
+                                }
+
+                                @Override
+                                public void onIfaceAdded(int type, String name)
+                                        throws RemoteException {
+                                    Log.d(TAG, "onIfaceAdded: type=" + type + ", name=" + name);
+                                }
+
+                                @Override
+                                public void onIfaceRemoved(int type, String name)
+                                        throws RemoteException {
+                                    Log.d(TAG, "onIfaceRemoved: type=" + type + ", name=" + name);
+                                }
+
+                                @Override
+                                public void onDebugRingBufferDataAvailable(
+                                        WifiDebugRingBufferStatus status,
+                                        ArrayList<Byte> data) throws RemoteException {
+                                    Log.d(TAG, "onDebugRingBufferDataAvailable");
+                                }
+
+                                @Override
+                                public void onDebugErrorAlert(int errorCode,
+                                        ArrayList<Byte> debugData)
+                                        throws RemoteException {
+                                    Log.d(TAG, "onDebugErrorAlert");
+                                }
+                            });
+                    if (status.code != WifiStatusCode.SUCCESS) {
+                        Log.e(TAG, "registerEventCallback failed: " + statusString(status));
+                        continue; // still try next one?
+                    }
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "initIWifiChipDebugListeners: exception: " + e);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Get current information about all the chips in the system: modes, current mode (if any), and
+     * any existing interfaces.
+     *
+     * Intended to be called whenever we need to configure the chips - information is NOT cached (to
+     * reduce the likelihood that we get out-of-sync).
+     */
+    private WifiChipInfo[] getAllChipInfo() {
+        if (DBG) Log.d(TAG, "getAllChipInfo");
+
+        synchronized (mLock) {
+            if (mWifi == null) {
+                Log.e(TAG, "getAllChipInfo: called but mWifi is null!?");
+                return null;
+            }
+
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
+
+                // get all chip IDs
+                mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
+                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        chipIdsResp.value = chipIds;
+                    } else {
+                        Log.e(TAG, "getChipIds failed: " + statusString(status));
+                    }
+                });
+                if (!statusOk.value) {
+                    return null;
+                }
+
+                if (DBG) Log.d(TAG, "getChipIds=" + chipIdsResp.value);
+                if (chipIdsResp.value.size() == 0) {
+                    Log.e(TAG, "Should have at least 1 chip!");
+                    return null;
+                }
+
+                int chipInfoIndex = 0;
+                WifiChipInfo[] chipsInfo = new WifiChipInfo[chipIdsResp.value.size()];
+
+                Mutable<IWifiChip> chipResp = new Mutable<>();
+                for (Integer chipId: chipIdsResp.value) {
+                    mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
+                        statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                        if (statusOk.value) {
+                            chipResp.value = chip;
+                        } else {
+                            Log.e(TAG, "getChip failed: " + statusString(status));
+                        }
+                    });
+                    if (!statusOk.value) {
+                        return null;
+                    }
+
+                    Mutable<ArrayList<IWifiChip.ChipMode>> availableModesResp = new Mutable<>();
+                    chipResp.value.getAvailableModes(
+                            (WifiStatus status, ArrayList<IWifiChip.ChipMode> modes) -> {
+                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                if (statusOk.value) {
+                                    availableModesResp.value = modes;
+                                } else {
+                                    Log.e(TAG, "getAvailableModes failed: " + statusString(status));
+                                }
+                            });
+                    if (!statusOk.value) {
+                        return null;
+                    }
+
+                    MutableBoolean currentModeValidResp = new MutableBoolean(false);
+                    MutableInt currentModeResp = new MutableInt(0);
+                    chipResp.value.getMode((WifiStatus status, int modeId) -> {
+                        statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                        if (statusOk.value) {
+                            currentModeValidResp.value = true;
+                            currentModeResp.value = modeId;
+                        } else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
+                            statusOk.value = true; // valid response
+                        } else {
+                            Log.e(TAG, "getMode failed: " + statusString(status));
+                        }
+                    });
+                    if (!statusOk.value) {
+                        return null;
+                    }
+
+                    Mutable<ArrayList<String>> ifaceNamesResp = new Mutable<>();
+                    MutableInt ifaceIndex = new MutableInt(0);
+
+                    chipResp.value.getStaIfaceNames(
+                            (WifiStatus status, ArrayList<String> ifnames) -> {
+                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                if (statusOk.value) {
+                                    ifaceNamesResp.value = ifnames;
+                                } else {
+                                    Log.e(TAG, "getStaIfaceNames failed: " + statusString(status));
+                                }
+                            });
+                    if (!statusOk.value) {
+                        return null;
+                    }
+
+                    WifiIfaceInfo[] staIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
+                    for (String ifaceName: ifaceNamesResp.value) {
+                        chipResp.value.getStaIface(ifaceName,
+                                (WifiStatus status, IWifiStaIface iface) -> {
+                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                    if (statusOk.value) {
+                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
+                                        ifaceInfo.name = ifaceName;
+                                        ifaceInfo.iface = iface;
+                                        staIfaces[ifaceIndex.value++] = ifaceInfo;
+                                    } else {
+                                        Log.e(TAG, "getStaIface failed: " + statusString(status));
+                                    }
+                                });
+                        if (!statusOk.value) {
+                            return null;
+                        }
+                    }
+
+                    ifaceIndex.value = 0;
+                    chipResp.value.getApIfaceNames(
+                            (WifiStatus status, ArrayList<String> ifnames) -> {
+                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                if (statusOk.value) {
+                                    ifaceNamesResp.value = ifnames;
+                                } else {
+                                    Log.e(TAG, "getApIfaceNames failed: " + statusString(status));
+                                }
+                            });
+                    if (!statusOk.value) {
+                        return null;
+                    }
+
+                    WifiIfaceInfo[] apIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
+                    for (String ifaceName: ifaceNamesResp.value) {
+                        chipResp.value.getApIface(ifaceName,
+                                (WifiStatus status, IWifiApIface iface) -> {
+                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                    if (statusOk.value) {
+                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
+                                        ifaceInfo.name = ifaceName;
+                                        ifaceInfo.iface = iface;
+                                        apIfaces[ifaceIndex.value++] = ifaceInfo;
+                                    } else {
+                                        Log.e(TAG, "getApIface failed: " + statusString(status));
+                                    }
+                                });
+                        if (!statusOk.value) {
+                            return null;
+                        }
+                    }
+
+                    ifaceIndex.value = 0;
+                    chipResp.value.getP2pIfaceNames(
+                            (WifiStatus status, ArrayList<String> ifnames) -> {
+                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                if (statusOk.value) {
+                                    ifaceNamesResp.value = ifnames;
+                                } else {
+                                    Log.e(TAG, "getP2pIfaceNames failed: " + statusString(status));
+                                }
+                            });
+                    if (!statusOk.value) {
+                        return null;
+                    }
+
+                    WifiIfaceInfo[] p2pIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
+                    for (String ifaceName: ifaceNamesResp.value) {
+                        chipResp.value.getP2pIface(ifaceName,
+                                (WifiStatus status, IWifiP2pIface iface) -> {
+                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                    if (statusOk.value) {
+                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
+                                        ifaceInfo.name = ifaceName;
+                                        ifaceInfo.iface = iface;
+                                        p2pIfaces[ifaceIndex.value++] = ifaceInfo;
+                                    } else {
+                                        Log.e(TAG, "getP2pIface failed: " + statusString(status));
+                                    }
+                                });
+                        if (!statusOk.value) {
+                            return null;
+                        }
+                    }
+
+                    ifaceIndex.value = 0;
+                    chipResp.value.getNanIfaceNames(
+                            (WifiStatus status, ArrayList<String> ifnames) -> {
+                                statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                if (statusOk.value) {
+                                    ifaceNamesResp.value = ifnames;
+                                } else {
+                                    Log.e(TAG, "getNanIfaceNames failed: " + statusString(status));
+                                }
+                            });
+                    if (!statusOk.value) {
+                        return null;
+                    }
+
+                    WifiIfaceInfo[] nanIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
+                    for (String ifaceName: ifaceNamesResp.value) {
+                        chipResp.value.getNanIface(ifaceName,
+                                (WifiStatus status, IWifiNanIface iface) -> {
+                                    statusOk.value = status.code == WifiStatusCode.SUCCESS;
+                                    if (statusOk.value) {
+                                        WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
+                                        ifaceInfo.name = ifaceName;
+                                        ifaceInfo.iface = iface;
+                                        nanIfaces[ifaceIndex.value++] = ifaceInfo;
+                                    } else {
+                                        Log.e(TAG, "getNanIface failed: " + statusString(status));
+                                    }
+                                });
+                        if (!statusOk.value) {
+                            return null;
+                        }
+                    }
+
+                    WifiChipInfo chipInfo = new WifiChipInfo();
+                    chipsInfo[chipInfoIndex++] = chipInfo;
+
+                    chipInfo.chip = chipResp.value;
+                    chipInfo.chipId = chipId;
+                    chipInfo.availableModes = availableModesResp.value;
+                    chipInfo.currentModeIdValid = currentModeValidResp.value;
+                    chipInfo.currentModeId = currentModeResp.value;
+                    chipInfo.ifaces[IfaceType.STA] = staIfaces;
+                    chipInfo.ifaces[IfaceType.AP] = apIfaces;
+                    chipInfo.ifaces[IfaceType.P2P] = p2pIfaces;
+                    chipInfo.ifaces[IfaceType.NAN] = nanIfaces;
+                }
+
+                return chipsInfo;
+            } catch (RemoteException e) {
+                Log.e(TAG, "getAllChipInfoAndValidateCache exception: " + e);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Checks the local state of this object (the cached state) against the input 'chipInfos'
+     * state (which is a live representation of the Wi-Fi firmware status - read through the HAL).
+     * Returns 'true' if there are no discrepancies - 'false' otherwise.
+     *
+     * A discrepancy is if any local state contains references to a chip or interface which are not
+     * found on the information read from the chip.
+     */
+    private boolean validateInterfaceCache(WifiChipInfo[] chipInfos) {
+        if (DBG) Log.d(TAG, "validateInterfaceCache");
+
+        synchronized (mLock) {
+            for (Map.Entry<IWifiIface, InterfaceCacheEntry> entry: mInterfaceInfoCache.entrySet()) {
+                // search for chip
+                WifiChipInfo matchingChipInfo = null;
+                for (WifiChipInfo ci: chipInfos) {
+                    if (ci.chipId == entry.getValue().chipId) {
+                        matchingChipInfo = ci;
+                        break;
+                    }
+                }
+                if (matchingChipInfo == null) {
+                    Log.e(TAG, "validateInterfaceCache: no chip found for " + entry.getValue());
+                    return false;
+                }
+
+                // search for interface
+                WifiIfaceInfo[] ifaceInfoList = matchingChipInfo.ifaces[entry.getValue().type];
+                if (ifaceInfoList == null) {
+                    Log.e(TAG, "validateInterfaceCache: invalid type on entry " + entry.getValue());
+                    return false;
+                }
+
+                boolean matchFound = false;
+                for (WifiIfaceInfo ifaceInfo: ifaceInfoList) {
+                    if (ifaceInfo.name.equals(entry.getValue().name)) {
+                        matchFound = true;
+                        break;
+                    }
+                }
+                if (!matchFound) {
+                    Log.e(TAG, "validateInterfaceCache: no interface found for "
+                            + entry.getValue());
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    private boolean isWifiStarted() {
+        if (DBG) Log.d(TAG, "isWifiStart");
+
+        synchronized (mLock) {
+            try {
+                if (mWifi == null) {
+                    Log.w(TAG, "isWifiStarted called but mWifi is null!?");
+                    return false;
+                } else {
+                    return mWifi.isStarted();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "isWifiStarted exception: " + e);
+                return false;
+            }
+        }
+    }
+
+    private boolean startWifi() {
+        if (DBG) Log.d(TAG, "startWifi");
+
+        synchronized (mLock) {
+            try {
+                if (mWifi == null) {
+                    Log.w(TAG, "startWifi called but mWifi is null!?");
+                    return false;
+                } else {
+                    int triedCount = 0;
+                    while (triedCount <= START_HAL_RETRY_TIMES) {
+                        WifiStatus status = mWifi.start();
+                        if (status.code == WifiStatusCode.SUCCESS) {
+                            initIWifiChipDebugListeners();
+                            managerStatusListenerDispatch();
+                            if (triedCount != 0) {
+                                Log.d(TAG, "start IWifi succeeded after trying "
+                                         + triedCount + " times");
+                            }
+                            return true;
+                        } else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
+                            // Should retry. Hal might still be stopping.
+                            Log.e(TAG, "Cannot start IWifi: " + statusString(status)
+                                    + ", Retrying...");
+                            try {
+                                Thread.sleep(START_HAL_RETRY_INTERVAL_MS);
+                            } catch (InterruptedException ignore) {
+                                // no-op
+                            }
+                            triedCount++;
+                        } else {
+                            // Should not retry on other failures.
+                            Log.e(TAG, "Cannot start IWifi: " + statusString(status));
+                            return false;
+                        }
+                    }
+                    Log.e(TAG, "Cannot start IWifi after trying " + triedCount + " times");
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "startWifi exception: " + e);
+                return false;
+            }
+        }
+    }
+
+    private void stopWifi() {
+        if (DBG) Log.d(TAG, "stopWifi");
+
+        synchronized (mLock) {
+            try {
+                if (mWifi == null) {
+                    Log.w(TAG, "stopWifi called but mWifi is null!?");
+                } else {
+                    WifiStatus status = mWifi.stop();
+                    if (status.code != WifiStatusCode.SUCCESS) {
+                        Log.e(TAG, "Cannot stop IWifi: " + statusString(status));
+                    }
+
+                    // even on failure since WTF??
+                    teardownInternal();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "stopWifi exception: " + e);
+            }
+        }
+    }
+
+    private class WifiEventCallback extends IWifiEventCallback.Stub {
+        @Override
+        public void onStart() throws RemoteException {
+            if (DBG) Log.d(TAG, "IWifiEventCallback.onStart");
+            // NOP: only happens in reaction to my calls - will handle directly
+        }
+
+        @Override
+        public void onStop() throws RemoteException {
+            if (DBG) Log.d(TAG, "IWifiEventCallback.onStop");
+            // NOP: only happens in reaction to my calls - will handle directly
+        }
+
+        @Override
+        public void onFailure(WifiStatus status) throws RemoteException {
+            Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status));
+            teardownInternal();
+
+            // No need to do anything else: listeners may (will) re-start Wi-Fi
+        }
+    }
+
+    private void managerStatusListenerDispatch() {
+        synchronized (mLock) {
+            for (ManagerStatusListenerProxy cb : mManagerStatusListeners) {
+                cb.trigger();
+            }
+        }
+    }
+
+    private class ManagerStatusListenerProxy  extends
+            ListenerProxy<ManagerStatusListener> {
+        ManagerStatusListenerProxy(ManagerStatusListener statusListener,
+                Looper looper) {
+            super(statusListener, looper, "ManagerStatusListenerProxy");
+        }
+
+        @Override
+        protected void action() {
+            mListener.onStatusChanged();
+        }
+    }
+
+    Set<Integer> getSupportedIfaceTypesInternal(IWifiChip chip) {
+        Set<Integer> results = new HashSet<>();
+
+        WifiChipInfo[] chipInfos = getAllChipInfo();
+        if (chipInfos == null) {
+            Log.e(TAG, "getSupportedIfaceTypesInternal: no chip info found");
+            return results;
+        }
+
+        MutableInt chipIdIfProvided = new MutableInt(0); // NOT using 0 as a magic value
+        if (chip != null) {
+            MutableBoolean statusOk = new MutableBoolean(false);
+            try {
+                chip.getId((WifiStatus status, int id) -> {
+                    if (status.code == WifiStatusCode.SUCCESS) {
+                        chipIdIfProvided.value = id;
+                        statusOk.value = true;
+                    } else {
+                        Log.e(TAG, "getSupportedIfaceTypesInternal: IWifiChip.getId() error: "
+                                + statusString(status));
+                        statusOk.value = false;
+                    }
+                });
+            } catch (RemoteException e) {
+                Log.e(TAG, "getSupportedIfaceTypesInternal IWifiChip.getId() exception: " + e);
+                return results;
+            }
+            if (!statusOk.value) {
+                return results;
+            }
+        }
+
+        for (WifiChipInfo wci: chipInfos) {
+            if (chip != null && wci.chipId != chipIdIfProvided.value) {
+                continue;
+            }
+
+            for (IWifiChip.ChipMode cm: wci.availableModes) {
+                for (IWifiChip.ChipIfaceCombination cic: cm.availableCombinations) {
+                    for (IWifiChip.ChipIfaceCombinationLimit cicl: cic.limits) {
+                        for (int type: cicl.types) {
+                            results.add(type);
+                        }
+                    }
+                }
+            }
+        }
+
+        return results;
+    }
+
+    private IWifiIface createIface(int ifaceType, InterfaceDestroyedListener destroyedListener,
+            Looper looper) {
+        if (DBG) Log.d(TAG, "createIface: ifaceType=" + ifaceType);
+
+        synchronized (mLock) {
+            WifiChipInfo[] chipInfos = getAllChipInfo();
+            if (chipInfos == null) {
+                Log.e(TAG, "createIface: no chip info found");
+                stopWifi(); // major error: shutting down
+                return null;
+            }
+
+            if (!validateInterfaceCache(chipInfos)) {
+                Log.e(TAG, "createIface: local cache is invalid!");
+                stopWifi(); // major error: shutting down
+                return null;
+            }
+
+            IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, destroyedListener,
+                    looper);
+            if (iface != null) { // means that some configuration has changed
+                if (!dispatchAvailableForRequestListeners()) {
+                    return null; // catastrophic failure - shut down
+                }
+            }
+
+            return iface;
+        }
+    }
+
+    private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType,
+            InterfaceDestroyedListener destroyedListener, Looper looper) {
+        if (DBG) {
+            Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
+                    + ", ifaceType=" + ifaceType);
+        }
+        synchronized (mLock) {
+            IfaceCreationData bestIfaceCreationProposal = null;
+            for (WifiChipInfo chipInfo: chipInfos) {
+                for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
+                    for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
+                            .availableCombinations) {
+                        int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo);
+                        if (DBG) {
+                            Log.d(TAG, chipIfaceCombo + " expands to "
+                                    + Arrays.deepToString(expandedIfaceCombos));
+                        }
+
+                        for (int[] expandedIfaceCombo: expandedIfaceCombos) {
+                            IfaceCreationData currentProposal = canIfaceComboSupportRequest(
+                                    chipInfo, chipMode, expandedIfaceCombo, ifaceType);
+                            if (compareIfaceCreationData(currentProposal,
+                                    bestIfaceCreationProposal)) {
+                                if (DBG) Log.d(TAG, "new proposal accepted");
+                                bestIfaceCreationProposal = currentProposal;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (bestIfaceCreationProposal != null) {
+                IWifiIface iface = executeChipReconfiguration(bestIfaceCreationProposal, ifaceType);
+                if (iface != null) {
+                    InterfaceCacheEntry cacheEntry = new InterfaceCacheEntry();
+
+                    cacheEntry.chip = bestIfaceCreationProposal.chipInfo.chip;
+                    cacheEntry.chipId = bestIfaceCreationProposal.chipInfo.chipId;
+                    cacheEntry.name = getName(iface);
+                    cacheEntry.type = ifaceType;
+                    if (destroyedListener != null) {
+                        cacheEntry.destroyedListeners.add(
+                                new InterfaceDestroyedListenerProxy(destroyedListener,
+                                        looper == null ? Looper.myLooper() : looper));
+                    }
+
+                    mInterfaceInfoCache.put(iface, cacheEntry);
+                    return iface;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    // similar to createIfaceIfPossible - but simpler code: not looking for best option just
+    // for any option (so terminates on first one).
+    private boolean isItPossibleToCreateIface(WifiChipInfo[] chipInfos, int ifaceType) {
+        if (DBG) {
+            Log.d(TAG, "isItPossibleToCreateIface: chipInfos=" + Arrays.deepToString(chipInfos)
+                    + ", ifaceType=" + ifaceType);
+        }
+
+        for (WifiChipInfo chipInfo: chipInfos) {
+            for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
+                for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
+                        .availableCombinations) {
+                    int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo);
+                    if (DBG) {
+                        Log.d(TAG, chipIfaceCombo + " expands to "
+                                + Arrays.deepToString(expandedIfaceCombos));
+                    }
+
+                    for (int[] expandedIfaceCombo: expandedIfaceCombos) {
+                        if (canIfaceComboSupportRequest(chipInfo, chipMode, expandedIfaceCombo,
+                                ifaceType) != null) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Expands (or provides an alternative representation) of the ChipIfaceCombination as all
+     * possible combinations of interface.
+     *
+     * Returns [# of combinations][4 (IfaceType)]
+     *
+     * Note: there could be duplicates - allow (inefficient but ...).
+     * TODO: optimize by using a Set as opposed to a []: will remove duplicates. Will need to
+     * provide correct hashes.
+     */
+    private int[][] expandIfaceCombos(IWifiChip.ChipIfaceCombination chipIfaceCombo) {
+        int numOfCombos = 1;
+        for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) {
+            for (int i = 0; i < limit.maxIfaces; ++i) {
+                numOfCombos *= limit.types.size();
+            }
+        }
+
+        int[][] expandedIfaceCombos = new int[numOfCombos][IFACE_TYPES_BY_PRIORITY.length];
+
+        int span = numOfCombos; // span of an individual type (or sub-tree size)
+        for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) {
+            for (int i = 0; i < limit.maxIfaces; ++i) {
+                span /= limit.types.size();
+                for (int k = 0; k < numOfCombos; ++k) {
+                    expandedIfaceCombos[k][limit.types.get((k / span) % limit.types.size())]++;
+                }
+            }
+        }
+
+        return expandedIfaceCombos;
+    }
+
+    private class IfaceCreationData {
+        public WifiChipInfo chipInfo;
+        public int chipModeId;
+        public List<WifiIfaceInfo> interfacesToBeRemovedFirst;
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{chipInfo=").append(chipInfo).append(", chipModeId=").append(chipModeId)
+                    .append(", interfacesToBeRemovedFirst=").append(interfacesToBeRemovedFirst)
+                    .append(")");
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Checks whether the input chip-iface-combo can support the requested interface type: if not
+     * then returns null, if yes then returns information containing the list of interfaces which
+     * would have to be removed first before the requested interface can be created.
+     *
+     * Note: the list of interfaces to be removed is EMPTY if a chip mode change is required - in
+     * that case ALL the interfaces on the current chip have to be removed first.
+     *
+     * Response determined based on:
+     * - Mode configuration: i.e. could the mode support the interface type in principle
+     * - Priority information: i.e. are we 'allowed' to remove interfaces in order to create the
+     *   requested interface
+     */
+    private IfaceCreationData canIfaceComboSupportRequest(WifiChipInfo chipInfo,
+            IWifiChip.ChipMode chipMode, int[] chipIfaceCombo, int ifaceType) {
+        if (DBG) {
+            Log.d(TAG, "canIfaceComboSupportRequest: chipInfo=" + chipInfo + ", chipMode="
+                    + chipMode + ", chipIfaceCombo=" + chipIfaceCombo + ", ifaceType=" + ifaceType);
+        }
+
+        // short-circuit: does the chipIfaceCombo even support the requested type?
+        if (chipIfaceCombo[ifaceType] == 0) {
+            if (DBG) Log.d(TAG, "Requested type not supported by combo");
+            return null;
+        }
+
+        boolean isChipModeChangeProposed =
+                chipInfo.currentModeIdValid && chipInfo.currentModeId != chipMode.id;
+
+        // short-circuit: can't change chip-mode if an existing interface on this chip has a higher
+        // priority than the requested interface
+        if (isChipModeChangeProposed) {
+            for (int type: IFACE_TYPES_BY_PRIORITY) {
+                if (chipInfo.ifaces[type].length != 0) {
+                    if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+                        if (DBG) {
+                            Log.d(TAG, "Couldn't delete existing type " + type
+                                    + " interfaces for requested type");
+                        }
+                        return null;
+                    }
+                }
+            }
+
+            // but if priority allows the mode change then we're good to go
+            IfaceCreationData ifaceCreationData = new IfaceCreationData();
+            ifaceCreationData.chipInfo = chipInfo;
+            ifaceCreationData.chipModeId = chipMode.id;
+
+            return ifaceCreationData;
+        }
+
+        // possibly supported
+        List<WifiIfaceInfo> interfacesToBeRemovedFirst = new ArrayList<>();
+
+        for (int type: IFACE_TYPES_BY_PRIORITY) {
+            int tooManyInterfaces = chipInfo.ifaces[type].length - chipIfaceCombo[type];
+
+            // need to count the requested interface as well
+            if (type == ifaceType) {
+                tooManyInterfaces += 1;
+            }
+
+            if (tooManyInterfaces > 0) { // may need to delete some
+                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+                    if (DBG) {
+                        Log.d(TAG, "Would need to delete some higher priority interfaces");
+                    }
+                    return null;
+                }
+
+                // arbitrarily pick the first interfaces to delete
+                for (int i = 0; i < tooManyInterfaces; ++i) {
+                    interfacesToBeRemovedFirst.add(chipInfo.ifaces[type][i]);
+                }
+            }
+        }
+
+        IfaceCreationData ifaceCreationData = new IfaceCreationData();
+        ifaceCreationData.chipInfo = chipInfo;
+        ifaceCreationData.chipModeId = chipMode.id;
+        ifaceCreationData.interfacesToBeRemovedFirst = interfacesToBeRemovedFirst;
+
+        return ifaceCreationData;
+    }
+
+    /**
+     * Compares two options to create an interface and determines which is the 'best'. Returns
+     * true if proposal 1 (val1) is better, other false.
+     *
+     * Note: both proposals are 'acceptable' bases on priority criteria.
+     *
+     * Criteria:
+     * - Proposal is better if it means removing fewer high priority interfaces
+     */
+    private boolean compareIfaceCreationData(IfaceCreationData val1, IfaceCreationData val2) {
+        if (DBG) Log.d(TAG, "compareIfaceCreationData: val1=" + val1 + ", val2=" + val2);
+
+        // deal with trivial case of one or the other being null
+        if (val1 == null) {
+            return false;
+        } else if (val2 == null) {
+            return true;
+        }
+
+        for (int type: IFACE_TYPES_BY_PRIORITY) {
+            // # of interfaces to be deleted: the list or all interfaces of the type if mode change
+            int numIfacesToDelete1 = 0;
+            if (val1.chipInfo.currentModeIdValid
+                    && val1.chipInfo.currentModeId != val1.chipModeId) {
+                numIfacesToDelete1 = val1.chipInfo.ifaces[type].length;
+            } else {
+                numIfacesToDelete1 = val1.interfacesToBeRemovedFirst.size();
+            }
+
+            int numIfacesToDelete2 = 0;
+            if (val2.chipInfo.currentModeIdValid
+                    && val2.chipInfo.currentModeId != val2.chipModeId) {
+                numIfacesToDelete2 = val2.chipInfo.ifaces[type].length;
+            } else {
+                numIfacesToDelete2 = val2.interfacesToBeRemovedFirst.size();
+            }
+
+            if (numIfacesToDelete1 < numIfacesToDelete2) {
+                if (DBG) {
+                    Log.d(TAG, "decision based on type=" + type + ": " + numIfacesToDelete1
+                            + " < " + numIfacesToDelete2);
+                }
+                return true;
+            }
+        }
+
+        // arbitrary - flip a coin
+        if (DBG) Log.d(TAG, "proposals identical - flip a coin");
+        return false;
+    }
+
+    /**
+     * Returns true if we're allowed to delete the existing interface type for the requested
+     * interface type.
+     *
+     * Rules:
+     * 1. Request for AP or STA will destroy any other interface (except see #4)
+     * 2. Request for P2P will destroy NAN-only
+     * 3. Request for NAN will not destroy any interface
+     * --
+     * 4. No interface will be destroyed for a requested interface of the same type
+     */
+    private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
+            int requestedIfaceType) {
+        // rule 4
+        if (existingIfaceType == requestedIfaceType) {
+            return false;
+        }
+
+        // rule 3
+        if (requestedIfaceType == IfaceType.NAN) {
+            return false;
+        }
+
+        // rule 2
+        if (requestedIfaceType == IfaceType.P2P) {
+            return existingIfaceType == IfaceType.NAN;
+        }
+
+        // rule 1, the requestIfaceType is either AP or STA
+        return true;
+    }
+
+    /**
+     * Performs chip reconfiguration per the input:
+     * - Removes the specified interfaces
+     * - Reconfigures the chip to the new chip mode (if necessary)
+     * - Creates the new interface
+     *
+     * Returns the newly created interface or a null on any error.
+     */
+    private IWifiIface executeChipReconfiguration(IfaceCreationData ifaceCreationData,
+            int ifaceType) {
+        if (DBG) {
+            Log.d(TAG, "executeChipReconfiguration: ifaceCreationData=" + ifaceCreationData
+                    + ", ifaceType=" + ifaceType);
+        }
+        synchronized (mLock) {
+            try {
+                // is this a mode change?
+                boolean isModeConfigNeeded = !ifaceCreationData.chipInfo.currentModeIdValid
+                        || ifaceCreationData.chipInfo.currentModeId != ifaceCreationData.chipModeId;
+                if (DBG) Log.d(TAG, "isModeConfigNeeded=" + isModeConfigNeeded);
+
+                // first delete interfaces/change modes
+                if (isModeConfigNeeded) {
+                    // remove all interfaces pre mode-change
+                    // TODO: is this necessary? note that even if we don't want to explicitly
+                    // remove the interfaces we do need to call the onDeleted callbacks - which
+                    // this does
+                    for (WifiIfaceInfo[] ifaceInfos: ifaceCreationData.chipInfo.ifaces) {
+                        for (WifiIfaceInfo ifaceInfo: ifaceInfos) {
+                            removeIfaceInternal(ifaceInfo.iface); // ignore return value
+                        }
+                    }
+
+                    WifiStatus status = ifaceCreationData.chipInfo.chip.configureChip(
+                            ifaceCreationData.chipModeId);
+                    if (status.code != WifiStatusCode.SUCCESS) {
+                        Log.e(TAG, "executeChipReconfiguration: configureChip error: "
+                                + statusString(status));
+                        return null;
+                    }
+                } else {
+                    // remove all interfaces on the delete list
+                    for (WifiIfaceInfo ifaceInfo: ifaceCreationData.interfacesToBeRemovedFirst) {
+                        removeIfaceInternal(ifaceInfo.iface); // ignore return value
+                    }
+                }
+
+                // create new interface
+                Mutable<WifiStatus> statusResp = new Mutable<>();
+                Mutable<IWifiIface> ifaceResp = new Mutable<>();
+                switch (ifaceType) {
+                    case IfaceType.STA:
+                        ifaceCreationData.chipInfo.chip.createStaIface(
+                                (WifiStatus status, IWifiStaIface iface) -> {
+                                    statusResp.value = status;
+                                    ifaceResp.value = iface;
+                                });
+                        break;
+                    case IfaceType.AP:
+                        ifaceCreationData.chipInfo.chip.createApIface(
+                                (WifiStatus status, IWifiApIface iface) -> {
+                                    statusResp.value = status;
+                                    ifaceResp.value = iface;
+                                });
+                        break;
+                    case IfaceType.P2P:
+                        ifaceCreationData.chipInfo.chip.createP2pIface(
+                                (WifiStatus status, IWifiP2pIface iface) -> {
+                                    statusResp.value = status;
+                                    ifaceResp.value = iface;
+                                });
+                        break;
+                    case IfaceType.NAN:
+                        ifaceCreationData.chipInfo.chip.createNanIface(
+                                (WifiStatus status, IWifiNanIface iface) -> {
+                                    statusResp.value = status;
+                                    ifaceResp.value = iface;
+                                });
+                        break;
+                }
+
+                if (statusResp.value.code != WifiStatusCode.SUCCESS) {
+                    Log.e(TAG, "executeChipReconfiguration: failed to create interface ifaceType="
+                            + ifaceType + ": " + statusString(statusResp.value));
+                    return null;
+                }
+
+                return ifaceResp.value;
+            } catch (RemoteException e) {
+                Log.e(TAG, "executeChipReconfiguration exception: " + e);
+                return null;
+            }
+        }
+    }
+
+    private boolean removeIfaceInternal(IWifiIface iface) {
+        if (DBG) Log.d(TAG, "removeIfaceInternal: iface(name)=" + getName(iface));
+
+        synchronized (mLock) {
+            if (mWifi == null) {
+                Log.e(TAG, "removeIfaceInternal: null IWifi -- iface(name)=" + getName(iface));
+                return false;
+            }
+
+            IWifiChip chip = getChip(iface);
+            if (chip == null) {
+                Log.e(TAG, "removeIfaceInternal: null IWifiChip -- iface(name)=" + getName(iface));
+                return false;
+            }
+
+            String name = getName(iface);
+            if (name == null) {
+                Log.e(TAG, "removeIfaceInternal: can't get name");
+                return false;
+            }
+
+            int type = getType(iface);
+            if (type == -1) {
+                Log.e(TAG, "removeIfaceInternal: can't get type -- iface(name)=" + getName(iface));
+                return false;
+            }
+
+            WifiStatus status = null;
+            try {
+                switch (type) {
+                    case IfaceType.STA:
+                        status = chip.removeStaIface(name);
+                        break;
+                    case IfaceType.AP:
+                        status = chip.removeApIface(name);
+                        break;
+                    case IfaceType.P2P:
+                        status = chip.removeP2pIface(name);
+                        break;
+                    case IfaceType.NAN:
+                        status = chip.removeNanIface(name);
+                        break;
+                    default:
+                        Log.wtf(TAG, "removeIfaceInternal: invalid type=" + type);
+                        return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "IWifiChip.removeXxxIface exception: " + e);
+            }
+
+            // dispatch listeners no matter what status
+            dispatchDestroyedListeners(iface);
+
+            if (status != null && status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "IWifiChip.removeXxxIface failed: " + statusString(status));
+                return false;
+            }
+        }
+    }
+
+    // dispatch all available for request listeners of the specified type AND clean-out the list:
+    // listeners are called once at most!
+    private boolean dispatchAvailableForRequestListeners() {
+        if (DBG) Log.d(TAG, "dispatchAvailableForRequestListeners");
+
+        synchronized (mLock) {
+            WifiChipInfo[] chipInfos = getAllChipInfo();
+            if (chipInfos == null) {
+                Log.e(TAG, "dispatchAvailableForRequestListeners: no chip info found");
+                stopWifi(); // major error: shutting down
+                return false;
+            }
+            if (DBG) {
+                Log.d(TAG, "dispatchAvailableForRequestListeners: chipInfos="
+                        + Arrays.deepToString(chipInfos));
+            }
+
+            for (int ifaceType : IFACE_TYPES_BY_PRIORITY) {
+                dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
+            }
+        }
+
+        return true;
+    }
+
+    private void dispatchAvailableForRequestListenersForType(int ifaceType,
+            WifiChipInfo[] chipInfos) {
+        if (DBG) Log.d(TAG, "dispatchAvailableForRequestListenersForType: ifaceType=" + ifaceType);
+
+        Set<InterfaceAvailableForRequestListenerProxy> listeners =
+                mInterfaceAvailableForRequestListeners.get(ifaceType);
+
+        if (listeners.size() == 0) {
+            return;
+        }
+
+        if (!isItPossibleToCreateIface(chipInfos, ifaceType)) {
+            if (DBG) Log.d(TAG, "Creating interface type isn't possible: ifaceType=" + ifaceType);
+            return;
+        }
+
+        if (DBG) Log.d(TAG, "It is possible to create the interface type: ifaceType=" + ifaceType);
+        for (InterfaceAvailableForRequestListenerProxy listener : listeners) {
+            listener.trigger();
+        }
+    }
+
+    // dispatch all destroyed listeners registered for the specified interface AND remove the
+    // cache entry
+    private void dispatchDestroyedListeners(IWifiIface iface) {
+        if (DBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + getName(iface));
+
+        synchronized (mLock) {
+            InterfaceCacheEntry entry = mInterfaceInfoCache.get(iface);
+            if (entry == null) {
+                Log.e(TAG, "dispatchDestroyedListeners: no cache entry for iface(name)="
+                        + getName(iface));
+                return;
+            }
+
+            for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) {
+                listener.trigger();
+            }
+            entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
+            mInterfaceInfoCache.remove(iface);
+        }
+    }
+
+    // dispatch all destroyed listeners registered to all interfaces
+    private void dispatchAllDestroyedListeners() {
+        if (DBG) Log.d(TAG, "dispatchAllDestroyedListeners");
+
+        synchronized (mLock) {
+            Iterator<Map.Entry<IWifiIface, InterfaceCacheEntry>> it =
+                    mInterfaceInfoCache.entrySet().iterator();
+            while (it.hasNext()) {
+                InterfaceCacheEntry entry = it.next().getValue();
+                for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) {
+                    listener.trigger();
+                }
+                entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
+                it.remove();
+            }
+        }
+    }
+
+    private abstract class ListenerProxy<LISTENER>  {
+        private static final int LISTENER_TRIGGERED = 0;
+
+        protected LISTENER mListener;
+        private Handler mHandler;
+
+        // override equals & hash to make sure that the container HashSet is unique with respect to
+        // the contained listener
+        @Override
+        public boolean equals(Object obj) {
+            return mListener == ((ListenerProxy<LISTENER>) obj).mListener;
+        }
+
+        @Override
+        public int hashCode() {
+            return mListener.hashCode();
+        }
+
+        void trigger() {
+            mHandler.sendMessage(mHandler.obtainMessage(LISTENER_TRIGGERED));
+        }
+
+        protected abstract void action();
+
+        ListenerProxy(LISTENER listener, Looper looper, String tag) {
+            mListener = listener;
+            mHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    if (DBG) {
+                        Log.d(tag, "ListenerProxy.handleMessage: what=" + msg.what);
+                    }
+                    switch (msg.what) {
+                        case LISTENER_TRIGGERED:
+                            action();
+                            break;
+                        default:
+                            Log.e(tag, "ListenerProxy.handleMessage: unknown message what="
+                                    + msg.what);
+                    }
+                }
+            };
+        }
+    }
+
+    private class InterfaceDestroyedListenerProxy extends
+            ListenerProxy<InterfaceDestroyedListener> {
+        InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
+                Looper looper) {
+            super(destroyedListener, looper, "InterfaceDestroyedListenerProxy");
+        }
+
+        @Override
+        protected void action() {
+            mListener.onDestroyed();
+        }
+    }
+
+    private class InterfaceAvailableForRequestListenerProxy extends
+            ListenerProxy<InterfaceAvailableForRequestListener> {
+        InterfaceAvailableForRequestListenerProxy(
+                InterfaceAvailableForRequestListener destroyedListener, Looper looper) {
+            super(destroyedListener, looper, "InterfaceAvailableForRequestListenerProxy");
+        }
+
+        @Override
+        protected void action() {
+            mListener.onAvailableForRequest();
+        }
+    }
+
+    // general utilities
+
+    private static String statusString(WifiStatus status) {
+        if (status == null) {
+            return "status=null";
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append(status.code).append(" (").append(status.description).append(")");
+        return sb.toString();
+    }
+
+    // Will return -1 for invalid results! Otherwise will return one of the 4 valid values.
+    private static int getType(IWifiIface iface) {
+        MutableInt typeResp = new MutableInt(-1);
+        try {
+            iface.getType((WifiStatus status, int type) -> {
+                if (status.code == WifiStatusCode.SUCCESS) {
+                    typeResp.value = type;
+                } else {
+                    Log.e(TAG, "Error on getType: " + statusString(status));
+                }
+            });
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception on getType: " + e);
+        }
+
+        return typeResp.value;
+    }
+
+    private static class Mutable<E> {
+        public E value;
+
+        Mutable() {
+            value = null;
+        }
+
+        Mutable(E value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("HalDeviceManager:");
+        pw.println("  mServiceManager: " + mServiceManager);
+        pw.println("  mWifi: " + mWifi);
+        pw.println("  mManagerStatusListeners: " + mManagerStatusListeners);
+        pw.println("  mInterfaceAvailableForRequestListeners: "
+                + mInterfaceAvailableForRequestListeners);
+        pw.println("  mInterfaceInfoCache: " + mInterfaceInfoCache);
+    }
+}
diff --git a/service/java/com/android/server/wifi/IMSIParameter.java b/service/java/com/android/server/wifi/IMSIParameter.java
index deea870..ab9ec0a 100644
--- a/service/java/com/android/server/wifi/IMSIParameter.java
+++ b/service/java/com/android/server/wifi/IMSIParameter.java
@@ -1,8 +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.
+ */
+
 package com.android.server.wifi;
 
-import java.io.IOException;
+import android.text.TextUtils;
 
+/**
+ * Class for storing an IMSI (International Mobile Subscriber Identity) parameter.  The IMSI
+ * contains number (up to 15) of numerical digits.  When an IMSI ends with a '*', the specified
+ * IMSI is a prefix.
+ */
 public class IMSIParameter {
+    private static final int MAX_IMSI_LENGTH = 15;
+
+    /**
+     * MCC (Mobile Country Code) is a 3 digit number and MNC (Mobile Network Code) is also a 3
+     * digit number.
+     */
+    private static final int MCC_MNC_LENGTH = 6;
+
     private final String mImsi;
     private final boolean mPrefix;
 
@@ -11,11 +40,22 @@
         mPrefix = prefix;
     }
 
-    public IMSIParameter(String imsi) throws IOException {
-        if (imsi == null || imsi.length() == 0) {
-            throw new IOException("Bad IMSI: '" + imsi + "'");
+    /**
+     * Build an IMSIParameter object from the given string.  A null will be returned for a
+     * malformed string.
+     *
+     * @param imsi The IMSI string
+     * @return {@link IMSIParameter}
+     */
+    public static IMSIParameter build(String imsi) {
+        if (TextUtils.isEmpty(imsi)) {
+            return null;
+        }
+        if (imsi.length() > MAX_IMSI_LENGTH) {
+            return null;
         }
 
+        // Detect the first non-digit character.
         int nonDigit;
         char stopChar = '\0';
         for (nonDigit = 0; nonDigit < imsi.length(); nonDigit++) {
@@ -26,48 +66,55 @@
         }
 
         if (nonDigit == imsi.length()) {
-            mImsi = imsi;
-            mPrefix = false;
+            // Full IMSI.
+            return new IMSIParameter(imsi, false);
         }
         else if (nonDigit == imsi.length()-1 && stopChar == '*') {
-            mImsi = imsi.substring(0, nonDigit);
-            mPrefix = true;
+            // IMSI prefix.
+            return new IMSIParameter(imsi.substring(0, nonDigit), true);
         }
-        else {
-            throw new IOException("Bad IMSI: '" + imsi + "'");
-        }
+        return null;
     }
 
-    public boolean matches(String fullIMSI) {
-        if (mPrefix) {
-            return mImsi.regionMatches(false, 0, fullIMSI, 0, mImsi.length());
+    /**
+     * Perform matching against the given full IMSI.
+     *
+     * @param fullIMSI The full IMSI to match against
+     * @return true if matched
+     */
+    public boolean matchesImsi(String fullIMSI) {
+        if (fullIMSI == null) {
+            return false;
         }
-        else {
+
+        if (mPrefix) {
+            // Prefix matching.
+            return mImsi.regionMatches(false, 0, fullIMSI, 0, mImsi.length());
+        } else {
+            // Exact matching.
             return mImsi.equals(fullIMSI);
         }
     }
 
+    /**
+     * Perform matching against the given MCC-MNC (Mobile Country Code and Mobile Network
+     * Code) combination.
+     *
+     * @param mccMnc The MCC-MNC to match against
+     * @return true if matched
+     */
     public boolean matchesMccMnc(String mccMnc) {
-        if (mPrefix) {
-            // For a prefix match, the entire prefix must match the mcc+mnc
-            return mImsi.regionMatches(false, 0, mccMnc, 0, mImsi.length());
+        if (mccMnc == null) {
+            return false;
         }
-        else {
-            // For regular match, the entire length of mcc+mnc must match this IMSI
-            return mImsi.regionMatches(false, 0, mccMnc, 0, mccMnc.length());
+        if (mccMnc.length() != MCC_MNC_LENGTH) {
+            return false;
         }
-    }
-
-    public boolean isPrefix() {
-        return mPrefix;
-    }
-
-    public String getImsi() {
-        return mImsi;
-    }
-
-    public int prefixLength() {
-        return mImsi.length();
+        int checkLength = MCC_MNC_LENGTH;
+        if (mPrefix && mImsi.length() < MCC_MNC_LENGTH) {
+            checkLength = mImsi.length();
+        }
+        return mImsi.regionMatches(false, 0, mccMnc, 0, checkLength);
     }
 
     @Override
@@ -75,12 +122,12 @@
         if (this == thatObject) {
             return true;
         }
-        else if (thatObject == null || getClass() != thatObject.getClass()) {
+        if (!(thatObject instanceof IMSIParameter)) {
             return false;
         }
 
         IMSIParameter that = (IMSIParameter) thatObject;
-        return mPrefix == that.mPrefix && mImsi.equals(that.mImsi);
+        return mPrefix == that.mPrefix && TextUtils.equals(mImsi, that.mImsi);
     }
 
     @Override
diff --git a/service/java/com/android/server/wifi/LastMileLogger.java b/service/java/com/android/server/wifi/LastMileLogger.java
new file mode 100644
index 0000000..0fb810c
--- /dev/null
+++ b/service/java/com/android/server/wifi/LastMileLogger.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.os.FileUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.io.IoUtils;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Provides a facility for capturing kernel trace events related to Wifi control and data paths.
+ */
+public class LastMileLogger {
+    public LastMileLogger(WifiInjector injector) {
+        this(injector, WIFI_EVENT_BUFFER_PATH, WIFI_EVENT_ENABLE_PATH, WIFI_EVENT_RELEASE_PATH);
+    }
+
+    @VisibleForTesting
+    public LastMileLogger(WifiInjector injector, String bufferPath, String enablePath,
+                          String releasePath) {
+        mLog = injector.makeLog(TAG);
+        mEventBufferPath = bufferPath;
+        mEventEnablePath = enablePath;
+        mEventReleasePath = releasePath;
+    }
+
+    /**
+     * Informs LastMileLogger that a connection event has occurred.
+     * @param connectionId A non-negative connection identifier, or -1 to indicate unknown
+     * @param event an event defined in BaseWifiDiagnostics
+     */
+    public void reportConnectionEvent(long connectionId, byte event) {
+        if (connectionId < 0) {
+            mLog.warn("Ignoring negative connection id: %").c(connectionId);
+            return;
+        }
+
+        switch (event) {
+            case BaseWifiDiagnostics.CONNECTION_EVENT_STARTED:
+                mPendingConnectionId = connectionId;
+                enableTracing();
+                return;
+            case BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED:
+                mPendingConnectionId = -1;
+                disableTracing();
+                return;
+            case BaseWifiDiagnostics.CONNECTION_EVENT_FAILED:
+                if (connectionId >= mPendingConnectionId) {
+                    mPendingConnectionId = -1;
+                    disableTracing();
+                    mLastMileLogForLastFailure = readTrace();
+                    return;
+                } else {
+                    // Spurious failure message. Here's one scenario where this might happen:
+                    // t=00sec      start first connection attempt
+                    // t=30sec      start second connection attempt
+                    // t=60sec      timeout first connection attempt
+                    // We should not stop tracing in this case, since the second connection attempt
+                    // is still in progress.
+                    return;
+                }
+        }
+    }
+
+    /**
+     * Dumps the contents of the log.
+     * @param pw the PrintWriter that will receive the dump
+     */
+    public void dump(PrintWriter pw) {
+        dumpInternal(pw, "Last failed last-mile log", mLastMileLogForLastFailure);
+        dumpInternal(pw, "Latest last-mile log", readTrace());
+        mLastMileLogForLastFailure = null;
+    }
+
+    private static final String TAG = "LastMileLogger";
+    private static final String WIFI_EVENT_BUFFER_PATH =
+            "/sys/kernel/debug/tracing/instances/wifi/trace";
+    private static final String WIFI_EVENT_ENABLE_PATH =
+            "/sys/kernel/debug/tracing/instances/wifi/tracing_on";
+    private static final String WIFI_EVENT_RELEASE_PATH =
+            "/sys/kernel/debug/tracing/instances/wifi/free_buffer";
+
+    private final String mEventBufferPath;
+    private final String mEventEnablePath;
+    private final String mEventReleasePath;
+    private WifiLog mLog;
+    private byte[] mLastMileLogForLastFailure;
+    private FileInputStream mLastMileTraceHandle;
+    private long mPendingConnectionId = -1;
+
+    private void enableTracing() {
+        if (!ensureFailSafeIsArmed()) {
+            mLog.wC("Failed to arm fail-safe.");
+            return;
+        }
+
+        try {
+            FileUtils.stringToFile(mEventEnablePath, "1");
+        } catch (IOException e) {
+            mLog.warn("Failed to start event tracing: %").r(e.getMessage()).flush();
+        }
+    }
+
+    private void disableTracing() {
+        try {
+            FileUtils.stringToFile(mEventEnablePath, "0");
+        } catch (IOException e) {
+            mLog.warn("Failed to stop event tracing: %").r(e.getMessage()).flush();
+        }
+    }
+
+    private byte[] readTrace() {
+        try {
+            return IoUtils.readFileAsByteArray(mEventBufferPath);
+        } catch (IOException e) {
+            mLog.warn("Failed to read event trace: %").r(e.getMessage()).flush();
+            return new byte[0];
+        }
+    }
+
+    private boolean ensureFailSafeIsArmed() {
+        if (mLastMileTraceHandle != null) {
+            return true;
+        }
+
+        try {
+            // This file provides fail-safe behavior for Last-Mile logging. Given that we:
+            // 1. Set the disable_on_free option in the trace_options pseudo-file
+            //    (see wifi-events.rc), and
+            // 2. Hold the WIFI_EVENT_RELEASE_PATH open,
+            //
+            // Then, when this process dies, the kernel will automatically disable any
+            // tracing in the wifi trace instance.
+            //
+            // Note that, despite Studio's suggestion that |mLastMileTraceHandle| could be demoted
+            // to a local variable, we need to stick with a field. Otherwise, the handle could be
+            // garbage collected.
+            mLastMileTraceHandle = new FileInputStream(mEventReleasePath);
+            return true;
+        } catch (IOException e) {
+            mLog.warn("Failed to open free_buffer pseudo-file: %").r(e.getMessage()).flush();
+            return false;
+        }
+    }
+
+    private static void dumpInternal(PrintWriter pw, String description, byte[] lastMileLog) {
+        if (lastMileLog == null || lastMileLog.length < 1) {
+            pw.format("No last mile log for \"%s\"\n", description);
+            return;
+        }
+
+        pw.format("-------------------------- %s ---------------------------\n", description);
+        pw.print(new String(lastMileLog));
+        pw.println("--------------------------------------------------------------------");
+    }
+}
diff --git a/service/java/com/android/server/wifi/LocalOnlyHotspotRequestInfo.java b/service/java/com/android/server/wifi/LocalOnlyHotspotRequestInfo.java
new file mode 100644
index 0000000..9e3b4fb
--- /dev/null
+++ b/service/java/com/android/server/wifi/LocalOnlyHotspotRequestInfo.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Tracks information about applications requesting use of the LocalOnlyHotspot.
+ *
+ * @hide
+ */
+public class LocalOnlyHotspotRequestInfo implements IBinder.DeathRecipient {
+    static final int HOTSPOT_NO_ERROR = -1;
+
+    private final int mPid;
+    private final IBinder mBinder;
+    private final RequestingApplicationDeathCallback mCallback;
+    private final Messenger mMessenger;
+
+    /**
+     * Callback for use with LocalOnlyHotspot to unregister requesting applications upon death.
+     */
+    public interface RequestingApplicationDeathCallback {
+        /**
+         * Called when requesting app has died.
+         */
+        void onLocalOnlyHotspotRequestorDeath(LocalOnlyHotspotRequestInfo requestor);
+    }
+
+    LocalOnlyHotspotRequestInfo(@NonNull IBinder binder, @NonNull Messenger messenger,
+            @NonNull RequestingApplicationDeathCallback callback) {
+        mPid = Binder.getCallingPid();
+        mBinder = Preconditions.checkNotNull(binder);
+        mMessenger = Preconditions.checkNotNull(messenger);
+        mCallback = Preconditions.checkNotNull(callback);
+
+        try {
+            mBinder.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            binderDied();
+        }
+    }
+
+    /**
+     * Allow caller to unlink this object from binder death.
+     */
+    public void unlinkDeathRecipient() {
+        mBinder.unlinkToDeath(this, 0);
+    }
+
+    /**
+     * Application requesting LocalOnlyHotspot died
+     */
+    @Override
+    public void binderDied() {
+        mCallback.onLocalOnlyHotspotRequestorDeath(this);
+    }
+
+    /**
+     * Send a HOTSPOT_FAILED message to WifiManager for the calling application with the error code.
+     *
+     * @param reasonCode error code for the message
+     *
+     * @throws RemoteException
+     */
+    public void sendHotspotFailedMessage(int reasonCode) throws RemoteException {
+        Message message = Message.obtain();
+        message.what = WifiManager.HOTSPOT_FAILED;
+        message.arg1 = reasonCode;
+        mMessenger.send(message);
+    }
+
+    /**
+     * Send a HOTSPOT_STARTED message to WifiManager for the calling application with the config.
+     *
+     * @param config WifiConfiguration for the callback
+     *
+     * @throws RemoteException
+     */
+    public void sendHotspotStartedMessage(WifiConfiguration config) throws RemoteException {
+        Message message = Message.obtain();
+        message.what = WifiManager.HOTSPOT_STARTED;
+        message.obj = config;
+        mMessenger.send(message);
+    }
+
+    /**
+     * Send a HOTSPOT_STOPPED message to WifiManager for the calling application.
+     *
+     * @throws RemoteException
+     */
+    public void sendHotspotStoppedMessage() throws RemoteException {
+        Message message = Message.obtain();
+        message.what = WifiManager.HOTSPOT_STOPPED;
+        mMessenger.send(message);
+    }
+
+    public int getPid() {
+        return mPid;
+    }
+}
diff --git a/service/java/com/android/server/wifi/LogcatLog.java b/service/java/com/android/server/wifi/LogcatLog.java
new file mode 100644
index 0000000..ffe8950
--- /dev/null
+++ b/service/java/com/android/server/wifi/LogcatLog.java
@@ -0,0 +1,219 @@
+/*
+ * 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.util.Log;
+
+import com.android.internal.annotations.Immutable;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Provides a WifiLog implementation which uses logd as the
+ * logging backend.
+ *
+ * This class is trivially thread-safe, as instances are immutable.
+ * Note, however, that LogMessage instances are _not_ thread-safe.
+ */
+@ThreadSafe
+@Immutable
+class LogcatLog implements WifiLog {
+    private final String mTag;
+    private static volatile boolean sVerboseLogging = false;
+
+    LogcatLog(String tag) {
+        mTag = tag;
+    }
+
+    public static void enableVerboseLogging(int verboseMode) {
+        if (verboseMode > 0) {
+            sVerboseLogging = true;
+        } else {
+            sVerboseLogging = false;
+        }
+    }
+
+    /* New-style methods */
+    @Override
+    public LogMessage err(String format) {
+        return makeLogMessage(Log.ERROR, format);
+    }
+
+    @Override
+    public LogMessage warn(String format) {
+        return makeLogMessage(Log.WARN, format);
+    }
+
+    @Override
+    public LogMessage info(String format) {
+        return makeLogMessage(Log.INFO, format);
+    }
+
+    @Override
+    public LogMessage trace(String format) {
+        return makeLogMessage(Log.DEBUG, format);
+    }
+
+    @Override
+    public LogMessage dump(String format) {
+        return makeLogMessage(Log.VERBOSE, format);
+    }
+
+    @Override
+    public void eC(String msg) {
+        Log.e(mTag, msg);
+    }
+
+    @Override
+    public void wC(String msg) {
+        Log.w(mTag, msg);
+    }
+
+    @Override
+    public void iC(String msg) {
+        Log.i(mTag, msg);
+    }
+
+    @Override
+    public void tC(String msg) {
+        Log.d(mTag, msg);
+    }
+
+    /* Legacy methods */
+    @Override
+    public void e(String msg) {
+        Log.e(mTag, msg);
+    }
+
+    @Override
+    public void w(String msg) {
+        Log.w(mTag, msg);
+    }
+
+    @Override
+    public void i(String msg) {
+        Log.i(mTag, msg);
+    }
+
+    @Override
+    public void d(String msg) {
+        Log.d(mTag, msg);
+    }
+
+    @Override
+    public void v(String msg) {
+        Log.v(mTag, msg);
+    }
+
+    /* Internal details */
+    private static class RealLogMessage implements WifiLog.LogMessage {
+        private final int mLogLevel;
+        private final String mTag;
+        private final String mFormat;
+        private final StringBuilder mStringBuilder;
+        private int mNextFormatCharPos;
+
+        RealLogMessage(int logLevel, String tag, String format) {
+            mLogLevel = logLevel;
+            mTag = tag;
+            mFormat = format;
+            mStringBuilder = new StringBuilder();
+            mNextFormatCharPos = 0;
+        }
+
+        @Override
+        public WifiLog.LogMessage r(String value) {
+            // Since the logcat back-end is just transitional, we don't attempt to tag sensitive
+            // information in it.
+            return c(value);
+        }
+
+        @Override
+        public WifiLog.LogMessage c(String value) {
+            copyUntilPlaceholder();
+            if (mNextFormatCharPos < mFormat.length()) {
+                mStringBuilder.append(value);
+                ++mNextFormatCharPos;
+            }
+            return this;
+        }
+
+        @Override
+        public WifiLog.LogMessage c(long value) {
+            copyUntilPlaceholder();
+            if (mNextFormatCharPos < mFormat.length()) {
+                mStringBuilder.append(value);
+                ++mNextFormatCharPos;
+            }
+            return this;
+        }
+
+        @Override
+        public WifiLog.LogMessage c(char value) {
+            copyUntilPlaceholder();
+            if (mNextFormatCharPos < mFormat.length()) {
+                mStringBuilder.append(value);
+                ++mNextFormatCharPos;
+            }
+            return this;
+        }
+
+        @Override
+        public WifiLog.LogMessage c(boolean value) {
+            copyUntilPlaceholder();
+            if (mNextFormatCharPos < mFormat.length()) {
+                mStringBuilder.append(value);
+                ++mNextFormatCharPos;
+            }
+            return this;
+        }
+
+        @Override
+        public void flush() {
+            if (mNextFormatCharPos < mFormat.length()) {
+                mStringBuilder.append(mFormat, mNextFormatCharPos, mFormat.length());
+            }
+            if (sVerboseLogging || mLogLevel > Log.DEBUG) {
+                Log.println(mLogLevel, mTag, mStringBuilder.toString());
+            }
+        }
+
+        /* Should generally not be used; implemented primarily to aid in testing. */
+        public String toString() {
+            return mStringBuilder.toString();
+        }
+
+        private void copyUntilPlaceholder() {
+            if (mNextFormatCharPos >= mFormat.length()) {
+                return;
+            }
+
+            int placeholderPos = mFormat.indexOf(WifiLog.PLACEHOLDER, mNextFormatCharPos);
+            if (placeholderPos == -1) {
+                placeholderPos = mFormat.length();
+            }
+
+            mStringBuilder.append(mFormat, mNextFormatCharPos, placeholderPos);
+            mNextFormatCharPos = placeholderPos;
+        }
+    }
+
+    private LogMessage makeLogMessage(int logLevel, String format) {
+        // TODO(b/30737821): Consider adding an isLoggable() check.
+        return new RealLogMessage(logLevel, mTag, format);
+    }
+}
diff --git a/service/java/com/android/server/wifi/NetworkListStoreData.java b/service/java/com/android/server/wifi/NetworkListStoreData.java
new file mode 100644
index 0000000..5ddfd4d
--- /dev/null
+++ b/service/java/com/android/server/wifi/NetworkListStoreData.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.IpConfiguration;
+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.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.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class performs serialization and parsing of XML data block that contain the list of WiFi
+ * network configurations (XML block data inside <NetworkList> tag).
+ */
+public class NetworkListStoreData implements WifiConfigStore.StoreData {
+    private static final String TAG = "NetworkListStoreData";
+
+    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";
+
+    /**
+     * List of saved shared networks visible to all the users to be stored in the shared store file.
+     */
+    private List<WifiConfiguration> mSharedConfigurations;
+    /**
+     * List of saved private networks only visible to the current user to be stored in the user
+     * specific store file.
+     */
+    private List<WifiConfiguration> mUserConfigurations;
+
+    NetworkListStoreData() {}
+
+    @Override
+    public void serializeData(XmlSerializer out, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            serializeNetworkList(out, mSharedConfigurations);
+        } else {
+            serializeNetworkList(out, mUserConfigurations);
+        }
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            mSharedConfigurations = parseNetworkList(in, outerTagDepth);
+        } else {
+            mUserConfigurations = parseNetworkList(in, outerTagDepth);
+        }
+    }
+
+    @Override
+    public void resetData(boolean shared) {
+        if (shared) {
+            mSharedConfigurations = null;
+        } else {
+            mUserConfigurations = null;
+        }
+    }
+
+    @Override
+    public String getName() {
+        return XML_TAG_SECTION_HEADER_NETWORK_LIST;
+    }
+
+    @Override
+    public boolean supportShareData() {
+        return true;
+    }
+
+    public void setSharedConfigurations(List<WifiConfiguration> configs) {
+        mSharedConfigurations = configs;
+    }
+
+    /**
+     * An empty list will be returned if no shared configurations.
+     *
+     * @return List of {@link WifiConfiguration}
+     */
+    public List<WifiConfiguration> getSharedConfigurations() {
+        if (mSharedConfigurations == null) {
+            return new ArrayList<WifiConfiguration>();
+        }
+        return mSharedConfigurations;
+    }
+
+    public void setUserConfigurations(List<WifiConfiguration> configs) {
+        mUserConfigurations = configs;
+    }
+
+    /**
+     * An empty list will be returned if no user configurations.
+     *
+     * @return List of {@link WifiConfiguration}
+     */
+    public List<WifiConfiguration> getUserConfigurations() {
+        if (mUserConfigurations == null) {
+            return new ArrayList<WifiConfiguration>();
+        }
+        return mUserConfigurations;
+    }
+
+    /**
+     * Serialize the list of {@link WifiConfiguration} to an output stream in XML format.
+     *
+     * @param out The output stream to serialize the data to
+     * @param networkList The network list to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeNetworkList(XmlSerializer out, List<WifiConfiguration> networkList)
+            throws XmlPullParserException, IOException {
+        if (networkList == null) {
+            return;
+        }
+        for (WifiConfiguration network : networkList) {
+            serializeNetwork(out, network);
+        }
+    }
+
+    /**
+     * Serialize a {@link WifiConfiguration} to an output stream in XML format.
+     * @param out
+     * @param config
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeNetwork(XmlSerializer out, WifiConfiguration config)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
+
+        // Serialize WifiConfiguration.
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, config);
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+
+        // Serialize network selection status.
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_STATUS);
+        NetworkSelectionStatusXmlUtil.writeToXml(out, config.getNetworkSelectionStatus());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_STATUS);
+
+        // Serialize IP configuration.
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
+        IpConfigurationXmlUtil.writeToXml(out, config.getIpConfiguration());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
+
+        // Serialize enterprise configuration for enterprise networks.
+        if (config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+            XmlUtil.writeNextSectionStart(
+                    out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
+            WifiEnterpriseConfigXmlUtil.writeToXml(out, config.enterpriseConfig);
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
+        }
+
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK);
+    }
+
+    /**
+     * Parse a list of {@link WifiConfiguration} from an input stream in XML format.
+     *
+     * @param in The input stream to read from
+     * @param outerTagDepth The XML tag depth of the outer XML block
+     * @return List of {@link WifiConfiguration}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private List<WifiConfiguration> parseNetworkList(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        List<WifiConfiguration> networkList = new ArrayList<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_NETWORK,
+                outerTagDepth)) {
+            // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are
+            // fatal and should abort the entire loading process.
+            try {
+                WifiConfiguration config = parseNetwork(in, outerTagDepth + 1);
+                networkList.add(config);
+            } catch (RuntimeException e) {
+                // Failed to parse this network, skip it.
+                Log.e(TAG, "Failed to parse network config. Skipping...", e);
+            }
+        }
+        return networkList;
+    }
+
+    /**
+     * Parse a {@link WifiConfiguration} from an input stream in XML format.
+     *
+     * @param in The input stream to read from
+     * @param outerTagDepth The XML tag depth of the outer XML block
+     * @return {@link WifiConfiguration}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private WifiConfiguration parseNetwork(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        Pair<String, WifiConfiguration> parsedConfig = null;
+        NetworkSelectionStatus status = null;
+        IpConfiguration ipConfiguration = null;
+        WifiEnterpriseConfig enterpriseConfig = null;
+
+        String[] headerName = new String[1];
+        while (XmlUtil.gotoNextSectionOrEnd(in, headerName, outerTagDepth)) {
+            switch (headerName[0]) {
+                case XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION:
+                    if (parsedConfig != null) {
+                        throw new XmlPullParserException("Detected duplicate tag for: "
+                                + XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+                    }
+                    parsedConfig = WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                    break;
+                case XML_TAG_SECTION_HEADER_NETWORK_STATUS:
+                    if (status != null) {
+                        throw new XmlPullParserException("Detected duplicate tag for: "
+                                + XML_TAG_SECTION_HEADER_NETWORK_STATUS);
+                    }
+                    status = NetworkSelectionStatusXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                    break;
+                case XML_TAG_SECTION_HEADER_IP_CONFIGURATION:
+                    if (ipConfiguration != null) {
+                        throw new XmlPullParserException("Detected duplicate tag for: "
+                                + XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
+                    }
+                    ipConfiguration = IpConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                    break;
+                case XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION:
+                    if (enterpriseConfig != null) {
+                        throw new XmlPullParserException("Detected duplicate tag for: "
+                                + XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
+                    }
+                    enterpriseConfig =
+                            WifiEnterpriseConfigXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown tag under "
+                            + XML_TAG_SECTION_HEADER_NETWORK + ": " + headerName[0]);
+            }
+        }
+        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);
+        }
+
+        configuration.setNetworkSelectionStatus(status);
+        configuration.setIpConfiguration(ipConfiguration);
+        if (enterpriseConfig != null) {
+            configuration.enterpriseConfig = enterpriseConfig;
+        }
+        return configuration;
+    }
+}
+
diff --git a/service/java/com/android/server/wifi/NetworkUpdateResult.java b/service/java/com/android/server/wifi/NetworkUpdateResult.java
index 63cc33f..851b13b 100644
--- a/service/java/com/android/server/wifi/NetworkUpdateResult.java
+++ b/service/java/com/android/server/wifi/NetworkUpdateResult.java
@@ -18,22 +18,25 @@
 
 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
 
-class NetworkUpdateResult {
+public class NetworkUpdateResult {
     int netId;
     boolean ipChanged;
     boolean proxyChanged;
+    boolean credentialChanged;
     boolean isNewNetwork = false;
 
     public NetworkUpdateResult(int id) {
         netId = id;
         ipChanged = false;
         proxyChanged = false;
+        credentialChanged = false;
     }
 
-    public NetworkUpdateResult(boolean ip, boolean proxy) {
+    public NetworkUpdateResult(boolean ip, boolean proxy, boolean credential) {
         netId = INVALID_NETWORK_ID;
         ipChanged = ip;
         proxyChanged = proxy;
+        credentialChanged = credential;
     }
 
     public void setNetworkId(int id) {
@@ -44,22 +47,18 @@
         return netId;
     }
 
-    public void setIpChanged(boolean ip) {
-        ipChanged = ip;
-    }
-
     public boolean hasIpChanged() {
         return ipChanged;
     }
 
-    public void setProxyChanged(boolean proxy) {
-        proxyChanged = proxy;
-    }
-
     public boolean hasProxyChanged() {
         return proxyChanged;
     }
 
+    public boolean hasCredentialChanged() {
+        return credentialChanged;
+    }
+
     public boolean isNewNetwork() {
         return isNewNetwork;
     }
@@ -67,4 +66,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/OWNERS b/service/java/com/android/server/wifi/OWNERS
new file mode 100644
index 0000000..8487100
--- /dev/null
+++ b/service/java/com/android/server/wifi/OWNERS
@@ -0,0 +1,82 @@
+# This file follows the Gerrit find-owners OWNERS syntax described at
+# https://gerrit.googlesource.com/plugins/find-owners/+/master/src/main/resources/Documentation/syntax.md
+
+etancohen@google.com
+kuh@google.com
+lorenzo@google.com
+mett@google.com
+mplass@google.com
+nywang@google.com
+pstew@google.com
+quiche@google.com
+rpius@google.com
+silberst@google.com
+sohanirao@google.com
+zpan@google.com
+zqiu@google.com
+
+# configuration
+per-file WifiBackupRestore*=rpius@google.com
+per-file WifiConfigManager*=rpius@google.com
+per-file WifiConfigStore*=rpius@google.com
+per-file WifiSupplicantControl*=rpius@google.com
+
+# diagnostics
+per-file BaseWifiDiagnostics*=quiche@google.com
+per-file LastMileLogger*=quiche@google.com
+per-file WifiDiagnostics*=quiche@google.com
+per-file WifiLoggerHal*=quiche@google.com
+
+# HAL support
+per-file HalDeviceManager*=etancohen@google.com
+per-file SupplicantStaNetworkHal*=kuh@google.com
+per-file SupplicantStaNetworkHal*=rpius@google.com
+per-file SupplicantStaIfaceHal*=kuh@google.com
+per-file SupplicantStaIfaceHal*=rpius@google.com
+per-file WifiVendorHal*=mplass@google.com
+
+# logging
+per-file DummyLogMessage*=quiche@google.com
+per-file FakeWifiLog*=quiche@google.com
+per-file LogcatLog*=quiche@google.com
+per-file WifiLog*=quiche@google.com
+
+# mode management
+per-file ActiveModeManager*=silberst@google.com
+per-file ClientModeManager*=silberst@google.com
+per-file ScanOnlyModeManager*=silberst@google.com
+per-file WifiController*=silberst@google.com
+per-file WifiService*=silberst@google.com
+per-file WifiServiceImpl*=silberst@google.com
+per-file WifiStateMachine*=silberst@google.com
+
+# network selection
+per-file SavedNetworkEvaluator*=zpan@google.com
+per-file WifiConnectivityManager*=zpan@google.com
+per-file WifiNetworkSelector*=zpan@google.com
+
+# random bits
+per-file ByteBufferReader*=zqiu@google.com
+per-file IMSIParameter*=zqiu@google.com
+per-file SIMAccessor*=zqiu@google.com
+per-file WifiCountryCode*=nywang@google.com
+per-file WifiLastResortWatchdog*=kuh@google.com
+per-file WifiLastResortWatchdog*=silberst@google.com
+per-file WifiMetrics*=kuh@google.com
+per-file WifiScoreReport*=mplass@google.com
+
+# soft-ap/tethering
+per-file SoftApManager*=silberst@google.com
+per-file WifiApConfigStore*=silberst@google.com
+
+# test-support
+per-file Clock*=quiche@google.com
+per-file FrameworkFacade*=kuh@google.com
+per-file FrameworkFacade*=rpius@google.com
+per-file FrameworkFacade*=silberst@google.com
+per-file WifiInjector*=kuh@google.com
+per-file WifiInjector*=rpius@google.com
+per-file WifiInjector*=silberst@google.com
+
+# wificond
+per-file WificondControl*=nywang@google.com
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/RttService.java b/service/java/com/android/server/wifi/RttService.java
index 3eafc77..7e4648b 100644
--- a/service/java/com/android/server/wifi/RttService.java
+++ b/service/java/com/android/server/wifi/RttService.java
@@ -5,36 +5,49 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IClientInterface;
+import android.net.wifi.IInterfaceEventCallback;
 import android.net.wifi.IRttManager;
+import android.net.wifi.IWificond;
 import android.net.wifi.RttManager;
 import android.net.wifi.RttManager.ResponderConfig;
 import android.net.wifi.WifiManager;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.IState;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.SystemService;
 
-import java.util.HashMap;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Queue;
 import java.util.Set;
 
 public final class RttService extends SystemService {
 
     public static final boolean DBG = true;
+    private static final String WIFICOND_SERVICE_NAME = "wificond";
 
     static class RttServiceImpl extends IRttManager.Stub {
 
@@ -65,20 +78,27 @@
                             if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
                         }
                         if (DBG) Slog.d(TAG, "closing client " + msg.replyTo);
-                        ClientInfo ci = mClients.remove(msg.replyTo);
-                        if (ci != null) ci.cleanup();
+                        synchronized (mLock) {
+                            ClientInfo ci = mClients.remove(msg.replyTo);
+                            if (ci != null) ci.cleanup();
+                        }
                         return;
                     case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
                         AsyncChannel ac = new AsyncChannel();
                         ac.connected(mContext, this, msg.replyTo);
-                        ClientInfo client = new ClientInfo(ac, msg.replyTo);
-                        mClients.put(msg.replyTo, client);
+                        ClientInfo client = new ClientInfo(ac, msg.sendingUid);
+                        synchronized (mLock) {
+                            mClients.put(msg.replyTo, client);
+                        }
                         ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
                                 AsyncChannel.STATUS_SUCCESSFUL);
                         return;
                 }
 
-                ClientInfo ci = mClients.get(msg.replyTo);
+                ClientInfo ci;
+                synchronized (mLock) {
+                    ci = mClients.get(msg.replyTo);
+                }
                 if (ci == null) {
                     Slog.e(TAG, "Could not find client info for message " + msg.replyTo);
                     replyFailed(msg, RttManager.REASON_INVALID_LISTENER, "Could not find listener");
@@ -123,17 +143,18 @@
         private final Looper mLooper;
         private RttStateMachine mStateMachine;
         private ClientHandler mClientHandler;
+        private WifiInjector mWifiInjector;
 
-        RttServiceImpl(Context context, Looper looper) {
+        RttServiceImpl(Context context, Looper looper, WifiInjector wifiInjector) {
             mContext = context;
-            mWifiNative = WifiNative.getWlanNativeInterface();
+            mWifiNative = wifiInjector.getWifiNative();
             mLooper = looper;
+            mWifiInjector = wifiInjector;
         }
 
         public void startService() {
             mClientHandler = new ClientHandler(mLooper);
             mStateMachine = new RttStateMachine(mLooper);
-
             mContext.registerReceiver(
                     new BroadcastReceiver() {
                         @Override
@@ -170,15 +191,15 @@
 
         private class ClientInfo {
             private final AsyncChannel mChannel;
-            private final Messenger mMessenger;
-            HashMap<Integer, RttRequest> mRequests = new HashMap<Integer,
-                    RttRequest>();
+            private final int mUid;
+
+            ArrayMap<Integer, RttRequest> mRequests = new ArrayMap<>();
             // Client keys of all outstanding responders.
             Set<Integer> mResponderRequests = new HashSet<>();
 
-            ClientInfo(AsyncChannel c, Messenger m) {
-                mChannel = c;
-                mMessenger = m;
+            ClientInfo(AsyncChannel channel, int uid) {
+                mChannel = channel;
+                mUid = uid;
             }
 
             void addResponderRequest(int key) {
@@ -215,7 +236,7 @@
 
             void reportResponderEnableFailed(int key, int reason) {
                 mChannel.sendMessage(RttManager.CMD_OP_ENALBE_RESPONDER_FAILED, reason, key);
-                mResponderRequests.remove(key);
+                removeResponderRequest(key);
             }
 
             void reportResult(RttRequest request, RttManager.RttResult[] results) {
@@ -224,7 +245,7 @@
 
                 mChannel.sendMessage(RttManager.CMD_OP_SUCCEEDED,
                         0, request.key, parcelableResults);
-                mRequests.remove(request.key);
+                removeRttRequest(request.key);
             }
 
             void reportFailed(RttRequest request, int reason, String description) {
@@ -235,7 +256,7 @@
                 Bundle bundle = new Bundle();
                 bundle.putString(RttManager.DESCRIPTION_KEY, description);
                 mChannel.sendMessage(RttManager.CMD_OP_FAILED, key, reason, bundle);
-                mRequests.remove(key);
+                removeRttRequest(key);
             }
 
             void reportAborted(int key) {
@@ -252,10 +273,19 @@
                 mResponderRequests.clear();
                 mStateMachine.sendMessage(RttManager.CMD_OP_DISABLE_RESPONDER);
             }
+
+            @Override
+            public String toString() {
+                return "ClientInfo [uid=" + mUid + ", channel=" + mChannel + "]";
+            }
         }
 
-        private Queue<RttRequest> mRequestQueue = new LinkedList<RttRequest>();
-        private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>(4);
+        private Queue<RttRequest> mRequestQueue = new LinkedList<>();
+
+        @GuardedBy("mLock")
+        private ArrayMap<Messenger, ClientInfo> mClients = new ArrayMap<>();
+        // Lock for mClients.
+        private final Object mLock = new Object();
 
         private static final int BASE = Protocol.BASE_WIFI_RTT_SERVICE;
 
@@ -263,11 +293,36 @@
         private static final int CMD_DRIVER_UNLOADED                     = BASE + 1;
         private static final int CMD_ISSUE_NEXT_REQUEST                  = BASE + 2;
         private static final int CMD_RTT_RESPONSE                        = BASE + 3;
+        private static final int CMD_CLIENT_INTERFACE_READY              = BASE + 4;
+        private static final int CMD_CLIENT_INTERFACE_DOWN               = BASE + 5;
 
         // Maximum duration for responder role.
         private static final int MAX_RESPONDER_DURATION_SECONDS = 60 * 10;
 
+        private static class InterfaceEventHandler extends IInterfaceEventCallback.Stub {
+            InterfaceEventHandler(RttStateMachine rttStateMachine) {
+                mRttStateMachine = rttStateMachine;
+            }
+            @Override
+            public void OnClientTorndownEvent(IClientInterface networkInterface) {
+                mRttStateMachine.sendMessage(CMD_CLIENT_INTERFACE_DOWN, networkInterface);
+            }
+            @Override
+            public void OnClientInterfaceReady(IClientInterface networkInterface) {
+                mRttStateMachine.sendMessage(CMD_CLIENT_INTERFACE_READY, networkInterface);
+            }
+            @Override
+            public void OnApTorndownEvent(IApInterface networkInterface) { }
+            @Override
+            public void OnApInterfaceReady(IApInterface networkInterface) { }
+
+            private RttStateMachine mRttStateMachine;
+        }
+
         class RttStateMachine extends StateMachine {
+            private IWificond mWificond;
+            private InterfaceEventHandler mInterfaceEventHandler;
+            private IClientInterface mClientInterface;
 
             DefaultState mDefaultState = new DefaultState();
             EnabledState mEnabledState = new EnabledState();
@@ -305,7 +360,11 @@
                         case RttManager.CMD_OP_STOP_RANGING:
                             return HANDLED;
                         case RttManager.CMD_OP_ENABLE_RESPONDER:
-                            ClientInfo client = mClients.get(msg.replyTo);
+
+                            ClientInfo client;
+                            synchronized (mLock) {
+                                client = mClients.get(msg.replyTo);
+                            }
                             if (client == null) {
                                 Log.e(TAG, "client not connected yet!");
                                 break;
@@ -325,9 +384,48 @@
 
             class EnabledState extends State {
                 @Override
+                public void enter() {
+                    // This allows us to tolerate wificond restarts.
+                    // When wificond restarts WifiStateMachine is supposed to go
+                    // back to initial state and restart.
+                    // 1) RttService watches for WIFI_STATE_ENABLED broadcasts
+                    // 2) WifiStateMachine sends these broadcasts in the SupplicantStarted state
+                    // 3) Since WSM will only be in SupplicantStarted for as long as wificond is
+                    // alive, we refresh our wificond handler here and we don't subscribe to
+                    // wificond's death explicitly.
+                    mWificond = mWifiInjector.makeWificond();
+                    if (mWificond == null) {
+                        Log.w(TAG, "Failed to get wificond binder handler");
+                        transitionTo(mDefaultState);
+                    }
+                    mInterfaceEventHandler = new InterfaceEventHandler(mStateMachine);
+                    try {
+                        mWificond.RegisterCallback(mInterfaceEventHandler);
+                        // Get the current client interface, assuming there is at most
+                        // one client interface for now.
+                        List<IBinder> interfaces = mWificond.GetClientInterfaces();
+                        if (interfaces.size() > 0) {
+                            mStateMachine.sendMessage(
+                                    CMD_CLIENT_INTERFACE_READY,
+                                    IClientInterface.Stub.asInterface(interfaces.get(0)));
+                        }
+                    } catch (RemoteException e1) { }
+
+                }
+                @Override
+                public void exit() {
+                    try {
+                        mWificond.UnregisterCallback(mInterfaceEventHandler);
+                    } catch (RemoteException e1) { }
+                    mInterfaceEventHandler = null;
+                }
+                @Override
                 public boolean processMessage(Message msg) {
                     if (DBG) Log.d(TAG, "EnabledState got" + msg);
-                    ClientInfo ci = mClients.get(msg.replyTo);
+                    ClientInfo ci;
+                    synchronized (mLock) {
+                        ci = mClients.get(msg.replyTo);
+                    }
 
                     switch (msg.what) {
                         case CMD_DRIVER_UNLOADED:
@@ -383,6 +481,14 @@
                             break;
                         case RttManager.CMD_OP_DISABLE_RESPONDER:
                             break;
+                        case CMD_CLIENT_INTERFACE_DOWN:
+                            if (mClientInterface == (IClientInterface) msg.obj) {
+                                mClientInterface = null;
+                            }
+                            break;
+                        case CMD_CLIENT_INTERFACE_READY:
+                            mClientInterface = (IClientInterface) msg.obj;
+                            break;
                         default:
                             return NOT_HANDLED;
                     }
@@ -455,9 +561,11 @@
 
             // Check if there are still outstanding responder requests from any client.
             private boolean hasOutstandingReponderRequests() {
-                for (ClientInfo client : mClients.values()) {
-                    if (!client.mResponderRequests.isEmpty()) {
-                        return true;
+                synchronized (mLock) {
+                    for (ClientInfo client : mClients.values()) {
+                        if (!client.mResponderRequests.isEmpty()) {
+                            return true;
+                        }
                     }
                 }
                 return false;
@@ -470,7 +578,10 @@
                 @Override
                 public boolean processMessage(Message msg) {
                     if (DBG) Log.d(TAG, "ResponderEnabledState got " + msg);
-                    ClientInfo ci = mClients.get(msg.replyTo);
+                    ClientInfo ci;
+                    synchronized (mLock) {
+                        ci = mClients.get(msg.replyTo);
+                    }
                     int key = msg.arg2;
                     switch(msg.what) {
                         case RttManager.CMD_OP_ENABLE_RESPONDER:
@@ -503,6 +614,14 @@
                     }
                 }
             }
+
+            /**
+             * Returns name of current state.
+             */
+            String currentState() {
+                IState state = getCurrentState();
+                return state == null ? "null" : state.getName();
+            }
         }
 
         void replySucceeded(Message msg, Object obj) {
@@ -551,6 +670,26 @@
             return true;
         }
 
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump RttService from from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid()
+                        + " without permission "
+                        + android.Manifest.permission.DUMP);
+                return;
+            }
+            pw.println("current state: " + mStateMachine.currentState());
+            pw.println("clients:");
+            synchronized (mLock) {
+                for (ClientInfo client : mClients.values()) {
+                    pw.println("  " + client);
+                }
+            }
+        }
+
         private WifiNative.RttEventHandler mEventHandler = new WifiNative.RttEventHandler() {
             @Override
             public void onRttResults(RttManager.RttResult[] result) {
@@ -597,7 +736,8 @@
 
     @Override
     public void onStart() {
-        mImpl = new RttServiceImpl(getContext(), mHandlerThread.getLooper());
+        mImpl = new RttServiceImpl(getContext(),
+                mHandlerThread.getLooper(), WifiInjector.getInstance());
 
         Log.i(TAG, "Starting " + Context.WIFI_RTT_SERVICE);
         publishBinderService(Context.WIFI_RTT_SERVICE, mImpl);
@@ -608,7 +748,8 @@
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             Log.i(TAG, "Registering " + Context.WIFI_RTT_SERVICE);
             if (mImpl == null) {
-                mImpl = new RttServiceImpl(getContext(), mHandlerThread.getLooper());
+                mImpl = new RttServiceImpl(getContext(),
+                        mHandlerThread.getLooper(),  WifiInjector.getInstance());
             }
             mImpl.startService();
         }
diff --git a/service/java/com/android/server/wifi/SIMAccessor.java b/service/java/com/android/server/wifi/SIMAccessor.java
index e446436..21bfb9c 100644
--- a/service/java/com/android/server/wifi/SIMAccessor.java
+++ b/service/java/com/android/server/wifi/SIMAccessor.java
@@ -23,7 +23,7 @@
         List<String> imsis = new ArrayList<>();
         for (int subId : mSubscriptionManager.getActiveSubscriptionIdList()) {
             String imsi = mTelephonyManager.getSubscriberId(subId);
-            if (mccMnc.matches(imsi)) {
+            if (imsi != null && mccMnc.matchesImsi(imsi)) {
                 imsis.add(imsi);
             }
         }
diff --git a/service/java/com/android/server/wifi/SavedNetworkEvaluator.java b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
new file mode 100644
index 0000000..9ac7068
--- /dev/null
+++ b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.util.LocalLog;
+import android.util.Pair;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class is the WifiNetworkSelector.NetworkEvaluator implementation for
+ * saved networks.
+ */
+public class SavedNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
+    private static final String NAME = "SavedNetworkEvaluator";
+    private final WifiConfigManager mWifiConfigManager;
+    private final Clock mClock;
+    private final LocalLog mLocalLog;
+    private final WifiConnectivityHelper mConnectivityHelper;
+    private final int mRssiScoreSlope;
+    private final int mRssiScoreOffset;
+    private final int mSameBssidAward;
+    private final int mSameNetworkAward;
+    private final int mBand5GHzAward;
+    private final int mLastSelectionAward;
+    private final int mSecurityAward;
+    private final int mThresholdSaturatedRssi24;
+    private final int mThresholdSaturatedRssi5;
+
+    SavedNetworkEvaluator(final Context context, WifiConfigManager configManager, Clock clock,
+            LocalLog localLog, WifiConnectivityHelper connectivityHelper) {
+        mWifiConfigManager = configManager;
+        mClock = clock;
+        mLocalLog = localLog;
+        mConnectivityHelper = connectivityHelper;
+
+        mRssiScoreSlope = context.getResources().getInteger(
+                R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
+        mRssiScoreOffset = context.getResources().getInteger(
+                R.integer.config_wifi_framework_RSSI_SCORE_OFFSET);
+        mSameBssidAward = context.getResources().getInteger(
+                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
+        mSameNetworkAward = context.getResources().getInteger(
+                R.integer.config_wifi_framework_current_network_boost);
+        mLastSelectionAward = context.getResources().getInteger(
+                R.integer.config_wifi_framework_LAST_SELECTION_AWARD);
+        mSecurityAward = context.getResources().getInteger(
+                R.integer.config_wifi_framework_SECURITY_AWARD);
+        mBand5GHzAward = context.getResources().getInteger(
+                R.integer.config_wifi_framework_5GHz_preference_boost_factor);
+        mThresholdSaturatedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
+        mThresholdSaturatedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
+    }
+
+    private void localLog(String log) {
+        mLocalLog.log(log);
+    }
+
+    /**
+     * Get the evaluator name.
+     */
+    public String getName() {
+        return NAME;
+    }
+
+    /**
+     * Update all the saved networks' selection status
+     */
+    private void updateSavedNetworkSelectionStatus() {
+        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
+        if (savedNetworks.size() == 0) {
+            localLog("No saved networks.");
+            return;
+        }
+
+        StringBuffer sbuf = new StringBuffer();
+        for (WifiConfiguration network : savedNetworks) {
+            /**
+             * Ignore Passpoint networks. Passpoint networks are also considered as "saved"
+             * network, but without being persisted to the storage. They are managed
+             * by {@link PasspointNetworkEvaluator}.
+             */
+            if (network.isPasspoint()) {
+                continue;
+            }
+
+            // If a configuration is temporarily disabled, re-enable it before trying
+            // to connect to it.
+            mWifiConfigManager.tryEnableNetwork(network.networkId);
+
+            //TODO(b/30928589): Enable "permanently" disabled networks if we are in DISCONNECTED
+            // state.
+
+            // Clear the cached candidate, score and seen.
+            mWifiConfigManager.clearNetworkCandidateScanResult(network.networkId);
+
+            // Log disabled network.
+            WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
+            if (!status.isNetworkEnabled()) {
+                sbuf.append("  ").append(WifiNetworkSelector.toNetworkString(network)).append(" ");
+                for (int index = WifiConfiguration.NetworkSelectionStatus
+                            .NETWORK_SELECTION_DISABLED_STARTING_INDEX;
+                        index < WifiConfiguration.NetworkSelectionStatus
+                            .NETWORK_SELECTION_DISABLED_MAX;
+                        index++) {
+                    int count = status.getDisableReasonCounter(index);
+                    // Here we log the reason as long as its count is greater than zero. The
+                    // network may not be disabled because of this particular reason. Logging
+                    // this information anyway to help understand what happened to the network.
+                    if (count > 0) {
+                        sbuf.append("reason=")
+                                .append(WifiConfiguration.NetworkSelectionStatus
+                                        .getNetworkDisableReasonString(index))
+                                .append(", count=").append(count).append("; ");
+                    }
+                }
+                sbuf.append("\n");
+            }
+        }
+
+        if (sbuf.length() > 0) {
+            localLog("Disabled saved networks:");
+            localLog(sbuf.toString());
+        }
+    }
+
+    /**
+     * Update the evaluator.
+     */
+    public void update(List<ScanDetail> scanDetails) {
+        updateSavedNetworkSelectionStatus();
+    }
+
+    private int calculateBssidScore(ScanResult scanResult, WifiConfiguration network,
+                        WifiConfiguration currentNetwork, String currentBssid,
+                        StringBuffer sbuf) {
+        int score = 0;
+        boolean is5GHz = scanResult.is5GHz();
+
+        sbuf.append("[ ").append(scanResult.SSID).append(" ").append(scanResult.BSSID)
+                .append(" RSSI:").append(scanResult.level).append(" ] ");
+        // Calculate the RSSI score.
+        int rssiSaturationThreshold = is5GHz ? mThresholdSaturatedRssi5 : mThresholdSaturatedRssi24;
+        int rssi = scanResult.level < rssiSaturationThreshold ? scanResult.level
+                : rssiSaturationThreshold;
+        score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
+        sbuf.append(" RSSI score: ").append(score).append(",");
+
+        // 5GHz band bonus.
+        if (is5GHz) {
+            score += mBand5GHzAward;
+            sbuf.append(" 5GHz bonus: ").append(mBand5GHzAward).append(",");
+        }
+
+        // Last user selection award.
+        int lastUserSelectedNetworkId = mWifiConfigManager.getLastSelectedNetwork();
+        if (lastUserSelectedNetworkId != WifiConfiguration.INVALID_NETWORK_ID
+                && lastUserSelectedNetworkId == network.networkId) {
+            long timeDifference = mClock.getElapsedSinceBootMillis()
+                    - mWifiConfigManager.getLastSelectedTimeStamp();
+            if (timeDifference > 0) {
+                int bonus = mLastSelectionAward - (int) (timeDifference / 1000 / 60);
+                score += bonus > 0 ? bonus : 0;
+                sbuf.append(" User selection ").append(timeDifference / 1000 / 60)
+                        .append(" minutes ago, bonus: ").append(bonus).append(",");
+            }
+        }
+
+        // Same network award.
+        if (currentNetwork != null
+                && (network.networkId == currentNetwork.networkId
+                //TODO(b/36788683): re-enable linked configuration check
+                /* || network.isLinked(currentNetwork) */)) {
+            score += mSameNetworkAward;
+            sbuf.append(" Same network bonus: ").append(mSameNetworkAward).append(",");
+
+            // When firmware roaming is supported, equivalent BSSIDs (the ones under the
+            // same network as the currently connected one) get the same BSSID award.
+            if (mConnectivityHelper.isFirmwareRoamingSupported()
+                    && currentBssid != null && !currentBssid.equals(scanResult.BSSID)) {
+                score += mSameBssidAward;
+                sbuf.append(" Equivalent BSSID bonus: ").append(mSameBssidAward).append(",");
+            }
+        }
+
+        // Same BSSID award.
+        if (currentBssid != null && currentBssid.equals(scanResult.BSSID)) {
+            score += mSameBssidAward;
+            sbuf.append(" Same BSSID bonus: ").append(mSameBssidAward).append(",");
+        }
+
+        // Security award.
+        if (!WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
+            score += mSecurityAward;
+            sbuf.append(" Secure network bonus: ").append(mSecurityAward).append(",");
+        }
+
+        sbuf.append(" ## Total score: ").append(score).append("\n");
+
+        return score;
+    }
+
+    /**
+     * Evaluate all the networks from the scan results and return
+     * the WifiConfiguration of the network chosen for connection.
+     *
+     * @return configuration of the chosen network;
+     *         null if no network in this category is available.
+     */
+    public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
+                    WifiConfiguration currentNetwork, String currentBssid, boolean connected,
+                    boolean untrustedNetworkAllowed,
+                    List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
+        int highestScore = Integer.MIN_VALUE;
+        ScanResult scanResultCandidate = null;
+        WifiConfiguration candidate = null;
+        StringBuffer scoreHistory = new StringBuffer();
+
+        for (ScanDetail scanDetail : scanDetails) {
+            ScanResult scanResult = scanDetail.getScanResult();
+            int highestScoreOfScanResult = Integer.MIN_VALUE;
+            int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID;
+
+            // One ScanResult can be associated with more than one networks, hence we calculate all
+            // the scores and use the highest one as the ScanResult's score.
+            List<WifiConfiguration> associatedConfigurations = null;
+            WifiConfiguration associatedConfiguration =
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+
+            if (associatedConfiguration == null) {
+                continue;
+            } else {
+                associatedConfigurations =
+                    new ArrayList<>(Arrays.asList(associatedConfiguration));
+            }
+
+            for (WifiConfiguration network : associatedConfigurations) {
+                /**
+                 * Ignore Passpoint and Ephemeral networks. They are configured networks,
+                 * but without being persisted to the storage. They are evaluated by
+                 * {@link PasspointNetworkEvaluator} and {@link ScoredNetworkEvaluator}
+                 * respectively.
+                 */
+                if (network.isPasspoint() || network.isEphemeral()) {
+                    continue;
+                }
+
+                WifiConfiguration.NetworkSelectionStatus status =
+                        network.getNetworkSelectionStatus();
+                status.setSeenInLastQualifiedNetworkSelection(true);
+
+                if (!status.isNetworkEnabled()) {
+                    continue;
+                } else if (network.BSSID != null &&  !network.BSSID.equals("any")
+                        && !network.BSSID.equals(scanResult.BSSID)) {
+                    // App has specified the only BSSID to connect for this
+                    // configuration. So only the matching ScanResult can be a candidate.
+                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
+                            + " has specified BSSID " + network.BSSID + ". Skip "
+                            + scanResult.BSSID);
+                    continue;
+                }
+
+                int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
+                        scoreHistory);
+
+                // Set candidate ScanResult for all saved networks to ensure that users can
+                // override network selection. See WifiNetworkSelector#setUserConnectChoice.
+                // TODO(b/36067705): consider alternative designs to push filtering/selecting of
+                // user connect choice networks to RecommendedNetworkEvaluator.
+                if (score > status.getCandidateScore() || (score == status.getCandidateScore()
+                        && status.getCandidate() != null
+                        && scanResult.level > status.getCandidate().level)) {
+                    mWifiConfigManager.setNetworkCandidateScanResult(
+                            network.networkId, scanResult, score);
+                }
+
+                // If the network is marked to use external scores, or is an open network with
+                // curate saved open networks enabled, do not consider it for network selection.
+                if (network.useExternalScores) {
+                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
+                            + " has external score.");
+                    continue;
+                }
+
+                if (score > highestScoreOfScanResult) {
+                    highestScoreOfScanResult = score;
+                    candidateIdOfScanResult = network.networkId;
+                }
+            }
+
+            if (connectableNetworks != null) {
+                connectableNetworks.add(Pair.create(scanDetail,
+                        mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult)));
+            }
+
+            if (highestScoreOfScanResult > highestScore
+                    || (highestScoreOfScanResult == highestScore
+                    && scanResultCandidate != null
+                    && scanResult.level > scanResultCandidate.level)) {
+                highestScore = highestScoreOfScanResult;
+                scanResultCandidate = scanResult;
+                mWifiConfigManager.setNetworkCandidateScanResult(
+                        candidateIdOfScanResult, scanResultCandidate, highestScore);
+                // Reload the network config with the updated info.
+                candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult);
+            }
+        }
+
+        if (scoreHistory.length() > 0) {
+            localLog("\n" + scoreHistory.toString());
+        }
+
+        if (scanResultCandidate == null) {
+            localLog("did not see any good candidates.");
+        }
+        return candidate;
+    }
+}
diff --git a/service/java/com/android/server/wifi/ScanDetail.java b/service/java/com/android/server/wifi/ScanDetail.java
index dc87a5b..d39888e 100644
--- a/service/java/com/android/server/wifi/ScanDetail.java
+++ b/service/java/com/android/server/wifi/ScanDetail.java
@@ -20,15 +20,13 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiSsid;
 
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.anqp.HSFriendlyNameElement;
-import com.android.server.wifi.anqp.RawByteElement;
-import com.android.server.wifi.anqp.VenueNameElement;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.HSFriendlyNameElement;
+import com.android.server.wifi.hotspot2.anqp.RawByteElement;
+import com.android.server.wifi.hotspot2.anqp.VenueNameElement;
 import com.android.server.wifi.hotspot2.NetworkDetail;
-import com.android.server.wifi.hotspot2.PasspointMatch;
 import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
 
 import java.util.List;
 import java.util.Map;
@@ -39,7 +37,6 @@
 public class ScanDetail {
     private final ScanResult mScanResult;
     private volatile NetworkDetail mNetworkDetail;
-    private final Map<HomeSP, PasspointMatch> mMatches;
     private long mSeen = 0;
 
     public ScanDetail(NetworkDetail networkDetail, WifiSsid wifiSsid, String bssid,
@@ -50,7 +47,7 @@
                 networkDetail.getAnqpDomainID(), networkDetail.getOsuProviders(),
                 caps, level, frequency, tsf);
         mSeen = System.currentTimeMillis();
-        //mScanResult.seen = mSeen;
+        mScanResult.seen = mSeen;
         mScanResult.channelWidth = networkDetail.getChannelWidth();
         mScanResult.centerFreq0 = networkDetail.getCenterfreq0();
         mScanResult.centerFreq1 = networkDetail.getCenterfreq1();
@@ -62,7 +59,6 @@
         if (networkDetail.isInterworking()) {
             mScanResult.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
         }
-        mMatches = null;
     }
 
     public ScanDetail(WifiSsid wifiSsid, String bssid, String caps, int level, int frequency,
@@ -70,53 +66,19 @@
         mNetworkDetail = null;
         mScanResult = new ScanResult(wifiSsid, bssid, 0L, -1, null, caps, level, frequency, tsf);
         mSeen = seen;
-        //mScanResult.seen = mSeen;
+        mScanResult.seen = mSeen;
         mScanResult.channelWidth = 0;
         mScanResult.centerFreq0 = 0;
         mScanResult.centerFreq1 = 0;
         mScanResult.flags = 0;
-        mMatches = null;
     }
 
-    public ScanDetail(ScanResult scanResult, NetworkDetail networkDetail,
-                       Map<HomeSP, PasspointMatch> matches) {
+    public ScanDetail(ScanResult scanResult, NetworkDetail networkDetail) {
         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..a651379 100644
--- a/service/java/com/android/server/wifi/ScanDetailCache.java
+++ b/service/java/com/android/server/wifi/ScanDetailCache.java
@@ -21,16 +21,11 @@
 import android.os.SystemClock;
 import android.util.Log;
 
-import com.android.server.wifi.hotspot2.PasspointMatch;
-import com.android.server.wifi.hotspot2.PasspointMatchInfo;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.Iterator;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.HashMap;
 
 /**
  * Maps BSSIDs to their individual ScanDetails for a given WifiConfiguration.
@@ -40,28 +35,36 @@
     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;
 
-    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);
     }
 
     void put(ScanDetail scanDetail) {
-        put(scanDetail, null, null);
-    }
-
-    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);
-
-        if (match != null && homeSp != null) {
-            mPasspointMatches.put(scanDetail.getBSSIDString(),
-                    new PasspointMatchInfo(match, scanDetail, homeSp));
-        }
     }
 
     ScanResult get(String bssid) {
@@ -94,13 +97,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,11 +122,10 @@
                 }
             });
         }
-        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());
-            mPasspointMatches.remove(result.getBSSIDString());
         }
     }
 
@@ -219,60 +220,6 @@
     }
 
     /**
-     * Method returning the Visibility based on passpoint match time.
-     *
-     * @param age long Desired time window for matches.
-     * @return WifiConfiguration.Visibility matches in the given visibility
-     */
-    public WifiConfiguration.Visibility getVisibilityByPasspointMatch(long age) {
-
-        long now_ms = System.currentTimeMillis();
-        PasspointMatchInfo pmiBest24 = null, pmiBest5 = null;
-
-        for (PasspointMatchInfo pmi : mPasspointMatches.values()) {
-            ScanDetail scanDetail = pmi.getScanDetail();
-            if (scanDetail == null) continue;
-            ScanResult result = scanDetail.getScanResult();
-            if (result == null) continue;
-
-            if (scanDetail.getSeen() == 0) continue;
-
-            if ((now_ms - result.seen) > age) continue;
-
-            if (result.is5GHz()) {
-                if (pmiBest5 == null || pmiBest5.compareTo(pmi) < 0) {
-                    pmiBest5 = pmi;
-                }
-            } else if (result.is24GHz()) {
-                if (pmiBest24 == null || pmiBest24.compareTo(pmi) < 0) {
-                    pmiBest24 = pmi;
-                }
-            }
-        }
-
-        WifiConfiguration.Visibility status = new WifiConfiguration.Visibility();
-        String logMsg = "Visiblity by passpoint match returned ";
-        if (pmiBest5 != null) {
-            ScanResult result = pmiBest5.getScanDetail().getScanResult();
-            status.rssi5 = result.level;
-            status.age5 = result.seen;
-            status.BSSID5 = result.BSSID;
-            logMsg += "5 GHz BSSID of " + result.BSSID;
-        }
-        if (pmiBest24 != null) {
-            ScanResult result = pmiBest24.getScanDetail().getScanResult();
-            status.rssi24 = result.level;
-            status.age24 = result.seen;
-            status.BSSID24 = result.BSSID;
-            logMsg += "2.4 GHz BSSID of " + result.BSSID;
-        }
-
-        Log.d(TAG, logMsg);
-
-        return status;
-    }
-
-    /**
      * Method to get scan matches for the desired time window.  Returns matches by passpoint time if
      * the WifiConfiguration is passpoint.
      *
@@ -280,15 +227,10 @@
      * @return WifiConfiguration.Visibility matches in the given visibility
      */
     public WifiConfiguration.Visibility getVisibility(long age) {
-        if (mConfig.isPasspoint()) {
-            return getVisibilityByPasspointMatch(age);
-        } else {
-            return getVisibilityByRssi(age);
-        }
+        return getVisibilityByRssi(age);
     }
 
 
-
     @Override
     public String toString() {
         StringBuilder sbuf = new StringBuilder();
diff --git a/service/java/com/android/server/wifi/ScanOnlyModeManager.java b/service/java/com/android/server/wifi/ScanOnlyModeManager.java
new file mode 100644
index 0000000..1edb857
--- /dev/null
+++ b/service/java/com/android/server/wifi/ScanOnlyModeManager.java
@@ -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.
+ */
+
+package com.android.server.wifi;
+
+/**
+ * Manager WiFi in Scan Only Mode - no network connections.
+ */
+public class ScanOnlyModeManager implements ActiveModeManager {
+
+    private static final String TAG = "ScanOnlyModeManager";
+
+    ScanOnlyModeManager() {
+    }
+
+    /**
+     * Start scan only mode.
+     */
+    public void start() {
+
+    }
+
+    /**
+     * Cancel any pending scans and stop scan mode.
+     */
+    public void stop() {
+
+    }
+}
diff --git a/service/java/com/android/server/wifi/ScoredNetworkEvaluator.java b/service/java/com/android/server/wifi/ScoredNetworkEvaluator.java
new file mode 100644
index 0000000..483da99
--- /dev/null
+++ b/service/java/com/android/server/wifi/ScoredNetworkEvaluator.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.server.wifi.util.ScanResultUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link WifiNetworkSelector.NetworkEvaluator} implementation that uses scores obtained by
+ * {@link NetworkScoreManager#requestScores(NetworkKey[])} to make network connection decisions.
+ */
+public class ScoredNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
+    private static final String TAG = "ScoredNetworkEvaluator";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final NetworkScoreManager mNetworkScoreManager;
+    private final WifiConfigManager mWifiConfigManager;
+    private final LocalLog mLocalLog;
+    private final ContentObserver mContentObserver;
+    private boolean mNetworkRecommendationsEnabled;
+    private WifiNetworkScoreCache mScoreCache;
+
+    ScoredNetworkEvaluator(final Context context, Looper looper,
+            final FrameworkFacade frameworkFacade, NetworkScoreManager networkScoreManager,
+            WifiConfigManager wifiConfigManager, LocalLog localLog,
+            WifiNetworkScoreCache wifiNetworkScoreCache) {
+        mScoreCache = wifiNetworkScoreCache;
+        mNetworkScoreManager = networkScoreManager;
+        mWifiConfigManager = wifiConfigManager;
+        mLocalLog = localLog;
+        mContentObserver = new ContentObserver(new Handler(looper)) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mNetworkRecommendationsEnabled = frameworkFacade.getIntegerSetting(context,
+                        Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1;
+            }
+        };
+        frameworkFacade.registerContentObserver(context,
+                Settings.Global.getUriFor(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED),
+                false /* notifyForDescendents */, mContentObserver);
+        mContentObserver.onChange(false /* unused */);
+        mLocalLog.log("ScoredNetworkEvaluator constructed. mNetworkRecommendationsEnabled: "
+                + mNetworkRecommendationsEnabled);
+    }
+
+    @Override
+    public void update(List<ScanDetail> scanDetails) {
+        if (mNetworkRecommendationsEnabled) {
+            updateNetworkScoreCache(scanDetails);
+        }
+    }
+
+    private void updateNetworkScoreCache(List<ScanDetail> scanDetails) {
+        ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
+        for (int i = 0; i < scanDetails.size(); i++) {
+            ScanResult scanResult = scanDetails.get(i).getScanResult();
+            NetworkKey networkKey = NetworkKey.createFromScanResult(scanResult);
+            if (networkKey != null) {
+                // Is there a ScoredNetwork for this ScanResult? If not, request a score.
+                if (mScoreCache.getScoredNetwork(networkKey) == null) {
+                    unscoredNetworks.add(networkKey);
+                }
+            }
+        }
+
+        // Kick the score manager if there are any unscored network.
+        if (!unscoredNetworks.isEmpty()) {
+            NetworkKey[] unscoredNetworkKeys =
+                    unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]);
+            mNetworkScoreManager.requestScores(unscoredNetworkKeys);
+        }
+    }
+
+    @Override
+    public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
+            WifiConfiguration currentNetwork, String currentBssid, boolean connected,
+            boolean untrustedNetworkAllowed,
+            List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
+        if (!mNetworkRecommendationsEnabled) {
+            mLocalLog.log("Skipping evaluateNetworks; Network recommendations disabled.");
+            return null;
+        }
+
+        final ScoreTracker scoreTracker = new ScoreTracker();
+        for (int i = 0; i < scanDetails.size(); i++) {
+            ScanDetail scanDetail = scanDetails.get(i);
+            ScanResult scanResult = scanDetail.getScanResult();
+            if (scanResult == null) continue;
+            if (mWifiConfigManager.wasEphemeralNetworkDeleted(
+                    ScanResultUtil.createQuotedSSID(scanResult.SSID))) {
+                debugLog("Ignoring disabled ephemeral SSID: " + scanResult.SSID);
+                continue;
+            }
+            final WifiConfiguration configuredNetwork =
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+            boolean untrustedScanResult = configuredNetwork == null || configuredNetwork.ephemeral;
+
+            if (!untrustedNetworkAllowed && untrustedScanResult) {
+                continue;
+            }
+
+            // Track scan results for open wifi networks
+            if (configuredNetwork == null) {
+                if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)) {
+                    scoreTracker.trackUntrustedCandidate(scanResult);
+                }
+                continue;
+            }
+
+            // Ignore non-ephemeral and non-externally scored networks
+            if (!configuredNetwork.ephemeral && !configuredNetwork.useExternalScores) {
+                continue;
+            }
+
+            // Ignore externally scored or ephemeral networks that have been disabled for selection
+            if (!configuredNetwork.getNetworkSelectionStatus().isNetworkEnabled()) {
+                debugLog("Ignoring disabled SSID: " + configuredNetwork.SSID);
+                continue;
+            }
+
+            // TODO(b/37485956): consider applying a boost for networks with only the same SSID
+            boolean isCurrentNetwork = currentNetwork != null
+                    && currentNetwork.networkId == configuredNetwork.networkId
+                    && TextUtils.equals(currentBssid, scanResult.BSSID);
+            if (configuredNetwork.ephemeral) {
+                scoreTracker.trackUntrustedCandidate(
+                        scanResult, configuredNetwork, isCurrentNetwork);
+            } else {
+                scoreTracker.trackExternallyScoredCandidate(
+                        scanResult, configuredNetwork, isCurrentNetwork);
+            }
+            if (connectableNetworks != null) {
+                connectableNetworks.add(Pair.create(scanDetail, configuredNetwork));
+            }
+        }
+
+        return scoreTracker.getCandidateConfiguration();
+    }
+
+    /** Used to track the network with the highest score. */
+    class ScoreTracker {
+        private static final int EXTERNAL_SCORED_NONE = 0;
+        private static final int EXTERNAL_SCORED_SAVED_NETWORK = 1;
+        private static final int EXTERNAL_SCORED_UNTRUSTED_NETWORK = 2;
+
+        private int mBestCandidateType = EXTERNAL_SCORED_NONE;
+        private int mHighScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
+        private WifiConfiguration mEphemeralConfig;
+        private WifiConfiguration mSavedConfig;
+        private ScanResult mScanResultCandidate;
+
+        /**
+         * Returns the available external network score or null if no score is available.
+         *
+         * @param scanResult The scan result of the network to score.
+         * @param isCurrentNetwork Flag which indicates whether this is the current network.
+         * @return A valid external score if one is available or NULL.
+         */
+        @Nullable
+        private Integer getNetworkScore(ScanResult scanResult, boolean isCurrentNetwork) {
+            if (mScoreCache.isScoredNetwork(scanResult)) {
+                int score = mScoreCache.getNetworkScore(scanResult, isCurrentNetwork);
+                if (DEBUG) {
+                    mLocalLog.log(WifiNetworkSelector.toScanId(scanResult) + " has score: "
+                            + score + " isCurrentNetwork network: " + isCurrentNetwork);
+                }
+                return score;
+            }
+            return null;
+        }
+
+        /** Track an untrusted {@link ScanResult}. */
+        void trackUntrustedCandidate(ScanResult scanResult) {
+            Integer score = getNetworkScore(scanResult, false /* isCurrentNetwork */);
+            if (score != null && score > mHighScore) {
+                mHighScore = score;
+                mScanResultCandidate = scanResult;
+                mBestCandidateType = EXTERNAL_SCORED_UNTRUSTED_NETWORK;
+                debugLog(WifiNetworkSelector.toScanId(scanResult)
+                        + " becomes the new untrusted candidate.");
+            }
+        }
+
+        /**
+         * Track an untrusted {@link ScanResult} that already has a corresponding
+         * ephemeral {@link WifiConfiguration}.
+         */
+        void trackUntrustedCandidate(
+                ScanResult scanResult, WifiConfiguration config, boolean isCurrentNetwork) {
+            Integer score = getNetworkScore(scanResult, isCurrentNetwork);
+            if (score != null && score > mHighScore) {
+                mHighScore = score;
+                mScanResultCandidate = scanResult;
+                mBestCandidateType = EXTERNAL_SCORED_UNTRUSTED_NETWORK;
+                mEphemeralConfig = config;
+                mWifiConfigManager.setNetworkCandidateScanResult(config.networkId, scanResult, 0);
+                debugLog(WifiNetworkSelector.toScanId(scanResult)
+                        + " becomes the new untrusted candidate.");
+            }
+        }
+
+        /** Tracks a saved network that has been marked with useExternalScores */
+        void trackExternallyScoredCandidate(
+                ScanResult scanResult, WifiConfiguration config, boolean isCurrentNetwork) {
+            // Always take the highest score. If there's a tie and an untrusted network is currently
+            // the best then pick the saved network.
+            Integer score = getNetworkScore(scanResult, isCurrentNetwork);
+            if (score != null
+                    && (score > mHighScore
+                    || (mBestCandidateType == EXTERNAL_SCORED_UNTRUSTED_NETWORK
+                    && score == mHighScore))) {
+                mHighScore = score;
+                mSavedConfig = config;
+                mScanResultCandidate = scanResult;
+                mBestCandidateType = EXTERNAL_SCORED_SAVED_NETWORK;
+                mWifiConfigManager.setNetworkCandidateScanResult(config.networkId, scanResult, 0);
+                debugLog(WifiNetworkSelector.toScanId(scanResult)
+                        + " becomes the new externally scored saved network candidate.");
+            }
+        }
+
+        /** Returns the best candidate network tracked by this {@link ScoreTracker}. */
+        @Nullable
+        WifiConfiguration getCandidateConfiguration() {
+            int candidateNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+            switch (mBestCandidateType) {
+                case ScoreTracker.EXTERNAL_SCORED_UNTRUSTED_NETWORK:
+                    if (mEphemeralConfig != null) {
+                        candidateNetworkId = mEphemeralConfig.networkId;
+                        mLocalLog.log(String.format("existing ephemeral candidate %s network ID:%d"
+                                        + ", meteredHint=%b",
+                                WifiNetworkSelector.toScanId(mScanResultCandidate),
+                                candidateNetworkId,
+                                mEphemeralConfig.meteredHint));
+                        break;
+                    }
+
+                    mEphemeralConfig =
+                            ScanResultUtil.createNetworkFromScanResult(mScanResultCandidate);
+                    // Mark this config as ephemeral so it isn't persisted.
+                    mEphemeralConfig.ephemeral = true;
+                    mEphemeralConfig.meteredHint = mScoreCache.getMeteredHint(mScanResultCandidate);
+                    NetworkUpdateResult result =
+                            mWifiConfigManager.addOrUpdateNetwork(mEphemeralConfig,
+                                    Process.WIFI_UID);
+                    if (!result.isSuccess()) {
+                        mLocalLog.log("Failed to add ephemeral network");
+                        break;
+                    }
+                    if (!mWifiConfigManager.updateNetworkSelectionStatus(result.getNetworkId(),
+                            WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)) {
+                        mLocalLog.log("Failed to make ephemeral network selectable");
+                        break;
+                    }
+                    candidateNetworkId = result.getNetworkId();
+                    mWifiConfigManager.setNetworkCandidateScanResult(candidateNetworkId,
+                            mScanResultCandidate, 0);
+                    mLocalLog.log(String.format("new ephemeral candidate %s network ID:%d, "
+                                                + "meteredHint=%b",
+                                        WifiNetworkSelector.toScanId(mScanResultCandidate),
+                                        candidateNetworkId,
+                                        mEphemeralConfig.meteredHint));
+                    break;
+                case ScoreTracker.EXTERNAL_SCORED_SAVED_NETWORK:
+                    candidateNetworkId = mSavedConfig.networkId;
+                    mLocalLog.log(String.format("new saved network candidate %s network ID:%d",
+                                        WifiNetworkSelector.toScanId(mScanResultCandidate),
+                                        candidateNetworkId));
+                    break;
+                case ScoreTracker.EXTERNAL_SCORED_NONE:
+                default:
+                    mLocalLog.log("ScoredNetworkEvaluator did not see any good candidates.");
+                    break;
+            }
+            return mWifiConfigManager.getConfiguredNetwork(candidateNetworkId);
+        }
+    }
+
+    private void debugLog(String msg) {
+        if (DEBUG) {
+            mLocalLog.log(msg);
+        }
+    }
+
+    @Override
+    public String getName() {
+        return TAG;
+    }
+}
diff --git a/service/java/com/android/server/wifi/SelfRecovery.java b/service/java/com/android/server/wifi/SelfRecovery.java
new file mode 100644
index 0000000..b35b7cc
--- /dev/null
+++ b/service/java/com/android/server/wifi/SelfRecovery.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.util.Log;
+
+/**
+ * This class is used to recover the wifi stack from a fatal failure. The recovery mechanism
+ * involves triggering a stack restart (essentially simulating an airplane mode toggle) using
+ * {@link WifiController}.
+ * The current triggers for:
+ * 1. Last resort watchdog bite.
+ * 2. HAL/wificond crashes during normal operation.
+ * 3. TBD: supplicant crashes during normal operation.
+ */
+public class SelfRecovery {
+    private static final String TAG = "WifiSelfRecovery";
+
+    /**
+     * Reason codes for the various recovery triggers.
+     */
+    public static final int REASON_LAST_RESORT_WATCHDOG = 0;
+    public static final int REASON_HAL_CRASH = 1;
+    public static final int REASON_WIFICOND_CRASH = 2;
+
+    private static final String[] REASON_STRINGS = {
+            "Last Resort Watchdog", // REASON_LAST_RESORT_WATCHDOG
+            "Hal Crash",            // REASON_HAL_CRASH
+            "Wificond Crash"        // REASON_WIFICOND_CRASH
+    };
+
+    private final WifiController mWifiController;
+
+    SelfRecovery(WifiController wifiController) {
+        mWifiController = wifiController;
+    }
+
+    /**
+     * Trigger recovery.
+     *
+     * This method does the following:
+     * 1. Raises a wtf.
+     * 2. Sends {@link WifiController#CMD_RESTART_WIFI} to {@link WifiController} to initiate the
+     * stack restart.
+     * @param reason One of the above |REASON_*| codes.
+     */
+    public void trigger(int reason) {
+        if (reason < REASON_LAST_RESORT_WATCHDOG || reason > REASON_WIFICOND_CRASH) {
+            Log.e(TAG, "Invalid trigger reason. Ignoring...");
+            return;
+        }
+        Log.wtf(TAG, "Triggering recovery for reason: " + REASON_STRINGS[reason]);
+        mWifiController.sendMessage(WifiController.CMD_RESTART_WIFI);
+    }
+}
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index 2dfb754..64f4a14 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -20,41 +20,49 @@
 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.InterfaceConfiguration;
+import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiManager;
 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;
 import com.android.internal.util.StateMachine;
+import com.android.server.net.BaseNetworkObserver;
 import com.android.server.wifi.util.ApConfigUtil;
 
-import java.util.ArrayList;
+import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 
 /**
  * Manage WiFi in AP mode.
  * The internal state machine runs under "WifiStateMachine" thread context.
  */
-public class SoftApManager {
+public class SoftApManager implements ActiveModeManager {
     private static final String TAG = "SoftApManager";
 
-    private final INetworkManagementService mNmService;
     private final WifiNative mWifiNative;
-    private final ArrayList<Integer> mAllowed2GChannels;
 
     private final String mCountryCode;
 
-    private final String mInterfaceName;
-
     private final SoftApStateMachine mStateMachine;
 
     private final Listener mListener;
 
+    private final IApInterface mApInterface;
+
+    private final INetworkManagementService mNwService;
+    private final WifiApConfigStore mWifiApConfigStore;
+
+    private final WifiMetrics mWifiMetrics;
+
+    private WifiConfiguration mApConfig;
+
     /**
      * Listener for soft AP state changes.
      */
@@ -69,27 +77,34 @@
 
     public SoftApManager(Looper looper,
                          WifiNative wifiNative,
-                         INetworkManagementService nmService,
                          String countryCode,
-                         ArrayList<Integer> allowed2GChannels,
-                         Listener listener) {
+                         Listener listener,
+                         IApInterface apInterface,
+                         INetworkManagementService nms,
+                         WifiApConfigStore wifiApConfigStore,
+                         WifiConfiguration config,
+                         WifiMetrics wifiMetrics) {
         mStateMachine = new SoftApStateMachine(looper);
 
-        mNmService = nmService;
         mWifiNative = wifiNative;
         mCountryCode = countryCode;
-        mAllowed2GChannels = allowed2GChannels;
         mListener = listener;
-
-        mInterfaceName = mWifiNative.getInterfaceName();
+        mApInterface = apInterface;
+        mNwService = nms;
+        mWifiApConfigStore = wifiApConfigStore;
+        if (config == null) {
+            mApConfig = mWifiApConfigStore.getApConfiguration();
+        } else {
+            mApConfig = config;
+        }
+        mWifiMetrics = wifiMetrics;
     }
 
     /**
-     * Start soft AP with given configuration.
-     * @param config AP configuration
+     * Start soft AP with the supplied config.
      */
-    public void start(WifiConfiguration config) {
-        mStateMachine.sendMessage(SoftApStateMachine.CMD_START, config);
+    public void start() {
+        mStateMachine.sendMessage(SoftApStateMachine.CMD_START, mApConfig);
     }
 
     /**
@@ -116,27 +131,26 @@
      * @return integer result code
      */
     private int startSoftAp(WifiConfiguration config) {
-        if (config == null) {
-            Log.e(TAG, "Unable to start soft AP without configuration");
+        if (config == null || config.SSID == null) {
+            Log.e(TAG, "Unable to start soft AP without valid configuration");
             return ERROR_GENERIC;
         }
 
-        /* Make a copy of configuration for updating AP band and channel. */
+        // Make a copy of configuration for updating AP band and channel.
         WifiConfiguration localConfig = new WifiConfiguration(config);
 
         int result = ApConfigUtil.updateApChannelConfig(
-                mWifiNative, mCountryCode, mAllowed2GChannels, localConfig);
+                mWifiNative, mCountryCode,
+                mWifiApConfigStore.getAllowed2GChannel(), localConfig);
         if (result != SUCCESS) {
             Log.e(TAG, "Failed to update AP band and channel");
             return result;
         }
 
-        /* Setup country code if it is provide. */
+        // Setup country code if it is provided.
         if (mCountryCode != null) {
-            /**
-             * Country code is mandatory for 5GHz band, return an error if failed to set
-             * country code when AP is configured for 5GHz band.
-             */
+            // Country code is mandatory for 5GHz band, return an error if failed to set
+            // country code when AP is configured for 5GHz band.
             if (!mWifiNative.setCountryCodeHal(mCountryCode.toUpperCase(Locale.ROOT))
                     && config.apBand == WifiConfiguration.AP_BAND_5GHZ) {
                 Log.e(TAG, "Failed to set country code, required for setting up "
@@ -145,11 +159,30 @@
             }
         }
 
+        int encryptionType = getIApInterfaceEncryptionType(localConfig);
+
         try {
-            mNmService.startAccessPoint(localConfig, mInterfaceName);
-        } catch (Exception e) {
+            // Note that localConfig.SSID is intended to be either a hex string or "double quoted".
+            // However, it seems that whatever is handing us these configurations does not obey
+            // this convention.
+            boolean success = mApInterface.writeHostapdConfig(
+                    localConfig.SSID.getBytes(StandardCharsets.UTF_8), false,
+                    localConfig.apChannel, encryptionType,
+                    (localConfig.preSharedKey != null)
+                            ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8)
+                            : new byte[0]);
+            if (!success) {
+                Log.e(TAG, "Failed to write hostapd configuration");
+                return ERROR_GENERIC;
+            }
+
+            success = mApInterface.startHostapd();
+            if (!success) {
+                Log.e(TAG, "Failed to start hostapd.");
+                return ERROR_GENERIC;
+            }
+        } catch (RemoteException e) {
             Log.e(TAG, "Exception in starting soft AP: " + e);
-            return ERROR_GENERIC;
         }
 
         Log.d(TAG, "Soft AP is started");
@@ -157,13 +190,34 @@
         return SUCCESS;
     }
 
+    private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
+        int encryptionType;
+        switch (localConfig.getAuthType()) {
+            case KeyMgmt.NONE:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+                break;
+            case KeyMgmt.WPA_PSK:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
+                break;
+            case KeyMgmt.WPA2_PSK:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
+                break;
+            default:
+                // We really shouldn't default to None, but this was how NetworkManagementService
+                // used to do this.
+                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+                break;
+        }
+        return encryptionType;
+    }
+
     /**
      * Teardown soft AP.
      */
     private void stopSoftAp() {
         try {
-            mNmService.stopAccessPoint(mInterfaceName);
-        } catch (Exception e) {
+            mApInterface.stopHostapd();
+        } catch (RemoteException e) {
             Log.e(TAG, "Exception in stopping soft AP: " + e);
             return;
         }
@@ -171,18 +225,41 @@
     }
 
     private class SoftApStateMachine extends StateMachine {
-        /* Commands for the state machine. */
+        // 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;
+        public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
 
         private final State mIdleState = new IdleState();
         private final State mStartedState = new StartedState();
 
+        private final StateMachineDeathRecipient mDeathRecipient =
+                new StateMachineDeathRecipient(this, CMD_AP_INTERFACE_BINDER_DEATH);
+
+        private NetworkObserver mNetworkObserver;
+
+        private class NetworkObserver extends BaseNetworkObserver {
+            private final String mIfaceName;
+
+            NetworkObserver(String ifaceName) {
+                mIfaceName = ifaceName;
+            }
+
+            @Override
+            public void interfaceLinkStateChanged(String iface, boolean up) {
+                if (mIfaceName.equals(iface)) {
+                    SoftApStateMachine.this.sendMessage(
+                            CMD_INTERFACE_STATUS_CHANGED, up ? 1 : 0, 0, this);
+                }
+            }
+        }
+
         SoftApStateMachine(Looper looper) {
             super(TAG, looper);
 
             addState(mIdleState);
-            addState(mStartedState, mIdleState);
+            addState(mStartedState);
 
             setInitialState(mIdleState);
             start();
@@ -190,41 +267,126 @@
 
         private class IdleState extends State {
             @Override
-            public boolean processMessage(Message message) {
-                switch (message.what) {
-                    case CMD_START:
-                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
-                        int result = startSoftAp((WifiConfiguration) message.obj);
-                        if (result == SUCCESS) {
-                            updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0);
-                            transitionTo(mStartedState);
-                        } else {
-                            int reason = WifiManager.SAP_START_FAILURE_GENERAL;
-                            if (result == ERROR_NO_CHANNEL) {
-                                reason = WifiManager.SAP_START_FAILURE_NO_CHANNEL;
-                            }
-                            updateApState(WifiManager.WIFI_AP_STATE_FAILED, reason);
-                        }
-                        break;
-                    default:
-                        /* Ignore all other commands. */
-                        break;
-                }
-                return HANDLED;
+            public void enter() {
+                mDeathRecipient.unlinkToDeath();
+                unregisterObserver();
             }
-        }
 
-        private class StartedState extends State {
             @Override
             public boolean processMessage(Message message) {
                 switch (message.what) {
                     case CMD_START:
-                        /* Already started, ignore this command. */
+                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
+                        if (!mDeathRecipient.linkToDeath(mApInterface.asBinder())) {
+                            mDeathRecipient.unlinkToDeath();
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                    WifiManager.SAP_START_FAILURE_GENERAL);
+                            mWifiMetrics.incrementSoftApStartResult(
+                                    false, WifiManager.SAP_START_FAILURE_GENERAL);
+                            break;
+                        }
+
+                        try {
+                            mNetworkObserver = new NetworkObserver(mApInterface.getInterfaceName());
+                            mNwService.registerObserver(mNetworkObserver);
+                        } catch (RemoteException e) {
+                            mDeathRecipient.unlinkToDeath();
+                            unregisterObserver();
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                          WifiManager.SAP_START_FAILURE_GENERAL);
+                            mWifiMetrics.incrementSoftApStartResult(
+                                    false, WifiManager.SAP_START_FAILURE_GENERAL);
+                            break;
+                        }
+
+                        int result = startSoftAp((WifiConfiguration) message.obj);
+                        if (result != SUCCESS) {
+                            int failureReason = WifiManager.SAP_START_FAILURE_GENERAL;
+                            if (result == ERROR_NO_CHANNEL) {
+                                failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL;
+                            }
+                            mDeathRecipient.unlinkToDeath();
+                            unregisterObserver();
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED, failureReason);
+                            mWifiMetrics.incrementSoftApStartResult(false, failureReason);
+                            break;
+                        }
+
+                        transitionTo(mStartedState);
                         break;
+                    default:
+                        // Ignore all other commands.
+                        break;
+                }
+
+                return HANDLED;
+            }
+
+            private void unregisterObserver() {
+                if (mNetworkObserver == null) {
+                    return;
+                }
+                try {
+                    mNwService.unregisterObserver(mNetworkObserver);
+                } catch (RemoteException e) { }
+                mNetworkObserver = null;
+            }
+        }
+
+        private class StartedState extends State {
+            private boolean mIfaceIsUp;
+
+            private void onUpChanged(boolean isUp) {
+                if (isUp == mIfaceIsUp) {
+                    return;  // no change
+                }
+                mIfaceIsUp = isUp;
+                if (isUp) {
+                    Log.d(TAG, "SoftAp is ready for use");
+                    updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0);
+                    mWifiMetrics.incrementSoftApStartResult(true, 0);
+                } else {
+                    // TODO: handle the case where the interface was up, but goes down
+                }
+            }
+
+            @Override
+            public void enter() {
+                mIfaceIsUp = false;
+                InterfaceConfiguration config = null;
+                try {
+                    config = mNwService.getInterfaceConfig(mApInterface.getInterfaceName());
+                } catch (RemoteException e) {
+                }
+                if (config != null) {
+                    onUpChanged(config.isUp());
+                }
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_INTERFACE_STATUS_CHANGED:
+                        if (message.obj != mNetworkObserver) {
+                            // This is from some time before the most recent configuration.
+                            break;
+                        }
+                        boolean isUp = message.arg1 == 1;
+                        onUpChanged(isUp);
+                        break;
+                    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/SoftApModeConfiguration.java b/service/java/com/android/server/wifi/SoftApModeConfiguration.java
new file mode 100644
index 0000000..e880560
--- /dev/null
+++ b/service/java/com/android/server/wifi/SoftApModeConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.wifi.WifiConfiguration;
+
+/**
+ * Object holding the parameters needed to start SoftAp mode.
+ *
+ * Initially, this will hold the WifiConfiguration and mode.
+ */
+public class SoftApModeConfiguration {
+    final int mTargetMode;
+    final WifiConfiguration mConfig;
+
+    SoftApModeConfiguration(int targetMode, WifiConfiguration config) {
+        mTargetMode = targetMode;
+        mConfig = config;
+    }
+
+    public int getTargetMode() {
+        return mTargetMode;
+    }
+
+    public WifiConfiguration getWifiConfiguration() {
+        return mConfig;
+    }
+}
diff --git a/service/java/com/android/server/wifi/StateMachineDeathRecipient.java b/service/java/com/android/server/wifi/StateMachineDeathRecipient.java
new file mode 100644
index 0000000..64c4bee
--- /dev/null
+++ b/service/java/com/android/server/wifi/StateMachineDeathRecipient.java
@@ -0,0 +1,86 @@
+/*
+ * 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.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import com.android.internal.util.StateMachine;
+
+/**
+ * Allows StateMachine instances to subscribe to binder death.
+ *
+ * @hide
+ */
+public class StateMachineDeathRecipient implements DeathRecipient {
+
+    private final StateMachine mStateMachine;
+    private final int mDeathCommand;
+    private IBinder mLinkedBinder;
+
+    /**
+     * Construct a StateMachineDeathRecipient.
+     *
+     * @param sm StateMachine instance to receive a message upon Binder death.
+     * @param command message to send the state machine.
+     */
+    public StateMachineDeathRecipient(StateMachine sm, int command) {
+        mStateMachine = sm;
+        mDeathCommand = command;
+    }
+
+    /**
+     * Listen for the death of a binder.
+     *
+     * This method will unlink from death notifications from any
+     * previously linked IBinder instance.
+     *
+     * @param binder remote object to listen for death.
+     * @return true iff we have successfully subscribed to death notifications of a live
+     *         IBinder instance.
+     */
+    public boolean linkToDeath(IBinder binder) {
+        unlinkToDeath();
+        try {
+            binder.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            // The remote has already died.
+            return false;
+        }
+        mLinkedBinder = binder;
+        return true;
+    }
+
+    /**
+     * Unlink from notifications from the last linked IBinder instance.
+     */
+    public void unlinkToDeath() {
+        if (mLinkedBinder == null) {
+            return;
+        }
+        mLinkedBinder.unlinkToDeath(this, 0);
+        mLinkedBinder = null;
+    }
+
+    /**
+     * Called by the binder subsystem upon remote object death.
+     */
+    @Override
+    public void binderDied() {
+        mStateMachine.sendMessage(mDeathCommand);
+    }
+}
\ No newline at end of file
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
new file mode 100644
index 0000000..e9c20db
--- /dev/null
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -0,0 +1,2065 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQP3GPPNetwork;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPDomName;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPIPAddrAvailability;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPNAIRealm;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPRoamingConsortium;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.ANQPVenueName;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSConnCapability;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSFriendlyName;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSOSUProviders;
+import static com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType.HSWANMetrics;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.wifi.supplicant.V1_0.ISupplicant;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantNetwork;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback.BssidChangeReason;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetwork;
+import android.hardware.wifi.supplicant.V1_0.IfaceType;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
+import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.net.IpConfiguration;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.HwRemoteBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.server.wifi.hotspot2.AnqpEvent;
+import com.android.server.wifi.hotspot2.IconEvent;
+import com.android.server.wifi.hotspot2.WnmData;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.ANQPParser;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.util.NativeUtil;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Hal calls for bring up/shut down of the supplicant daemon and for
+ * sending requests to the supplicant daemon
+ */
+public class SupplicantStaIfaceHal {
+    private static final String TAG = "SupplicantStaIfaceHal";
+    /**
+     * Regex pattern for extracting the wps device type bytes.
+     * Matches a strings like the following: "<categ>-<OUI>-<subcateg>";
+     */
+    private static final Pattern WPS_DEVICE_TYPE_PATTERN =
+            Pattern.compile("^(\\d{1,2})-([0-9a-fA-F]{8})-(\\d{1,2})$");
+
+    private final Object mLock = new Object();
+    private boolean mVerboseLoggingEnabled = false;
+
+    // Supplicant HAL interface objects
+    private IServiceManager mIServiceManager = null;
+    private ISupplicant mISupplicant;
+    private ISupplicantStaIface mISupplicantStaIface;
+    private ISupplicantStaIfaceCallback mISupplicantStaIfaceCallback;
+    private final IServiceNotification mServiceNotificationCallback =
+            new IServiceNotification.Stub() {
+        public void onRegistration(String fqName, String name, boolean preexisting) {
+            synchronized (mLock) {
+                if (mVerboseLoggingEnabled) {
+                    Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName
+                            + ", " + name + " preexisting=" + preexisting);
+                }
+                if (!initSupplicantService() || !initSupplicantStaIface()) {
+                    Log.e(TAG, "initalizing ISupplicantIfaces failed.");
+                    supplicantServiceDiedHandler();
+                } else {
+                    Log.i(TAG, "Completed initialization of ISupplicant interfaces.");
+                }
+            }
+        }
+    };
+    private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
+            cookie -> {
+                Log.w(TAG, "IServiceManager died: cookie=" + cookie);
+                synchronized (mLock) {
+                    supplicantServiceDiedHandler();
+                    mIServiceManager = null; // Will need to register a new ServiceNotification
+                }
+            };
+    private final HwRemoteBinder.DeathRecipient mSupplicantDeathRecipient =
+            cookie -> {
+                Log.w(TAG, "ISupplicant/ISupplicantStaIface died: cookie=" + cookie);
+                synchronized (mLock) {
+                    supplicantServiceDiedHandler();
+                }
+            };
+
+    private String mIfaceName;
+    private SupplicantStaNetworkHal mCurrentNetworkRemoteHandle;
+    private WifiConfiguration mCurrentNetworkLocalConfig;
+    private final Context mContext;
+    private final WifiMonitor mWifiMonitor;
+
+    public SupplicantStaIfaceHal(Context context, WifiMonitor monitor) {
+        mContext = context;
+        mWifiMonitor = monitor;
+        mISupplicantStaIfaceCallback = new SupplicantStaIfaceHalCallback();
+    }
+
+    /**
+     * Enable/Disable verbose logging.
+     *
+     * @param enable true to enable, false to disable.
+     */
+    void enableVerboseLogging(boolean enable) {
+        mVerboseLoggingEnabled = enable;
+    }
+
+    private boolean linkToServiceManagerDeath() {
+        if (mIServiceManager == null) return false;
+        try {
+            if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
+                Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
+                supplicantServiceDiedHandler();
+                mIServiceManager = null; // Will need to register a new ServiceNotification
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "IServiceManager.linkToDeath exception", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Registers a service notification for the ISupplicant service, which triggers intialization of
+     * the ISupplicantStaIface
+     * @return true if the service notification was successfully registered
+     */
+    public boolean initialize() {
+        if (mVerboseLoggingEnabled) Log.i(TAG, "Registering ISupplicant service ready callback.");
+        synchronized (mLock) {
+            mISupplicant = null;
+            mISupplicantStaIface = null;
+            if (mIServiceManager != null) {
+                // Already have an IServiceManager and serviceNotification registered, don't
+                // don't register another.
+                return true;
+            }
+            try {
+                mIServiceManager = getServiceManagerMockable();
+                if (mIServiceManager == null) {
+                    Log.e(TAG, "Failed to get HIDL Service Manager");
+                    return false;
+                }
+                if (!linkToServiceManagerDeath()) {
+                    return false;
+                }
+                /* TODO(b/33639391) : Use the new ISupplicant.registerForNotifications() once it
+                   exists */
+                if (!mIServiceManager.registerForNotifications(
+                        ISupplicant.kInterfaceName, "", mServiceNotificationCallback)) {
+                    Log.e(TAG, "Failed to register for notifications to "
+                            + ISupplicant.kInterfaceName);
+                    mIServiceManager = null; // Will need to register a new ServiceNotification
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception while trying to register a listener for ISupplicant service: "
+                        + e);
+                supplicantServiceDiedHandler();
+            }
+            return true;
+        }
+    }
+
+    private boolean linkToSupplicantDeath() {
+        if (mISupplicant == null) return false;
+        try {
+            if (!mISupplicant.linkToDeath(mSupplicantDeathRecipient, 0)) {
+                Log.wtf(TAG, "Error on linkToDeath on ISupplicant");
+                supplicantServiceDiedHandler();
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "ISupplicant.linkToDeath exception", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean initSupplicantService() {
+        synchronized (mLock) {
+            try {
+                mISupplicant = getSupplicantMockable();
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicant.getService exception: " + e);
+                return false;
+            }
+            if (mISupplicant == null) {
+                Log.e(TAG, "Got null ISupplicant service. Stopping supplicant HIDL startup");
+                return false;
+            }
+            if (!linkToSupplicantDeath()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean linkToSupplicantStaIfaceDeath() {
+        if (mISupplicantStaIface == null) return false;
+        try {
+            if (!mISupplicantStaIface.linkToDeath(mSupplicantDeathRecipient, 0)) {
+                Log.wtf(TAG, "Error on linkToDeath on ISupplicantStaIface");
+                supplicantServiceDiedHandler();
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "ISupplicantStaIface.linkToDeath exception", e);
+            return false;
+        }
+        return true;
+    }
+
+    private int getCurrentNetworkId() {
+        if (mCurrentNetworkLocalConfig == null) {
+            return WifiConfiguration.INVALID_NETWORK_ID;
+        }
+        return mCurrentNetworkLocalConfig.networkId;
+    }
+
+    private boolean initSupplicantStaIface() {
+        synchronized (mLock) {
+            /** List all supplicant Ifaces */
+            final ArrayList<ISupplicant.IfaceInfo> supplicantIfaces = new ArrayList<>();
+            try {
+                mISupplicant.listInterfaces((SupplicantStatus status,
+                        ArrayList<ISupplicant.IfaceInfo> ifaces) -> {
+                    if (status.code != SupplicantStatusCode.SUCCESS) {
+                        Log.e(TAG, "Getting Supplicant Interfaces failed: " + status.code);
+                        return;
+                    }
+                    supplicantIfaces.addAll(ifaces);
+                });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicant.listInterfaces exception: " + e);
+                return false;
+            }
+            if (supplicantIfaces.size() == 0) {
+                Log.e(TAG, "Got zero HIDL supplicant ifaces. Stopping supplicant HIDL startup.");
+                return false;
+            }
+            Mutable<ISupplicantIface> supplicantIface = new Mutable<>();
+            Mutable<String> ifaceName = new Mutable<>();
+            for (ISupplicant.IfaceInfo ifaceInfo : supplicantIfaces) {
+                if (ifaceInfo.type == IfaceType.STA) {
+                    try {
+                        mISupplicant.getInterface(ifaceInfo,
+                                (SupplicantStatus status, ISupplicantIface iface) -> {
+                                if (status.code != SupplicantStatusCode.SUCCESS) {
+                                    Log.e(TAG, "Failed to get ISupplicantIface " + status.code);
+                                    return;
+                                }
+                                supplicantIface.value = iface;
+                            });
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "ISupplicant.getInterface exception: " + e);
+                        return false;
+                    }
+                    ifaceName.value = ifaceInfo.name;
+                    break;
+                }
+            }
+            if (supplicantIface.value == null) {
+                Log.e(TAG, "initSupplicantStaIface got null iface");
+                return false;
+            }
+            mISupplicantStaIface = getStaIfaceMockable(supplicantIface.value);
+            mIfaceName = ifaceName.value;
+            if (!linkToSupplicantStaIfaceDeath()) {
+                return false;
+            }
+            if (!registerCallback(mISupplicantStaIfaceCallback)) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    private void supplicantServiceDiedHandler() {
+        synchronized (mLock) {
+            mISupplicant = null;
+            mISupplicantStaIface = null;
+            mWifiMonitor.broadcastSupplicantDisconnectionEvent(mIfaceName);
+        }
+    }
+
+    /**
+     * Signals whether Initialization completed successfully.
+     */
+    public boolean isInitializationStarted() {
+        return mIServiceManager != null;
+    }
+
+    /**
+     * Signals whether Initialization completed successfully.
+     */
+    public boolean isInitializationComplete() {
+        return mISupplicantStaIface != null;
+    }
+
+    /**
+     * Wrapper functions to access static HAL methods, created to be mockable in unit tests
+     */
+    protected IServiceManager getServiceManagerMockable() throws RemoteException {
+        return IServiceManager.getService();
+    }
+
+    protected ISupplicant getSupplicantMockable() throws RemoteException {
+        return ISupplicant.getService();
+    }
+
+    protected ISupplicantStaIface getStaIfaceMockable(ISupplicantIface iface) {
+        return ISupplicantStaIface.asInterface(iface.asBinder());
+    }
+
+    /**
+     * Add a network configuration to wpa_supplicant.
+     *
+     * @param config Config corresponding to the network.
+     * @return a Pair object including SupplicantStaNetworkHal and WifiConfiguration objects
+     * for the current network.
+     */
+    private Pair<SupplicantStaNetworkHal, WifiConfiguration>
+            addNetworkAndSaveConfig(WifiConfiguration config) {
+        logi("addSupplicantStaNetwork via HIDL");
+        if (config == null) {
+            loge("Cannot add NULL network!");
+            return null;
+        }
+        SupplicantStaNetworkHal network = addNetwork();
+        if (network == null) {
+            loge("Failed to add a network!");
+            return null;
+        }
+        boolean saveSuccess = false;
+        try {
+            saveSuccess = network.saveWifiConfiguration(config);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Exception while saving config params: " + config, e);
+        }
+        if (!saveSuccess) {
+            loge("Failed to save variables for: " + config.configKey());
+            if (!removeAllNetworks()) {
+                loge("Failed to remove all networks on failure.");
+            }
+            return null;
+        }
+        return new Pair(network, new WifiConfiguration(config));
+    }
+
+    /**
+     * Add the provided network configuration to wpa_supplicant and initiate connection to it.
+     * This method does the following:
+     * 1. If |config| is different to the current supplicant network, removes all supplicant
+     * networks and saves |config|.
+     * 2. Select the new network in wpa_supplicant.
+     *
+     * @param config WifiConfiguration parameters for the provided network.
+     * @return {@code true} if it succeeds, {@code false} otherwise
+     */
+    public boolean connectToNetwork(@NonNull WifiConfiguration config) {
+        logd("connectToNetwork " + config.configKey());
+        if (WifiConfigurationUtil.isSameNetwork(config, mCurrentNetworkLocalConfig)) {
+            logd("Network is already saved, will not trigger remove and add operation.");
+        } else {
+            mCurrentNetworkRemoteHandle = null;
+            mCurrentNetworkLocalConfig = null;
+            if (!removeAllNetworks()) {
+                loge("Failed to remove existing networks");
+                return false;
+            }
+            Pair<SupplicantStaNetworkHal, WifiConfiguration> pair = addNetworkAndSaveConfig(config);
+            if (pair == null) {
+                loge("Failed to add/save network configuration: " + config.configKey());
+                return false;
+            }
+            mCurrentNetworkRemoteHandle = pair.first;
+            mCurrentNetworkLocalConfig = pair.second;
+        }
+
+        if (!mCurrentNetworkRemoteHandle.select()) {
+            loge("Failed to select network configuration: " + config.configKey());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Initiates roaming to the already configured network in wpa_supplicant. If the network
+     * configuration provided does not match the already configured network, then this triggers
+     * a new connection attempt (instead of roam).
+     * 1. First check if we're attempting to connect to the same network as we currently have
+     * configured.
+     * 2. Set the new bssid for the network in wpa_supplicant.
+     * 3. Trigger reassociate command to wpa_supplicant.
+     *
+     * @param config WifiConfiguration parameters for the provided network.
+     * @return {@code true} if it succeeds, {@code false} otherwise
+     */
+    public boolean roamToNetwork(WifiConfiguration config) {
+        if (getCurrentNetworkId() != config.networkId) {
+            Log.w(TAG, "Cannot roam to a different network, initiate new connection. "
+                    + "Current network ID: " + getCurrentNetworkId());
+            return connectToNetwork(config);
+        }
+        String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
+        logd("roamToNetwork" + config.configKey() + " (bssid " + bssid + ")");
+        if (!mCurrentNetworkRemoteHandle.setBssid(bssid)) {
+            loge("Failed to set new bssid on network: " + config.configKey());
+            return false;
+        }
+        if (!reassociate()) {
+            loge("Failed to trigger reassociate");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Load all the configured networks from wpa_supplicant.
+     *
+     * @param configs       Map of configuration key to configuration objects corresponding to all
+     *                      the networks.
+     * @param networkExtras Map of extra configuration parameters stored in wpa_supplicant.conf
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean loadNetworks(Map<String, WifiConfiguration> configs,
+                                SparseArray<Map<String, String>> networkExtras) {
+        List<Integer> networkIds = listNetworks();
+        if (networkIds == null) {
+            Log.e(TAG, "Failed to list networks");
+            return false;
+        }
+        for (Integer networkId : networkIds) {
+            SupplicantStaNetworkHal network = getNetwork(networkId);
+            if (network == null) {
+                Log.e(TAG, "Failed to get network with ID: " + networkId);
+                return false;
+            }
+            WifiConfiguration config = new WifiConfiguration();
+            Map<String, String> networkExtra = new HashMap<>();
+            boolean loadSuccess = false;
+            try {
+                loadSuccess = network.loadWifiConfiguration(config, networkExtra);
+            } catch (IllegalArgumentException e) {
+                Log.wtf(TAG, "Exception while loading config params: " + config, e);
+            }
+            if (!loadSuccess) {
+                Log.e(TAG, "Failed to load wifi configuration for network with ID: " + networkId
+                        + ". Skipping...");
+                continue;
+            }
+            // Set the default IP assignments.
+            config.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
+            config.setProxySettings(IpConfiguration.ProxySettings.NONE);
+
+            networkExtras.put(networkId, networkExtra);
+            String configKey = networkExtra.get(SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY);
+            final WifiConfiguration duplicateConfig = configs.put(configKey, config);
+            if (duplicateConfig != null) {
+                // The network is already known. Overwrite the duplicate entry.
+                Log.i(TAG, "Replacing duplicate network: " + duplicateConfig.networkId);
+                removeNetwork(duplicateConfig.networkId);
+                networkExtras.remove(duplicateConfig.networkId);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Remove the request |networkId| from supplicant if it's the current network,
+     * if the current configured network matches |networkId|.
+     *
+     * @param networkId network id of the network to be removed from supplicant.
+     */
+    public void removeNetworkIfCurrent(int networkId) {
+        synchronized (mLock) {
+            if (getCurrentNetworkId() == networkId) {
+                // Currently we only save 1 network in supplicant.
+                removeAllNetworks();
+            }
+        }
+    }
+
+    /**
+     * Remove all networks from supplicant
+     */
+    public boolean removeAllNetworks() {
+        synchronized (mLock) {
+            ArrayList<Integer> networks = listNetworks();
+            if (networks == null) {
+                Log.e(TAG, "removeAllNetworks failed, got null networks");
+                return false;
+            }
+            for (int id : networks) {
+                if (!removeNetwork(id)) {
+                    Log.e(TAG, "removeAllNetworks failed to remove network: " + id);
+                    return false;
+                }
+            }
+        }
+        // Reset current network info.  Probably not needed once we add support to remove/reset
+        // current network on receiving disconnection event from supplicant (b/32898136).
+        mCurrentNetworkLocalConfig = null;
+        mCurrentNetworkRemoteHandle = null;
+        return true;
+    }
+
+    /**
+     * Set the currently configured network's bssid.
+     *
+     * @param bssidStr Bssid to set in the form of "XX:XX:XX:XX:XX:XX"
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean setCurrentNetworkBssid(String bssidStr) {
+        if (mCurrentNetworkRemoteHandle == null) return false;
+        return mCurrentNetworkRemoteHandle.setBssid(bssidStr);
+    }
+
+    /**
+     * Get the currently configured network's WPS NFC token.
+     *
+     * @return Hex string corresponding to the WPS NFC token.
+     */
+    public String getCurrentNetworkWpsNfcConfigurationToken() {
+        if (mCurrentNetworkRemoteHandle == null) return null;
+        return mCurrentNetworkRemoteHandle.getWpsNfcConfigurationToken();
+    }
+
+    /**
+     * Get the eap anonymous identity for the currently configured network.
+     *
+     * @return anonymous identity string if succeeds, null otherwise.
+     */
+    public String getCurrentNetworkEapAnonymousIdentity() {
+        if (mCurrentNetworkRemoteHandle == null) return null;
+        return mCurrentNetworkRemoteHandle.fetchEapAnonymousIdentity();
+    }
+
+    /**
+     * Send the eap identity response for the currently configured network.
+     *
+     * @param identityStr String to send.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendCurrentNetworkEapIdentityResponse(String identityStr) {
+        if (mCurrentNetworkRemoteHandle == null) return false;
+        return mCurrentNetworkRemoteHandle.sendNetworkEapIdentityResponse(identityStr);
+    }
+
+    /**
+     * Send the eap sim gsm auth response for the currently configured network.
+     *
+     * @param paramsStr String to send.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendCurrentNetworkEapSimGsmAuthResponse(String paramsStr) {
+        if (mCurrentNetworkRemoteHandle == null) return false;
+        return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthResponse(paramsStr);
+    }
+
+    /**
+     * Send the eap sim gsm auth failure for the currently configured network.
+     *
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendCurrentNetworkEapSimGsmAuthFailure() {
+        if (mCurrentNetworkRemoteHandle == null) return false;
+        return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthFailure();
+    }
+
+    /**
+     * Send the eap sim umts auth response for the currently configured network.
+     *
+     * @param paramsStr String to send.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendCurrentNetworkEapSimUmtsAuthResponse(String paramsStr) {
+        if (mCurrentNetworkRemoteHandle == null) return false;
+        return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthResponse(paramsStr);
+    }
+
+    /**
+     * Send the eap sim umts auts response for the currently configured network.
+     *
+     * @param paramsStr String to send.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendCurrentNetworkEapSimUmtsAutsResponse(String paramsStr) {
+        if (mCurrentNetworkRemoteHandle == null) return false;
+        return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAutsResponse(paramsStr);
+    }
+
+    /**
+     * Send the eap sim umts auth failure for the currently configured network.
+     *
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendCurrentNetworkEapSimUmtsAuthFailure() {
+        if (mCurrentNetworkRemoteHandle == null) return false;
+        return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthFailure();
+    }
+
+    /**
+     * Adds a new network.
+     *
+     * @return The ISupplicantNetwork object for the new network, or null if the call fails
+     */
+    private SupplicantStaNetworkHal addNetwork() {
+        synchronized (mLock) {
+            final String methodStr = "addNetwork";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            Mutable<ISupplicantNetwork> newNetwork = new Mutable<>();
+            try {
+                mISupplicantStaIface.addNetwork((SupplicantStatus status,
+                        ISupplicantNetwork network) -> {
+                    if (checkStatusAndLogFailure(status, methodStr)) {
+                        newNetwork.value = network;
+                    }
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+            }
+            if (newNetwork.value != null) {
+                return getStaNetworkMockable(
+                        ISupplicantStaNetwork.asInterface(newNetwork.value.asBinder()));
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Remove network from supplicant with network Id
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    private boolean removeNetwork(int id) {
+        synchronized (mLock) {
+            final String methodStr = "removeNetwork";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.removeNetwork(id);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Use this to mock the creation of SupplicantStaNetworkHal instance.
+     *
+     * @param iSupplicantStaNetwork ISupplicantStaNetwork instance retrieved from HIDL.
+     * @return The ISupplicantNetwork object for the given SupplicantNetworkId int, returns null if
+     * the call fails
+     */
+    protected SupplicantStaNetworkHal getStaNetworkMockable(
+            ISupplicantStaNetwork iSupplicantStaNetwork) {
+        SupplicantStaNetworkHal network =
+                new SupplicantStaNetworkHal(iSupplicantStaNetwork, mIfaceName, mContext,
+                        mWifiMonitor);
+        if (network != null) {
+            network.enableVerboseLogging(mVerboseLoggingEnabled);
+        }
+        return network;
+    }
+
+    /**
+     * @return The ISupplicantNetwork object for the given SupplicantNetworkId int, returns null if
+     * the call fails
+     */
+    private SupplicantStaNetworkHal getNetwork(int id) {
+        synchronized (mLock) {
+            final String methodStr = "getNetwork";
+            Mutable<ISupplicantNetwork> gotNetwork = new Mutable<>();
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            try {
+                mISupplicantStaIface.getNetwork(id, (SupplicantStatus status,
+                        ISupplicantNetwork network) -> {
+                    if (checkStatusAndLogFailure(status, methodStr)) {
+                        gotNetwork.value = network;
+                    }
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+            }
+            if (gotNetwork.value != null) {
+                return getStaNetworkMockable(
+                        ISupplicantStaNetwork.asInterface(gotNetwork.value.asBinder()));
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean registerCallback(ISupplicantStaIfaceCallback callback) {
+        synchronized (mLock) {
+            final String methodStr = "registerCallback";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaIface.registerCallback(callback);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @return a list of SupplicantNetworkID ints for all networks controlled by supplicant, returns
+     * null if the call fails
+     */
+    private java.util.ArrayList<Integer> listNetworks() {
+        synchronized (mLock) {
+            final String methodStr = "listNetworks";
+            Mutable<ArrayList<Integer>> networkIdList = new Mutable<>();
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            try {
+                mISupplicantStaIface.listNetworks((SupplicantStatus status,
+                        java.util.ArrayList<Integer> networkIds) -> {
+                    if (checkStatusAndLogFailure(status, methodStr)) {
+                        networkIdList.value = networkIds;
+                    }
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+            }
+            return networkIdList.value;
+        }
+    }
+
+    /**
+     * Set WPS device name.
+     *
+     * @param name String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsDeviceName(String name) {
+        synchronized (mLock) {
+            final String methodStr = "setWpsDeviceName";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setWpsDeviceName(name);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set WPS device type.
+     *
+     * @param typeStr Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsDeviceType(String typeStr) {
+        try {
+            Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr);
+            if (!match.find() || match.groupCount() != 3) {
+                Log.e(TAG, "Malformed WPS device type " + typeStr);
+                return false;
+            }
+            short categ = Short.parseShort(match.group(1));
+            byte[] oui = NativeUtil.hexStringToByteArray(match.group(2));
+            short subCateg = Short.parseShort(match.group(3));
+
+            byte[] bytes = new byte[8];
+            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+            byteBuffer.putShort(categ);
+            byteBuffer.put(oui);
+            byteBuffer.putShort(subCateg);
+            return setWpsDeviceType(bytes);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + typeStr, e);
+            return false;
+        }
+    }
+
+    private boolean setWpsDeviceType(byte[/* 8 */] type) {
+        synchronized (mLock) {
+            final String methodStr = "setWpsDeviceType";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setWpsDeviceType(type);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set WPS manufacturer.
+     *
+     * @param manufacturer String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsManufacturer(String manufacturer) {
+        synchronized (mLock) {
+            final String methodStr = "setWpsManufacturer";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setWpsManufacturer(manufacturer);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set WPS model name.
+     *
+     * @param modelName String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsModelName(String modelName) {
+        synchronized (mLock) {
+            final String methodStr = "setWpsModelName";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setWpsModelName(modelName);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set WPS model number.
+     *
+     * @param modelNumber String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsModelNumber(String modelNumber) {
+        synchronized (mLock) {
+            final String methodStr = "setWpsModelNumber";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setWpsModelNumber(modelNumber);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set WPS serial number.
+     *
+     * @param serialNumber String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsSerialNumber(String serialNumber) {
+        synchronized (mLock) {
+            final String methodStr = "setWpsSerialNumber";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setWpsSerialNumber(serialNumber);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set WPS config methods
+     *
+     * @param configMethodsStr List of config methods.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsConfigMethods(String configMethodsStr) {
+        short configMethodsMask = 0;
+        String[] configMethodsStrArr = configMethodsStr.split("\\s+");
+        for (int i = 0; i < configMethodsStrArr.length; i++) {
+            configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]);
+        }
+        return setWpsConfigMethods(configMethodsMask);
+    }
+
+    private boolean setWpsConfigMethods(short configMethods) {
+        synchronized (mLock) {
+            final String methodStr = "setWpsConfigMethods";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setWpsConfigMethods(configMethods);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Trigger a reassociation even if the iface is currently connected.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean reassociate() {
+        synchronized (mLock) {
+            final String methodStr = "reassociate";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.reassociate();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Trigger a reconnection if the iface is disconnected.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean reconnect() {
+        synchronized (mLock) {
+            final String methodStr = "reconnect";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.reconnect();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Trigger a disconnection from the currently connected network.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean disconnect() {
+        synchronized (mLock) {
+            final String methodStr = "disconnect";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.disconnect();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Enable or disable power save mode.
+     *
+     * @param enable true to enable, false to disable.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setPowerSave(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "setPowerSave";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setPowerSave(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Initiate TDLS discover with the specified AP.
+     *
+     * @param macAddress MAC Address of the AP.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean initiateTdlsDiscover(String macAddress) {
+        try {
+            return initiateTdlsDiscover(NativeUtil.macAddressToByteArray(macAddress));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + macAddress, e);
+            return false;
+        }
+    }
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean initiateTdlsDiscover(byte[/* 6 */] macAddress) {
+        synchronized (mLock) {
+            final String methodStr = "initiateTdlsDiscover";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.initiateTdlsDiscover(macAddress);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Initiate TDLS setup with the specified AP.
+     *
+     * @param macAddress MAC Address of the AP.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean initiateTdlsSetup(String macAddress) {
+        try {
+            return initiateTdlsSetup(NativeUtil.macAddressToByteArray(macAddress));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + macAddress, e);
+            return false;
+        }
+    }
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean initiateTdlsSetup(byte[/* 6 */] macAddress) {
+        synchronized (mLock) {
+            final String methodStr = "initiateTdlsSetup";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.initiateTdlsSetup(macAddress);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Initiate TDLS teardown with the specified AP.
+     * @param macAddress MAC Address of the AP.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean initiateTdlsTeardown(String macAddress) {
+        try {
+            return initiateTdlsTeardown(NativeUtil.macAddressToByteArray(macAddress));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + macAddress, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean initiateTdlsTeardown(byte[/* 6 */] macAddress) {
+        synchronized (mLock) {
+            final String methodStr = "initiateTdlsTeardown";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.initiateTdlsTeardown(macAddress);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Request the specified ANQP elements |elements| from the specified AP |bssid|.
+     *
+     * @param bssid BSSID of the AP
+     * @param infoElements ANQP elements to be queried. Refer to ISupplicantStaIface.AnqpInfoId.
+     * @param hs20SubTypes HS subtypes to be queried. Refer to ISupplicantStaIface.Hs20AnqpSubTypes.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean initiateAnqpQuery(String bssid, ArrayList<Short> infoElements,
+                                     ArrayList<Integer> hs20SubTypes) {
+        try {
+            return initiateAnqpQuery(
+                    NativeUtil.macAddressToByteArray(bssid), infoElements, hs20SubTypes);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + bssid, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean initiateAnqpQuery(byte[/* 6 */] macAddress,
+            java.util.ArrayList<Short> infoElements, java.util.ArrayList<Integer> subTypes) {
+        synchronized (mLock) {
+            final String methodStr = "initiateAnqpQuery";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.initiateAnqpQuery(macAddress,
+                        infoElements, subTypes);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Request the specified ANQP ICON from the specified AP |bssid|.
+     *
+     * @param bssid BSSID of the AP
+     * @param fileName Name of the file to request.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean initiateHs20IconQuery(String bssid, String fileName) {
+        try {
+            return initiateHs20IconQuery(NativeUtil.macAddressToByteArray(bssid), fileName);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + bssid, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean initiateHs20IconQuery(byte[/* 6 */] macAddress, String fileName) {
+        synchronized (mLock) {
+            final String methodStr = "initiateHs20IconQuery";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.initiateHs20IconQuery(macAddress,
+                        fileName);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Makes a callback to HIDL to getMacAddress from supplicant
+     *
+     * @return string containing the MAC address, or null on a failed call
+     */
+    public String getMacAddress() {
+        synchronized (mLock) {
+            final String methodStr = "getMacAddress";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            Mutable<String> gotMac = new Mutable<>();
+            try {
+                mISupplicantStaIface.getMacAddress((SupplicantStatus status,
+                        byte[/* 6 */] macAddr) -> {
+                    if (checkStatusAndLogFailure(status, methodStr)) {
+                        gotMac.value = NativeUtil.macAddressFromByteArray(macAddr);
+                    }
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+            }
+            return gotMac.value;
+        }
+    }
+
+    /**
+     * Start using the added RX filters.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean startRxFilter() {
+        synchronized (mLock) {
+            final String methodStr = "startRxFilter";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.startRxFilter();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Stop using the added RX filters.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean stopRxFilter() {
+        synchronized (mLock) {
+            final String methodStr = "stopRxFilter";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.stopRxFilter();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Add an RX filter.
+     *
+     * @param type one of {@link WifiNative#RX_FILTER_TYPE_V4_MULTICAST}
+     *        {@link WifiNative#RX_FILTER_TYPE_V6_MULTICAST} values.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean addRxFilter(int type) {
+        byte halType;
+        switch (type) {
+            case WifiNative.RX_FILTER_TYPE_V4_MULTICAST:
+                halType = ISupplicantStaIface.RxFilterType.V4_MULTICAST;
+                break;
+            case WifiNative.RX_FILTER_TYPE_V6_MULTICAST:
+                halType = ISupplicantStaIface.RxFilterType.V6_MULTICAST;
+                break;
+            default:
+                Log.e(TAG, "Invalid Rx Filter type: " + type);
+                return false;
+        }
+        return addRxFilter(halType);
+    }
+
+    public boolean addRxFilter(byte type) {
+        synchronized (mLock) {
+            final String methodStr = "addRxFilter";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.addRxFilter(type);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Remove an RX filter.
+     *
+     * @param type one of {@link WifiNative#RX_FILTER_TYPE_V4_MULTICAST}
+     *        {@link WifiNative#RX_FILTER_TYPE_V6_MULTICAST} values.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean removeRxFilter(int type) {
+        byte halType;
+        switch (type) {
+            case WifiNative.RX_FILTER_TYPE_V4_MULTICAST:
+                halType = ISupplicantStaIface.RxFilterType.V4_MULTICAST;
+                break;
+            case WifiNative.RX_FILTER_TYPE_V6_MULTICAST:
+                halType = ISupplicantStaIface.RxFilterType.V6_MULTICAST;
+                break;
+            default:
+                Log.e(TAG, "Invalid Rx Filter type: " + type);
+                return false;
+        }
+        return removeRxFilter(halType);
+    }
+
+    public boolean removeRxFilter(byte type) {
+        synchronized (mLock) {
+            final String methodStr = "removeRxFilter";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.removeRxFilter(type);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set Bt co existense mode.
+     *
+     * @param mode one of the above {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_DISABLED},
+     *             {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_ENABLED} or
+     *             {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_SENSE}.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setBtCoexistenceMode(int mode) {
+        byte halMode;
+        switch (mode) {
+            case WifiNative.BLUETOOTH_COEXISTENCE_MODE_ENABLED:
+                halMode = ISupplicantStaIface.BtCoexistenceMode.ENABLED;
+                break;
+            case WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED:
+                halMode = ISupplicantStaIface.BtCoexistenceMode.DISABLED;
+                break;
+            case WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE:
+                halMode = ISupplicantStaIface.BtCoexistenceMode.SENSE;
+                break;
+            default:
+                Log.e(TAG, "Invalid Bt Coex mode: " + mode);
+                return false;
+        }
+        return setBtCoexistenceMode(halMode);
+    }
+
+    private boolean setBtCoexistenceMode(byte mode) {
+        synchronized (mLock) {
+            final String methodStr = "setBtCoexistenceMode";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setBtCoexistenceMode(mode);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /** Enable or disable BT coexistence mode.
+     *
+     * @param enable true to enable, false to disable.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setBtCoexistenceScanModeEnabled(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "setBtCoexistenceScanModeEnabled";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =
+                        mISupplicantStaIface.setBtCoexistenceScanModeEnabled(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Enable or disable suspend mode optimizations.
+     *
+     * @param enable true to enable, false otherwise.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setSuspendModeEnabled(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "setSuspendModeEnabled";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setSuspendModeEnabled(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set country code.
+     *
+     * @param codeStr 2 byte ASCII string. For ex: US, CA.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setCountryCode(String codeStr) {
+        if (TextUtils.isEmpty(codeStr)) return false;
+        return setCountryCode(NativeUtil.stringToByteArray(codeStr));
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean setCountryCode(byte[/* 2 */] code) {
+        synchronized (mLock) {
+            final String methodStr = "setCountryCode";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setCountryCode(code);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Start WPS pin registrar operation with the specified peer and pin.
+     *
+     * @param bssidStr BSSID of the peer.
+     * @param pin Pin to be used.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean startWpsRegistrar(String bssidStr, String pin) {
+        if (TextUtils.isEmpty(bssidStr) || TextUtils.isEmpty(pin)) return false;
+        try {
+            return startWpsRegistrar(NativeUtil.macAddressToByteArray(bssidStr), pin);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + bssidStr, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean startWpsRegistrar(byte[/* 6 */] bssid, String pin) {
+        synchronized (mLock) {
+            final String methodStr = "startWpsRegistrar";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.startWpsRegistrar(bssid, pin);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Start WPS pin display operation with the specified peer.
+     *
+     * @param bssidStr BSSID of the peer. Use empty bssid to indicate wildcard.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean startWpsPbc(String bssidStr) {
+        try {
+            return startWpsPbc(NativeUtil.macAddressToByteArray(bssidStr));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + bssidStr, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private boolean startWpsPbc(byte[/* 6 */] bssid) {
+        synchronized (mLock) {
+            final String methodStr = "startWpsPbc";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.startWpsPbc(bssid);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Start WPS pin keypad operation with the specified pin.
+     *
+     * @param pin Pin to be used.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean startWpsPinKeypad(String pin) {
+        if (TextUtils.isEmpty(pin)) return false;
+        synchronized (mLock) {
+            final String methodStr = "startWpsPinKeypad";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.startWpsPinKeypad(pin);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Start WPS pin display operation with the specified peer.
+     *
+     * @param bssidStr BSSID of the peer. Use empty bssid to indicate wildcard.
+     * @return new pin generated on success, null otherwise.
+     */
+    public String startWpsPinDisplay(String bssidStr) {
+        try {
+            return startWpsPinDisplay(NativeUtil.macAddressToByteArray(bssidStr));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + bssidStr, e);
+            return null;
+        }
+    }
+
+    /** See ISupplicantStaIface.hal for documentation */
+    private String startWpsPinDisplay(byte[/* 6 */] bssid) {
+        synchronized (mLock) {
+            final String methodStr = "startWpsPinDisplay";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            final Mutable<String> gotPin = new Mutable<>();
+            try {
+                mISupplicantStaIface.startWpsPinDisplay(bssid,
+                        (SupplicantStatus status, String pin) -> {
+                            if (checkStatusAndLogFailure(status, methodStr)) {
+                                gotPin.value = pin;
+                            }
+                        });
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+            }
+            return gotPin.value;
+        }
+    }
+
+    /**
+     * Cancels any ongoing WPS requests.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean cancelWps() {
+        synchronized (mLock) {
+            final String methodStr = "cancelWps";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.cancelWps();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Sets whether to use external sim for SIM/USIM processing.
+     *
+     * @param useExternalSim true to enable, false otherwise.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setExternalSim(boolean useExternalSim) {
+        synchronized (mLock) {
+            final String methodStr = "setExternalSim";
+            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.setExternalSim(useExternalSim);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /** See ISupplicant.hal for documentation */
+    public boolean enableAutoReconnect(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "enableAutoReconnect";
+            if (!checkSupplicantAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaIface.enableAutoReconnect(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set the debug log level for wpa_supplicant
+     *
+     * @param turnOnVerbose Whether to turn on verbose logging or not.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setLogLevel(boolean turnOnVerbose) {
+        int logLevel = turnOnVerbose
+                ? ISupplicant.DebugLevel.DEBUG
+                : ISupplicant.DebugLevel.INFO;
+        return setDebugParams(logLevel, false, false);
+    }
+
+    /** See ISupplicant.hal for documentation */
+    private boolean setDebugParams(int level, boolean showTimestamp, boolean showKeys) {
+        synchronized (mLock) {
+            final String methodStr = "setDebugParams";
+            if (!checkSupplicantAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =
+                        mISupplicant.setDebugParams(level, showTimestamp, showKeys);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set concurrency priority between P2P & STA operations.
+     *
+     * @param isStaHigherPriority Set to true to prefer STA over P2P during concurrency operations,
+     *                            false otherwise.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setConcurrencyPriority(boolean isStaHigherPriority) {
+        if (isStaHigherPriority) {
+            return setConcurrencyPriority(IfaceType.STA);
+        } else {
+            return setConcurrencyPriority(IfaceType.P2P);
+        }
+    }
+
+    /** See ISupplicant.hal for documentation */
+    private boolean setConcurrencyPriority(int type) {
+        synchronized (mLock) {
+            final String methodStr = "setConcurrencyPriority";
+            if (!checkSupplicantAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicant.setConcurrencyPriority(type);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Returns false if Supplicant is null, and logs failure to call methodStr
+     */
+    private boolean checkSupplicantAndLogFailure(final String methodStr) {
+        if (mISupplicant == null) {
+            Log.e(TAG, "Can't call " + methodStr + ", ISupplicant is null");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns false if SupplicantStaIface is null, and logs failure to call methodStr
+     */
+    private boolean checkSupplicantStaIfaceAndLogFailure(final String methodStr) {
+        if (mISupplicantStaIface == null) {
+            Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaIface is null");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if provided status code is SUCCESS, logs debug message and returns false
+     * otherwise
+     */
+    private boolean checkStatusAndLogFailure(SupplicantStatus status,
+            final String methodStr) {
+        if (status.code != SupplicantStatusCode.SUCCESS) {
+            Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed: "
+                    + supplicantStatusCodeToString(status.code) + ", " + status.debugMessage);
+            return false;
+        } else {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "ISupplicantStaIface." + methodStr + " succeeded");
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Helper function to log callbacks.
+     */
+    private void logCallback(final String methodStr) {
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "ISupplicantStaIfaceCallback." + methodStr + " received");
+        }
+    }
+
+
+    private void handleRemoteException(RemoteException e, String methodStr) {
+        supplicantServiceDiedHandler();
+        Log.e(TAG, "ISupplicantStaIface." + methodStr + " failed with exception", e);
+    }
+
+    /**
+     * Converts SupplicantStatus code values to strings for debug logging
+     * TODO(b/34811152) Remove this, or make it more break resistance
+     */
+    public static String supplicantStatusCodeToString(int code) {
+        switch (code) {
+            case 0:
+                return "SUCCESS";
+            case 1:
+                return "FAILURE_UNKNOWN";
+            case 2:
+                return "FAILURE_ARGS_INVALID";
+            case 3:
+                return "FAILURE_IFACE_INVALID";
+            case 4:
+                return "FAILURE_IFACE_UNKNOWN";
+            case 5:
+                return "FAILURE_IFACE_EXISTS";
+            case 6:
+                return "FAILURE_IFACE_DISABLED";
+            case 7:
+                return "FAILURE_IFACE_NOT_DISCONNECTED";
+            case 8:
+                return "FAILURE_NETWORK_INVALID";
+            case 9:
+                return "FAILURE_NETWORK_UNKNOWN";
+            default:
+                return "??? UNKNOWN_CODE";
+        }
+    }
+
+
+    /**
+     * Converts the Wps config method string to the equivalent enum value.
+     */
+    private static short stringToWpsConfigMethod(String configMethod) {
+        switch (configMethod) {
+            case "usba":
+                return WpsConfigMethods.USBA;
+            case "ethernet":
+                return WpsConfigMethods.ETHERNET;
+            case "label":
+                return WpsConfigMethods.LABEL;
+            case "display":
+                return WpsConfigMethods.DISPLAY;
+            case "int_nfc_token":
+                return WpsConfigMethods.INT_NFC_TOKEN;
+            case "ext_nfc_token":
+                return WpsConfigMethods.EXT_NFC_TOKEN;
+            case "nfc_interface":
+                return WpsConfigMethods.NFC_INTERFACE;
+            case "push_button":
+                return WpsConfigMethods.PUSHBUTTON;
+            case "keypad":
+                return WpsConfigMethods.KEYPAD;
+            case "virtual_push_button":
+                return WpsConfigMethods.VIRT_PUSHBUTTON;
+            case "physical_push_button":
+                return WpsConfigMethods.PHY_PUSHBUTTON;
+            case "p2ps":
+                return WpsConfigMethods.P2PS;
+            case "virtual_display":
+                return WpsConfigMethods.VIRT_DISPLAY;
+            case "physical_display":
+                return WpsConfigMethods.PHY_DISPLAY;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid WPS config method: " + configMethod);
+        }
+    }
+
+    /**
+     * Converts the supplicant state received from HIDL to the equivalent framework state.
+     */
+    private static SupplicantState supplicantHidlStateToFrameworkState(int state) {
+        switch (state) {
+            case ISupplicantStaIfaceCallback.State.DISCONNECTED:
+                return SupplicantState.DISCONNECTED;
+            case ISupplicantStaIfaceCallback.State.IFACE_DISABLED:
+                return SupplicantState.INTERFACE_DISABLED;
+            case ISupplicantStaIfaceCallback.State.INACTIVE:
+                return SupplicantState.INACTIVE;
+            case ISupplicantStaIfaceCallback.State.SCANNING:
+                return SupplicantState.SCANNING;
+            case ISupplicantStaIfaceCallback.State.AUTHENTICATING:
+                return SupplicantState.AUTHENTICATING;
+            case ISupplicantStaIfaceCallback.State.ASSOCIATING:
+                return SupplicantState.ASSOCIATING;
+            case ISupplicantStaIfaceCallback.State.ASSOCIATED:
+                return SupplicantState.ASSOCIATED;
+            case ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE:
+                return SupplicantState.FOUR_WAY_HANDSHAKE;
+            case ISupplicantStaIfaceCallback.State.GROUP_HANDSHAKE:
+                return SupplicantState.GROUP_HANDSHAKE;
+            case ISupplicantStaIfaceCallback.State.COMPLETED:
+                return SupplicantState.COMPLETED;
+            default:
+                throw new IllegalArgumentException("Invalid state: " + state);
+        }
+    }
+
+    private static class Mutable<E> {
+        public E value;
+
+        Mutable() {
+            value = null;
+        }
+
+        Mutable(E value) {
+            this.value = value;
+        }
+    }
+
+    private class SupplicantStaIfaceHalCallback extends ISupplicantStaIfaceCallback.Stub {
+        private static final int WLAN_REASON_IE_IN_4WAY_DIFFERS = 17; // IEEE 802.11i
+        private boolean mStateIsFourway = false; // Used to help check for PSK password mismatch
+
+        /**
+         * Parses the provided payload into an ANQP element.
+         *
+         * @param infoID  Element type.
+         * @param payload Raw payload bytes.
+         * @return AnqpElement instance on success, null on failure.
+         */
+        private ANQPElement parseAnqpElement(Constants.ANQPElementType infoID,
+                                             ArrayList<Byte> payload) {
+            try {
+                return Constants.getANQPElementID(infoID) != null
+                        ? ANQPParser.parseElement(
+                        infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload)))
+                        : ANQPParser.parseHS20Element(
+                        infoID, ByteBuffer.wrap(NativeUtil.byteArrayFromArrayList(payload)));
+            } catch (IOException | BufferUnderflowException e) {
+                Log.e(TAG, "Failed parsing ANQP element payload: " + infoID, e);
+                return null;
+            }
+        }
+
+        /**
+         * Parse the ANQP element data and add to the provided elements map if successful.
+         *
+         * @param elementsMap Map to add the parsed out element to.
+         * @param infoID  Element type.
+         * @param payload Raw payload bytes.
+         */
+        private void addAnqpElementToMap(Map<Constants.ANQPElementType, ANQPElement> elementsMap,
+                                         Constants.ANQPElementType infoID,
+                                         ArrayList<Byte> payload) {
+            if (payload == null || payload.isEmpty()) return;
+            ANQPElement element = parseAnqpElement(infoID, payload);
+            if (element != null) {
+                elementsMap.put(infoID, element);
+            }
+        }
+
+        @Override
+        public void onNetworkAdded(int id) {
+            logCallback("onNetworkAdded");
+        }
+
+        @Override
+        public void onNetworkRemoved(int id) {
+            logCallback("onNetworkRemoved");
+        }
+
+        @Override
+        public void onStateChanged(int newState, byte[/* 6 */] bssid, int id,
+                                   ArrayList<Byte> ssid) {
+            logCallback("onStateChanged");
+            synchronized (mLock) {
+                SupplicantState newSupplicantState = supplicantHidlStateToFrameworkState(newState);
+                WifiSsid wifiSsid =
+                        WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(ssid));
+                String bssidStr = NativeUtil.macAddressFromByteArray(bssid);
+                mStateIsFourway = (newState == ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE);
+                if (newSupplicantState == SupplicantState.COMPLETED) {
+                    mWifiMonitor.broadcastNetworkConnectionEvent(
+                            mIfaceName, getCurrentNetworkId(), bssidStr);
+                }
+                mWifiMonitor.broadcastSupplicantStateChangeEvent(
+                        mIfaceName, getCurrentNetworkId(), wifiSsid, bssidStr, newSupplicantState);
+            }
+        }
+
+        @Override
+        public void onAnqpQueryDone(byte[/* 6 */] bssid,
+                                    ISupplicantStaIfaceCallback.AnqpData data,
+                                    ISupplicantStaIfaceCallback.Hs20AnqpData hs20Data) {
+            logCallback("onAnqpQueryDone");
+            synchronized (mLock) {
+                Map<Constants.ANQPElementType, ANQPElement> elementsMap = new HashMap<>();
+                addAnqpElementToMap(elementsMap, ANQPVenueName, data.venueName);
+                addAnqpElementToMap(elementsMap, ANQPRoamingConsortium, data.roamingConsortium);
+                addAnqpElementToMap(
+                        elementsMap, ANQPIPAddrAvailability, data.ipAddrTypeAvailability);
+                addAnqpElementToMap(elementsMap, ANQPNAIRealm, data.naiRealm);
+                addAnqpElementToMap(elementsMap, ANQP3GPPNetwork, data.anqp3gppCellularNetwork);
+                addAnqpElementToMap(elementsMap, ANQPDomName, data.domainName);
+                addAnqpElementToMap(elementsMap, HSFriendlyName, hs20Data.operatorFriendlyName);
+                addAnqpElementToMap(elementsMap, HSWANMetrics, hs20Data.wanMetrics);
+                addAnqpElementToMap(elementsMap, HSConnCapability, hs20Data.connectionCapability);
+                addAnqpElementToMap(elementsMap, HSOSUProviders, hs20Data.osuProvidersList);
+                mWifiMonitor.broadcastAnqpDoneEvent(
+                        mIfaceName, new AnqpEvent(NativeUtil.macAddressToLong(bssid), elementsMap));
+            }
+        }
+
+        @Override
+        public void onHs20IconQueryDone(byte[/* 6 */] bssid, String fileName,
+                                        ArrayList<Byte> data) {
+            logCallback("onHs20IconQueryDone");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastIconDoneEvent(
+                        mIfaceName,
+                        new IconEvent(NativeUtil.macAddressToLong(bssid), fileName, data.size(),
+                                NativeUtil.byteArrayFromArrayList(data)));
+            }
+        }
+
+        @Override
+        public void onHs20SubscriptionRemediation(byte[/* 6 */] bssid, byte osuMethod, String url) {
+            logCallback("onHs20SubscriptionRemediation");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastWnmEvent(
+                        mIfaceName,
+                        new WnmData(NativeUtil.macAddressToLong(bssid), url, osuMethod));
+            }
+        }
+
+        @Override
+        public void onHs20DeauthImminentNotice(byte[/* 6 */] bssid, int reasonCode,
+                                               int reAuthDelayInSec, String url) {
+            logCallback("onHs20DeauthImminentNotice");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastWnmEvent(
+                        mIfaceName,
+                        new WnmData(NativeUtil.macAddressToLong(bssid), url,
+                                reasonCode == WnmData.ESS, reAuthDelayInSec));
+            }
+        }
+
+        @Override
+        public void onDisconnected(byte[/* 6 */] bssid, boolean locallyGenerated, int reasonCode) {
+            logCallback("onDisconnected");
+            synchronized (mLock) {
+                if (mVerboseLoggingEnabled) {
+                    Log.e(TAG, "onDisconnected 4way=" + mStateIsFourway
+                            + " locallyGenerated=" + locallyGenerated
+                            + " reasonCode=" + reasonCode);
+                }
+                if (mStateIsFourway
+                        && (!locallyGenerated || reasonCode != WLAN_REASON_IE_IN_4WAY_DIFFERS)) {
+                    mWifiMonitor.broadcastAuthenticationFailureEvent(
+                            mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+                }
+                mWifiMonitor.broadcastNetworkDisconnectionEvent(
+                        mIfaceName, locallyGenerated ? 1 : 0, reasonCode,
+                        NativeUtil.macAddressFromByteArray(bssid));
+            }
+        }
+
+        @Override
+        public void onAssociationRejected(byte[/* 6 */] bssid, int statusCode, boolean timedOut) {
+            logCallback("onAssociationRejected");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastAssociationRejectionEvent(mIfaceName, statusCode, timedOut,
+                        NativeUtil.macAddressFromByteArray(bssid));
+            }
+        }
+
+        @Override
+        public void onAuthenticationTimeout(byte[/* 6 */] bssid) {
+            logCallback("onAuthenticationTimeout");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastAuthenticationFailureEvent(
+                        mIfaceName, WifiManager.ERROR_AUTH_FAILURE_TIMEOUT);
+            }
+        }
+
+        @Override
+        public void onBssidChanged(byte reason, byte[/* 6 */] bssid) {
+            logCallback("onBssidChanged");
+            synchronized (mLock) {
+                if (reason == BssidChangeReason.ASSOC_START) {
+                    mWifiMonitor.broadcastTargetBssidEvent(
+                            mIfaceName, NativeUtil.macAddressFromByteArray(bssid));
+                } else if (reason == BssidChangeReason.ASSOC_COMPLETE) {
+                    mWifiMonitor.broadcastAssociatedBssidEvent(
+                            mIfaceName, NativeUtil.macAddressFromByteArray(bssid));
+                }
+            }
+        }
+
+        @Override
+        public void onEapFailure() {
+            logCallback("onEapFailure");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastAuthenticationFailureEvent(
+                        mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE);
+            }
+        }
+
+        @Override
+        public void onWpsEventSuccess() {
+            logCallback("onWpsEventSuccess");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastWpsSuccessEvent(mIfaceName);
+            }
+        }
+
+        @Override
+        public void onWpsEventFail(byte[/* 6 */] bssid, short configError, short errorInd) {
+            logCallback("onWpsEventFail");
+            synchronized (mLock) {
+                if (configError == WpsConfigError.MSG_TIMEOUT
+                        && errorInd == WpsErrorIndication.NO_ERROR) {
+                    mWifiMonitor.broadcastWpsTimeoutEvent(mIfaceName);
+                } else {
+                    mWifiMonitor.broadcastWpsFailEvent(mIfaceName, configError, errorInd);
+                }
+            }
+        }
+
+        @Override
+        public void onWpsEventPbcOverlap() {
+            logCallback("onWpsEventPbcOverlap");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastWpsOverlapEvent(mIfaceName);
+            }
+        }
+
+        @Override
+        public void onExtRadioWorkStart(int id) {
+            logCallback("onExtRadioWorkStart");
+        }
+
+        @Override
+        public void onExtRadioWorkTimeout(int id) {
+            logCallback("onExtRadioWorkTimeout");
+        }
+    }
+
+    private void logd(String s) {
+        Log.d(TAG, s);
+    }
+
+    private void logi(String s) {
+        Log.i(TAG, s);
+    }
+
+    private void loge(String s) {
+        Log.e(TAG, s);
+    }
+}
diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
new file mode 100644
index 0000000..6e7d98c
--- /dev/null
+++ b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
@@ -0,0 +1,2533 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetwork;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetworkCallback;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.MutableBoolean;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.wifi.util.NativeUtil;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * Wrapper class for ISupplicantStaNetwork HAL calls. Gets and sets supplicant sta network variables
+ * and interacts with networks.
+ * Public fields should be treated as invalid until their 'get' method is called, which will set the
+ * value if it returns true
+ */
+public class SupplicantStaNetworkHal {
+    private static final String TAG = "SupplicantStaNetworkHal";
+    @VisibleForTesting
+    public static final String ID_STRING_KEY_FQDN = "fqdn";
+    @VisibleForTesting
+    public static final String ID_STRING_KEY_CREATOR_UID = "creatorUid";
+    @VisibleForTesting
+    public static final String ID_STRING_KEY_CONFIG_KEY = "configKey";
+
+    /**
+     * Regex pattern for extracting the GSM sim authentication response params from a string.
+     * Matches a strings like the following: "[:<kc_value>:<sres_value>]";
+     */
+    private static final Pattern GSM_AUTH_RESPONSE_PARAMS_PATTERN =
+            Pattern.compile(":([0-9a-fA-F]+):([0-9a-fA-F]+)");
+    /**
+     * Regex pattern for extracting the UMTS sim authentication response params from a string.
+     * Matches a strings like the following: ":<ik_value>:<ck_value>:<res_value>";
+     */
+    private static final Pattern UMTS_AUTH_RESPONSE_PARAMS_PATTERN =
+            Pattern.compile("^:([0-9a-fA-F]+):([0-9a-fA-F]+):([0-9a-fA-F]+)$");
+    /**
+     * Regex pattern for extracting the UMTS sim auts response params from a string.
+     * Matches a strings like the following: ":<auts_value>";
+     */
+    private static final Pattern UMTS_AUTS_RESPONSE_PARAMS_PATTERN =
+            Pattern.compile("^:([0-9a-fA-F]+)$");
+
+    private final Object mLock = new Object();
+    private final String mIfaceName;
+    private final WifiMonitor mWifiMonitor;
+    private ISupplicantStaNetwork mISupplicantStaNetwork;
+    private ISupplicantStaNetworkCallback mISupplicantStaNetworkCallback;
+
+    private boolean mVerboseLoggingEnabled = false;
+    // Indicates whether the system is capable of 802.11r fast BSS transition.
+    private boolean mSystemSupportsFastBssTransition = false;
+
+    // Network variables read from wpa_supplicant.
+    private int mNetworkId;
+    private ArrayList<Byte> mSsid;
+    private byte[/* 6 */] mBssid;
+    private boolean mScanSsid;
+    private int mKeyMgmtMask;
+    private int mProtoMask;
+    private int mAuthAlgMask;
+    private int mGroupCipherMask;
+    private int mPairwiseCipherMask;
+    private String mPskPassphrase;
+    private byte[] mPsk;
+    private ArrayList<Byte> mWepKey;
+    private int mWepTxKeyIdx;
+    private boolean mRequirePmf;
+    private String mIdStr;
+    private int mEapMethod;
+    private int mEapPhase2Method;
+    private ArrayList<Byte> mEapIdentity;
+    private ArrayList<Byte> mEapAnonymousIdentity;
+    private ArrayList<Byte> mEapPassword;
+    private String mEapCACert;
+    private String mEapCAPath;
+    private String mEapClientCert;
+    private String mEapPrivateKeyId;
+    private String mEapSubjectMatch;
+    private String mEapAltSubjectMatch;
+    private boolean mEapEngine;
+    private String mEapEngineID;
+    private String mEapDomainSuffixMatch;
+
+    SupplicantStaNetworkHal(ISupplicantStaNetwork iSupplicantStaNetwork, String ifaceName,
+                            Context context, WifiMonitor monitor) {
+        mISupplicantStaNetwork = iSupplicantStaNetwork;
+        mIfaceName = ifaceName;
+        mWifiMonitor = monitor;
+        mSystemSupportsFastBssTransition =
+                context.getResources().getBoolean(R.bool.config_wifi_fast_bss_transition_enabled);
+    }
+
+    /**
+     * Enable/Disable verbose logging.
+     *
+     * @param enable true to enable, false to disable.
+     */
+    void enableVerboseLogging(boolean enable) {
+        mVerboseLoggingEnabled = enable;
+    }
+
+    /**
+     * Read network variables from wpa_supplicant into the provided WifiConfiguration object.
+     *
+     * @param config        WifiConfiguration object to be populated.
+     * @param networkExtras Map of network extras parsed from wpa_supplicant.
+     * @return true if succeeds, false otherwise.
+     * @throws IllegalArgumentException on malformed configuration params.
+     */
+    public boolean loadWifiConfiguration(WifiConfiguration config,
+                                         Map<String, String> networkExtras) {
+        if (config == null) return false;
+        /** SSID */
+        config.SSID = null;
+        if (getSsid() && !ArrayUtils.isEmpty(mSsid)) {
+            config.SSID = NativeUtil.encodeSsid(mSsid);
+        } else {
+            Log.e(TAG, "failed to read ssid");
+            return false;
+        }
+        /** Network Id */
+        config.networkId = -1;
+        if (getId()) {
+            config.networkId = mNetworkId;
+        } else {
+            Log.e(TAG, "getId failed");
+            return false;
+        }
+        /** BSSID */
+        config.getNetworkSelectionStatus().setNetworkSelectionBSSID(null);
+        if (getBssid() && !ArrayUtils.isEmpty(mBssid)) {
+            config.getNetworkSelectionStatus().setNetworkSelectionBSSID(
+                    NativeUtil.macAddressFromByteArray(mBssid));
+        }
+        /** Scan SSID (Is Hidden Network?) */
+        config.hiddenSSID = false;
+        if (getScanSsid()) {
+            config.hiddenSSID = mScanSsid;
+        }
+        /** Require PMF*/
+        config.requirePMF = false;
+        if (getRequirePmf()) {
+            config.requirePMF = mRequirePmf;
+        }
+        /** WEP keys **/
+        config.wepTxKeyIndex = -1;
+        if (getWepTxKeyIdx()) {
+            config.wepTxKeyIndex = mWepTxKeyIdx;
+        }
+        for (int i = 0; i < 4; i++) {
+            config.wepKeys[i] = null;
+            if (getWepKey(i) && !ArrayUtils.isEmpty(mWepKey)) {
+                config.wepKeys[i] = NativeUtil.bytesToHexOrQuotedAsciiString(mWepKey);
+            }
+        }
+        /** PSK pass phrase */
+        config.preSharedKey = null;
+        if (getPskPassphrase() && !TextUtils.isEmpty(mPskPassphrase)) {
+            config.preSharedKey = NativeUtil.addEnclosingQuotes(mPskPassphrase);
+        } else if (getPsk() && !ArrayUtils.isEmpty(mPsk)) {
+            config.preSharedKey = NativeUtil.hexStringFromByteArray(mPsk);
+        }
+        /** allowedKeyManagement */
+        if (getKeyMgmt()) {
+            BitSet keyMgmtMask = supplicantToWifiConfigurationKeyMgmtMask(mKeyMgmtMask);
+            config.allowedKeyManagement = removeFastTransitionFlags(keyMgmtMask);
+        }
+        /** allowedProtocols */
+        if (getProto()) {
+            config.allowedProtocols =
+                    supplicantToWifiConfigurationProtoMask(mProtoMask);
+        }
+        /** allowedAuthAlgorithms */
+        if (getAuthAlg()) {
+            config.allowedAuthAlgorithms =
+                    supplicantToWifiConfigurationAuthAlgMask(mAuthAlgMask);
+        }
+        /** allowedGroupCiphers */
+        if (getGroupCipher()) {
+            config.allowedGroupCiphers =
+                    supplicantToWifiConfigurationGroupCipherMask(mGroupCipherMask);
+        }
+        /** allowedPairwiseCiphers */
+        if (getPairwiseCipher()) {
+            config.allowedPairwiseCiphers =
+                    supplicantToWifiConfigurationPairwiseCipherMask(mPairwiseCipherMask);
+        }
+        /** metadata: idstr */
+        if (getIdStr() && !TextUtils.isEmpty(mIdStr)) {
+            Map<String, String> metadata = parseNetworkExtra(mIdStr);
+            networkExtras.putAll(metadata);
+        } else {
+            Log.w(TAG, "getIdStr failed or empty");
+        }
+        return loadWifiEnterpriseConfig(config.SSID, config.enterpriseConfig);
+    }
+
+    /**
+     * Save an entire WifiConfiguration to wpa_supplicant via HIDL.
+     *
+     * @param config WifiConfiguration object to be saved.
+     * @return true if succeeds, false otherwise.
+     * @throws IllegalArgumentException on malformed configuration params.
+     */
+    public boolean saveWifiConfiguration(WifiConfiguration config) {
+        if (config == null) return false;
+        /** SSID */
+        if (config.SSID != null) {
+            if (!setSsid(NativeUtil.decodeSsid(config.SSID))) {
+                Log.e(TAG, "failed to set SSID: " + config.SSID);
+                return false;
+            }
+        }
+        /** BSSID */
+        String bssidStr = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
+        if (bssidStr != null) {
+            byte[] bssid = NativeUtil.macAddressToByteArray(bssidStr);
+            if (!setBssid(bssid)) {
+                Log.e(TAG, "failed to set BSSID: " + bssidStr);
+                return false;
+            }
+        }
+        /** Pre Shared Key. This can either be quoted ASCII passphrase or hex string for raw psk */
+        if (config.preSharedKey != null) {
+            if (config.preSharedKey.startsWith("\"")) {
+                if (!setPskPassphrase(NativeUtil.removeEnclosingQuotes(config.preSharedKey))) {
+                    Log.e(TAG, "failed to set psk passphrase");
+                    return false;
+                }
+            } else {
+                if (!setPsk(NativeUtil.hexStringToByteArray(config.preSharedKey))) {
+                    Log.e(TAG, "failed to set psk");
+                    return false;
+                }
+            }
+        }
+
+        /** Wep Keys */
+        boolean hasSetKey = false;
+        if (config.wepKeys != null) {
+            for (int i = 0; i < config.wepKeys.length; i++) {
+                if (config.wepKeys[i] != null) {
+                    if (!setWepKey(
+                            i, NativeUtil.hexOrQuotedAsciiStringToBytes(config.wepKeys[i]))) {
+                        Log.e(TAG, "failed to set wep_key " + i);
+                        return false;
+                    }
+                    hasSetKey = true;
+                }
+            }
+        }
+        /** Wep Tx Key Idx */
+        if (hasSetKey) {
+            if (!setWepTxKeyIdx(config.wepTxKeyIndex)) {
+                Log.e(TAG, "failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
+                return false;
+            }
+        }
+        /** HiddenSSID */
+        if (!setScanSsid(config.hiddenSSID)) {
+            Log.e(TAG, config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID);
+            return false;
+        }
+        /** RequirePMF */
+        if (!setRequirePmf(config.requirePMF)) {
+            Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePMF);
+            return false;
+        }
+        /** Key Management Scheme */
+        if (config.allowedKeyManagement.cardinality() != 0) {
+            // Add FT flags if supported.
+            BitSet keyMgmtMask = addFastTransitionFlags(config.allowedKeyManagement);
+            if (!setKeyMgmt(wifiConfigurationToSupplicantKeyMgmtMask(keyMgmtMask))) {
+                Log.e(TAG, "failed to set Key Management");
+                return false;
+            }
+        }
+        /** Security Protocol */
+        if (config.allowedProtocols.cardinality() != 0
+                && !setProto(wifiConfigurationToSupplicantProtoMask(config.allowedProtocols))) {
+            Log.e(TAG, "failed to set Security Protocol");
+            return false;
+        }
+        /** Auth Algorithm */
+        if (config.allowedAuthAlgorithms.cardinality() != 0
+                && !setAuthAlg(wifiConfigurationToSupplicantAuthAlgMask(
+                config.allowedAuthAlgorithms))) {
+            Log.e(TAG, "failed to set AuthAlgorithm");
+            return false;
+        }
+        /** Group Cipher */
+        if (config.allowedGroupCiphers.cardinality() != 0
+                && !setGroupCipher(wifiConfigurationToSupplicantGroupCipherMask(
+                config.allowedGroupCiphers))) {
+            Log.e(TAG, "failed to set Group Cipher");
+            return false;
+        }
+        /** Pairwise Cipher*/
+        if (config.allowedPairwiseCiphers.cardinality() != 0
+                && !setPairwiseCipher(wifiConfigurationToSupplicantPairwiseCipherMask(
+                        config.allowedPairwiseCiphers))) {
+            Log.e(TAG, "failed to set PairwiseCipher");
+            return false;
+        }
+        /** metadata: FQDN + ConfigKey + CreatorUid */
+        final Map<String, String> metadata = new HashMap<String, String>();
+        if (config.isPasspoint()) {
+            metadata.put(ID_STRING_KEY_FQDN, config.FQDN);
+        }
+        metadata.put(ID_STRING_KEY_CONFIG_KEY, config.configKey());
+        metadata.put(ID_STRING_KEY_CREATOR_UID, Integer.toString(config.creatorUid));
+        if (!setIdStr(createNetworkExtra(metadata))) {
+            Log.e(TAG, "failed to set id string");
+            return false;
+        }
+        /** UpdateIdentifier */
+        if (config.updateIdentifier != null
+                && !setUpdateIdentifier(Integer.parseInt(config.updateIdentifier))) {
+            Log.e(TAG, "failed to set update identifier");
+            return false;
+        }
+        // Finish here if no EAP config to set
+        if (config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+            if (!saveWifiEnterpriseConfig(config.SSID, config.enterpriseConfig)) {
+                return false;
+            }
+        }
+
+        // Now that the network is configured fully, start listening for callback events.
+        mISupplicantStaNetworkCallback =
+                new SupplicantStaNetworkHalCallback(config.networkId, config.SSID);
+        if (!registerCallback(mISupplicantStaNetworkCallback)) {
+            Log.e(TAG, "Failed to register callback");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Read network variables from wpa_supplicant into the provided WifiEnterpriseConfig object.
+     *
+     * @param ssid SSID of the network. (Used for logging purposes only)
+     * @param eapConfig WifiEnterpriseConfig object to be populated.
+     * @return true if succeeds, false otherwise.
+     */
+    private boolean loadWifiEnterpriseConfig(String ssid, WifiEnterpriseConfig eapConfig) {
+        if (eapConfig == null) return false;
+        /** EAP method */
+        if (getEapMethod()) {
+            eapConfig.setEapMethod(supplicantToWifiConfigurationEapMethod(mEapMethod));
+        } else {
+            // Invalid eap method could be because it's not an enterprise config.
+            Log.e(TAG, "failed to get eap method. Assumimg not an enterprise network");
+            return true;
+        }
+        /** EAP Phase 2 method */
+        if (getEapPhase2Method()) {
+            eapConfig.setPhase2Method(
+                    supplicantToWifiConfigurationEapPhase2Method(mEapPhase2Method));
+        } else {
+            // We cannot have an invalid eap phase 2 method. Return failure.
+            Log.e(TAG, "failed to get eap phase2 method");
+            return false;
+        }
+        /** EAP Identity */
+        if (getEapIdentity() && !ArrayUtils.isEmpty(mEapIdentity)) {
+            eapConfig.setFieldValue(
+                    WifiEnterpriseConfig.IDENTITY_KEY,
+                    NativeUtil.stringFromByteArrayList(mEapIdentity));
+        }
+        /** EAP Anonymous Identity */
+        if (getEapAnonymousIdentity() && !ArrayUtils.isEmpty(mEapAnonymousIdentity)) {
+            eapConfig.setFieldValue(
+                    WifiEnterpriseConfig.ANON_IDENTITY_KEY,
+                    NativeUtil.stringFromByteArrayList(mEapAnonymousIdentity));
+        }
+        /** EAP Password */
+        if (getEapPassword() && !ArrayUtils.isEmpty(mEapPassword)) {
+            eapConfig.setFieldValue(
+                    WifiEnterpriseConfig.PASSWORD_KEY,
+                    NativeUtil.stringFromByteArrayList(mEapPassword));
+        }
+        /** EAP Client Cert */
+        if (getEapClientCert() && !TextUtils.isEmpty(mEapClientCert)) {
+            eapConfig.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, mEapClientCert);
+        }
+        /** EAP CA Cert */
+        if (getEapCACert() && !TextUtils.isEmpty(mEapCACert)) {
+            eapConfig.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, mEapCACert);
+        }
+        /** EAP Subject Match */
+        if (getEapSubjectMatch() && !TextUtils.isEmpty(mEapSubjectMatch)) {
+            eapConfig.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, mEapSubjectMatch);
+        }
+        /** EAP Engine ID */
+        if (getEapEngineID() && !TextUtils.isEmpty(mEapEngineID)) {
+            eapConfig.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, mEapEngineID);
+        }
+        /** EAP Engine. Set this only if the engine id is non null. */
+        if (getEapEngine() && !TextUtils.isEmpty(mEapEngineID)) {
+            eapConfig.setFieldValue(
+                    WifiEnterpriseConfig.ENGINE_KEY,
+                    mEapEngine
+                            ? WifiEnterpriseConfig.ENGINE_ENABLE
+                            : WifiEnterpriseConfig.ENGINE_DISABLE);
+        }
+        /** EAP Private Key */
+        if (getEapPrivateKeyId() && !TextUtils.isEmpty(mEapPrivateKeyId)) {
+            eapConfig.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, mEapPrivateKeyId);
+        }
+        /** EAP Alt Subject Match */
+        if (getEapAltSubjectMatch() && !TextUtils.isEmpty(mEapAltSubjectMatch)) {
+            eapConfig.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, mEapAltSubjectMatch);
+        }
+        /** EAP Domain Suffix Match */
+        if (getEapDomainSuffixMatch() && !TextUtils.isEmpty(mEapDomainSuffixMatch)) {
+            eapConfig.setFieldValue(
+                    WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, mEapDomainSuffixMatch);
+        }
+        /** EAP CA Path*/
+        if (getEapCAPath() && !TextUtils.isEmpty(mEapCAPath)) {
+            eapConfig.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, mEapCAPath);
+        }
+        return true;
+    }
+
+    /**
+     * Save network variables from the provided WifiEnterpriseConfig object to wpa_supplicant.
+     *
+     * @param ssid SSID of the network. (Used for logging purposes only)
+     * @param eapConfig WifiEnterpriseConfig object to be saved.
+     * @return true if succeeds, false otherwise.
+     */
+    private boolean saveWifiEnterpriseConfig(String ssid, WifiEnterpriseConfig eapConfig) {
+        if (eapConfig == null) return false;
+        /** EAP method */
+        if (!setEapMethod(wifiConfigurationToSupplicantEapMethod(eapConfig.getEapMethod()))) {
+            Log.e(TAG, ssid + ": failed to set eap method: " + eapConfig.getEapMethod());
+            return false;
+        }
+        /** EAP Phase 2 method */
+        if (!setEapPhase2Method(wifiConfigurationToSupplicantEapPhase2Method(
+                eapConfig.getPhase2Method()))) {
+            Log.e(TAG, ssid + ": failed to set eap phase 2 method: " + eapConfig.getPhase2Method());
+            return false;
+        }
+        String eapParam = null;
+        /** EAP Identity */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY);
+        if (!TextUtils.isEmpty(eapParam)
+                && !setEapIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
+            Log.e(TAG, ssid + ": failed to set eap identity: " + eapParam);
+            return false;
+        }
+        /** EAP Anonymous Identity */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY);
+        if (!TextUtils.isEmpty(eapParam)
+                && !setEapAnonymousIdentity(NativeUtil.stringToByteArrayList(eapParam))) {
+            Log.e(TAG, ssid + ": failed to set eap anonymous identity: " + eapParam);
+            return false;
+        }
+        /** EAP Password */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY);
+        if (!TextUtils.isEmpty(eapParam)
+                && !setEapPassword(NativeUtil.stringToByteArrayList(eapParam))) {
+            Log.e(TAG, ssid + ": failed to set eap password");
+            return false;
+        }
+        /** EAP Client Cert */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapClientCert(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap client cert: " + eapParam);
+            return false;
+        }
+        /** EAP CA Cert */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapCACert(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap ca cert: " + eapParam);
+            return false;
+        }
+        /** EAP Subject Match */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapSubjectMatch(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap subject match: " + eapParam);
+            return false;
+        }
+        /** EAP Engine ID */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapEngineID(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap engine id: " + eapParam);
+            return false;
+        }
+        /** EAP Engine */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapEngine(
+                eapParam.equals(WifiEnterpriseConfig.ENGINE_ENABLE) ? true : false)) {
+            Log.e(TAG, ssid + ": failed to set eap engine: " + eapParam);
+            return false;
+        }
+        /** EAP Private Key */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapPrivateKeyId(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap private key: " + eapParam);
+            return false;
+        }
+        /** EAP Alt Subject Match */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapAltSubjectMatch(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap alt subject match: " + eapParam);
+            return false;
+        }
+        /** EAP Domain Suffix Match */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapDomainSuffixMatch(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap domain suffix match: " + eapParam);
+            return false;
+        }
+        /** EAP CA Path*/
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY);
+        if (!TextUtils.isEmpty(eapParam) && !setEapCAPath(eapParam)) {
+            Log.e(TAG, ssid + ": failed to set eap ca path: " + eapParam);
+            return false;
+        }
+
+        /** EAP Proactive Key Caching */
+        eapParam = eapConfig.getFieldValue(WifiEnterpriseConfig.OPP_KEY_CACHING);
+        if (!TextUtils.isEmpty(eapParam)
+                && !setEapProactiveKeyCaching(eapParam.equals("1") ? true : false)) {
+            Log.e(TAG, ssid + ": failed to set proactive key caching: " + eapParam);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Maps WifiConfiguration Key Management BitSet to Supplicant HIDL bitmask int
+     * TODO(b/32571829): Update mapping when fast transition keys are added
+     * @return bitmask int describing the allowed Key Management schemes, readable by the Supplicant
+     *         HIDL hal
+     */
+    private static int wifiConfigurationToSupplicantKeyMgmtMask(BitSet keyMgmt) {
+        int mask = 0;
+        for (int bit = keyMgmt.nextSetBit(0); bit != -1; bit = keyMgmt.nextSetBit(bit + 1)) {
+            switch (bit) {
+                case WifiConfiguration.KeyMgmt.NONE:
+                    mask |= ISupplicantStaNetwork.KeyMgmtMask.NONE;
+                    break;
+                case WifiConfiguration.KeyMgmt.WPA_PSK:
+                    mask |= ISupplicantStaNetwork.KeyMgmtMask.WPA_PSK;
+                    break;
+                case WifiConfiguration.KeyMgmt.WPA_EAP:
+                    mask |= ISupplicantStaNetwork.KeyMgmtMask.WPA_EAP;
+                    break;
+                case WifiConfiguration.KeyMgmt.IEEE8021X:
+                    mask |= ISupplicantStaNetwork.KeyMgmtMask.IEEE8021X;
+                    break;
+                case WifiConfiguration.KeyMgmt.OSEN:
+                    mask |= ISupplicantStaNetwork.KeyMgmtMask.OSEN;
+                    break;
+                case WifiConfiguration.KeyMgmt.FT_PSK:
+                    mask |= ISupplicantStaNetwork.KeyMgmtMask.FT_PSK;
+                    break;
+                case WifiConfiguration.KeyMgmt.FT_EAP:
+                    mask |= ISupplicantStaNetwork.KeyMgmtMask.FT_EAP;
+                    break;
+                case WifiConfiguration.KeyMgmt.WPA2_PSK: // This should never happen
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid protoMask bit in keyMgmt: " + bit);
+            }
+        }
+        return mask;
+    }
+
+    private static int wifiConfigurationToSupplicantProtoMask(BitSet protoMask) {
+        int mask = 0;
+        for (int bit = protoMask.nextSetBit(0); bit != -1; bit = protoMask.nextSetBit(bit + 1)) {
+            switch (bit) {
+                case WifiConfiguration.Protocol.WPA:
+                    mask |= ISupplicantStaNetwork.ProtoMask.WPA;
+                    break;
+                case WifiConfiguration.Protocol.RSN:
+                    mask |= ISupplicantStaNetwork.ProtoMask.RSN;
+                    break;
+                case WifiConfiguration.Protocol.OSEN:
+                    mask |= ISupplicantStaNetwork.ProtoMask.OSEN;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid protoMask bit in wificonfig: " + bit);
+            }
+        }
+        return mask;
+    };
+
+    private static int wifiConfigurationToSupplicantAuthAlgMask(BitSet authAlgMask) {
+        int mask = 0;
+        for (int bit = authAlgMask.nextSetBit(0); bit != -1;
+                bit = authAlgMask.nextSetBit(bit + 1)) {
+            switch (bit) {
+                case WifiConfiguration.AuthAlgorithm.OPEN:
+                    mask |= ISupplicantStaNetwork.AuthAlgMask.OPEN;
+                    break;
+                case WifiConfiguration.AuthAlgorithm.SHARED:
+                    mask |= ISupplicantStaNetwork.AuthAlgMask.SHARED;
+                    break;
+                case WifiConfiguration.AuthAlgorithm.LEAP:
+                    mask |= ISupplicantStaNetwork.AuthAlgMask.LEAP;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid authAlgMask bit in wificonfig: " + bit);
+            }
+        }
+        return mask;
+    };
+
+    private static int wifiConfigurationToSupplicantGroupCipherMask(BitSet groupCipherMask) {
+        int mask = 0;
+        for (int bit = groupCipherMask.nextSetBit(0); bit != -1; bit =
+                groupCipherMask.nextSetBit(bit + 1)) {
+            switch (bit) {
+                case WifiConfiguration.GroupCipher.WEP40:
+                    mask |= ISupplicantStaNetwork.GroupCipherMask.WEP40;
+                    break;
+                case WifiConfiguration.GroupCipher.WEP104:
+                    mask |= ISupplicantStaNetwork.GroupCipherMask.WEP104;
+                    break;
+                case WifiConfiguration.GroupCipher.TKIP:
+                    mask |= ISupplicantStaNetwork.GroupCipherMask.TKIP;
+                    break;
+                case WifiConfiguration.GroupCipher.CCMP:
+                    mask |= ISupplicantStaNetwork.GroupCipherMask.CCMP;
+                    break;
+                case WifiConfiguration.GroupCipher.GTK_NOT_USED:
+                    mask |= ISupplicantStaNetwork.GroupCipherMask.GTK_NOT_USED;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid GroupCipherMask bit in wificonfig: " + bit);
+            }
+        }
+        return mask;
+    };
+
+    private static int wifiConfigurationToSupplicantPairwiseCipherMask(BitSet pairwiseCipherMask) {
+        int mask = 0;
+        for (int bit = pairwiseCipherMask.nextSetBit(0); bit != -1;
+                bit = pairwiseCipherMask.nextSetBit(bit + 1)) {
+            switch (bit) {
+                case WifiConfiguration.PairwiseCipher.NONE:
+                    mask |= ISupplicantStaNetwork.PairwiseCipherMask.NONE;
+                    break;
+                case WifiConfiguration.PairwiseCipher.TKIP:
+                    mask |= ISupplicantStaNetwork.PairwiseCipherMask.TKIP;
+                    break;
+                case WifiConfiguration.PairwiseCipher.CCMP:
+                    mask |= ISupplicantStaNetwork.PairwiseCipherMask.CCMP;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid pairwiseCipherMask bit in wificonfig: " + bit);
+            }
+        }
+        return mask;
+    };
+
+    private static int supplicantToWifiConfigurationEapMethod(int value) {
+        switch (value) {
+            case ISupplicantStaNetwork.EapMethod.PEAP:
+                return WifiEnterpriseConfig.Eap.PEAP;
+            case ISupplicantStaNetwork.EapMethod.TLS:
+                return WifiEnterpriseConfig.Eap.TLS;
+            case ISupplicantStaNetwork.EapMethod.TTLS:
+                return WifiEnterpriseConfig.Eap.TTLS;
+            case ISupplicantStaNetwork.EapMethod.PWD:
+                return WifiEnterpriseConfig.Eap.PWD;
+            case ISupplicantStaNetwork.EapMethod.SIM:
+                return WifiEnterpriseConfig.Eap.SIM;
+            case ISupplicantStaNetwork.EapMethod.AKA:
+                return WifiEnterpriseConfig.Eap.AKA;
+            case ISupplicantStaNetwork.EapMethod.AKA_PRIME:
+                return WifiEnterpriseConfig.Eap.AKA_PRIME;
+            case ISupplicantStaNetwork.EapMethod.WFA_UNAUTH_TLS:
+                return WifiEnterpriseConfig.Eap.UNAUTH_TLS;
+            // WifiEnterpriseConfig.Eap.NONE:
+            default:
+                Log.e(TAG, "invalid eap method value from supplicant: " + value);
+                return -1;
+        }
+    };
+
+    private static int supplicantToWifiConfigurationEapPhase2Method(int value) {
+        switch (value) {
+            case ISupplicantStaNetwork.EapPhase2Method.NONE:
+                return WifiEnterpriseConfig.Phase2.NONE;
+            case ISupplicantStaNetwork.EapPhase2Method.PAP:
+                return WifiEnterpriseConfig.Phase2.PAP;
+            case ISupplicantStaNetwork.EapPhase2Method.MSPAP:
+                return WifiEnterpriseConfig.Phase2.MSCHAP;
+            case ISupplicantStaNetwork.EapPhase2Method.MSPAPV2:
+                return WifiEnterpriseConfig.Phase2.MSCHAPV2;
+            case ISupplicantStaNetwork.EapPhase2Method.GTC:
+                return WifiEnterpriseConfig.Phase2.GTC;
+            case ISupplicantStaNetwork.EapPhase2Method.SIM:
+                return WifiEnterpriseConfig.Phase2.SIM;
+            case ISupplicantStaNetwork.EapPhase2Method.AKA:
+                return WifiEnterpriseConfig.Phase2.AKA;
+            case ISupplicantStaNetwork.EapPhase2Method.AKA_PRIME:
+                return WifiEnterpriseConfig.Phase2.AKA_PRIME;
+            default:
+                Log.e(TAG, "invalid eap phase2 method value from supplicant: " + value);
+                return -1;
+        }
+    };
+
+    private static int supplicantMaskValueToWifiConfigurationBitSet(int supplicantMask,
+                                                             int supplicantValue, BitSet bitset,
+                                                             int bitSetPosition) {
+        bitset.set(bitSetPosition, (supplicantMask & supplicantValue) == supplicantValue);
+        int modifiedSupplicantMask = supplicantMask & ~supplicantValue;
+        return modifiedSupplicantMask;
+    }
+
+    private static BitSet supplicantToWifiConfigurationKeyMgmtMask(int mask) {
+        BitSet bitset = new BitSet();
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.KeyMgmtMask.NONE, bitset,
+                WifiConfiguration.KeyMgmt.NONE);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.KeyMgmtMask.WPA_PSK, bitset,
+                WifiConfiguration.KeyMgmt.WPA_PSK);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.KeyMgmtMask.WPA_EAP, bitset,
+                WifiConfiguration.KeyMgmt.WPA_EAP);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.KeyMgmtMask.IEEE8021X, bitset,
+                WifiConfiguration.KeyMgmt.IEEE8021X);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.KeyMgmtMask.OSEN, bitset,
+                WifiConfiguration.KeyMgmt.OSEN);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.KeyMgmtMask.FT_PSK, bitset,
+                WifiConfiguration.KeyMgmt.FT_PSK);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.KeyMgmtMask.FT_EAP, bitset,
+                WifiConfiguration.KeyMgmt.FT_EAP);
+        if (mask != 0) {
+            throw new IllegalArgumentException(
+                    "invalid key mgmt mask from supplicant: " + mask);
+        }
+        return bitset;
+    }
+
+    private static BitSet supplicantToWifiConfigurationProtoMask(int mask) {
+        BitSet bitset = new BitSet();
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.ProtoMask.WPA, bitset,
+                WifiConfiguration.Protocol.WPA);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.ProtoMask.RSN, bitset,
+                WifiConfiguration.Protocol.RSN);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.ProtoMask.OSEN, bitset,
+                WifiConfiguration.Protocol.OSEN);
+        if (mask != 0) {
+            throw new IllegalArgumentException(
+                    "invalid proto mask from supplicant: " + mask);
+        }
+        return bitset;
+    };
+
+    private static BitSet supplicantToWifiConfigurationAuthAlgMask(int mask) {
+        BitSet bitset = new BitSet();
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.AuthAlgMask.OPEN, bitset,
+                WifiConfiguration.AuthAlgorithm.OPEN);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.AuthAlgMask.SHARED, bitset,
+                WifiConfiguration.AuthAlgorithm.SHARED);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.AuthAlgMask.LEAP, bitset,
+                WifiConfiguration.AuthAlgorithm.LEAP);
+        if (mask != 0) {
+            throw new IllegalArgumentException(
+                    "invalid auth alg mask from supplicant: " + mask);
+        }
+        return bitset;
+    };
+
+    private static BitSet supplicantToWifiConfigurationGroupCipherMask(int mask) {
+        BitSet bitset = new BitSet();
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.GroupCipherMask.WEP40, bitset,
+                WifiConfiguration.GroupCipher.WEP40);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.GroupCipherMask.WEP104, bitset,
+                WifiConfiguration.GroupCipher.WEP104);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.GroupCipherMask.TKIP, bitset,
+                WifiConfiguration.GroupCipher.TKIP);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.GroupCipherMask.CCMP, bitset,
+                WifiConfiguration.GroupCipher.CCMP);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.GroupCipherMask.GTK_NOT_USED, bitset,
+                WifiConfiguration.GroupCipher.GTK_NOT_USED);
+        if (mask != 0) {
+            throw new IllegalArgumentException(
+                    "invalid group cipher mask from supplicant: " + mask);
+        }
+        return bitset;
+    };
+
+    private static BitSet supplicantToWifiConfigurationPairwiseCipherMask(int mask) {
+        BitSet bitset = new BitSet();
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.PairwiseCipherMask.NONE, bitset,
+                WifiConfiguration.PairwiseCipher.NONE);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.PairwiseCipherMask.TKIP, bitset,
+                WifiConfiguration.PairwiseCipher.TKIP);
+        mask = supplicantMaskValueToWifiConfigurationBitSet(
+                mask, ISupplicantStaNetwork.PairwiseCipherMask.CCMP, bitset,
+                WifiConfiguration.PairwiseCipher.CCMP);
+        if (mask != 0) {
+            throw new IllegalArgumentException(
+                    "invalid pairwise cipher mask from supplicant: " + mask);
+        }
+        return bitset;
+    };
+
+    private static int wifiConfigurationToSupplicantEapMethod(int value) {
+        switch (value) {
+            case WifiEnterpriseConfig.Eap.PEAP:
+                return ISupplicantStaNetwork.EapMethod.PEAP;
+            case WifiEnterpriseConfig.Eap.TLS:
+                return ISupplicantStaNetwork.EapMethod.TLS;
+            case WifiEnterpriseConfig.Eap.TTLS:
+                return ISupplicantStaNetwork.EapMethod.TTLS;
+            case WifiEnterpriseConfig.Eap.PWD:
+                return ISupplicantStaNetwork.EapMethod.PWD;
+            case WifiEnterpriseConfig.Eap.SIM:
+                return ISupplicantStaNetwork.EapMethod.SIM;
+            case WifiEnterpriseConfig.Eap.AKA:
+                return ISupplicantStaNetwork.EapMethod.AKA;
+            case WifiEnterpriseConfig.Eap.AKA_PRIME:
+                return ISupplicantStaNetwork.EapMethod.AKA_PRIME;
+            case WifiEnterpriseConfig.Eap.UNAUTH_TLS:
+                return ISupplicantStaNetwork.EapMethod.WFA_UNAUTH_TLS;
+            // WifiEnterpriseConfig.Eap.NONE:
+            default:
+                Log.e(TAG, "invalid eap method value from WifiConfiguration: " + value);
+                return -1;
+        }
+    };
+
+    private static int wifiConfigurationToSupplicantEapPhase2Method(int value) {
+        switch (value) {
+            case WifiEnterpriseConfig.Phase2.NONE:
+                return ISupplicantStaNetwork.EapPhase2Method.NONE;
+            case WifiEnterpriseConfig.Phase2.PAP:
+                return ISupplicantStaNetwork.EapPhase2Method.PAP;
+            case WifiEnterpriseConfig.Phase2.MSCHAP:
+                return ISupplicantStaNetwork.EapPhase2Method.MSPAP;
+            case WifiEnterpriseConfig.Phase2.MSCHAPV2:
+                return ISupplicantStaNetwork.EapPhase2Method.MSPAPV2;
+            case WifiEnterpriseConfig.Phase2.GTC:
+                return ISupplicantStaNetwork.EapPhase2Method.GTC;
+            case WifiEnterpriseConfig.Phase2.SIM:
+                return ISupplicantStaNetwork.EapPhase2Method.SIM;
+            case WifiEnterpriseConfig.Phase2.AKA:
+                return ISupplicantStaNetwork.EapPhase2Method.AKA;
+            case WifiEnterpriseConfig.Phase2.AKA_PRIME:
+                return ISupplicantStaNetwork.EapPhase2Method.AKA_PRIME;
+            default:
+                Log.e(TAG, "invalid eap phase2 method value from WifiConfiguration: " + value);
+                return -1;
+        }
+    };
+
+    /** See ISupplicantNetwork.hal for documentation */
+    private boolean getId() {
+        synchronized (mLock) {
+            final String methodStr = "getId";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getId((SupplicantStatus status, int idValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mNetworkId = idValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean registerCallback(ISupplicantStaNetworkCallback callback) {
+        synchronized (mLock) {
+            final String methodStr = "registerCallback";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.registerCallback(callback);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setSsid(java.util.ArrayList<Byte> ssid) {
+        synchronized (mLock) {
+            final String methodStr = "setSsid";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setSsid(ssid);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set the BSSID for this network.
+     *
+     * @param bssidStr MAC address in "XX:XX:XX:XX:XX:XX" form or "any" to reset the mac address.
+     * @return true if it succeeds, false otherwise.
+     */
+    public boolean setBssid(String bssidStr) {
+        try {
+            return setBssid(NativeUtil.macAddressToByteArray(bssidStr));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + bssidStr, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setBssid(byte[/* 6 */] bssid) {
+        synchronized (mLock) {
+            final String methodStr = "setBssid";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setBssid(bssid);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setScanSsid(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "setScanSsid";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setScanSsid(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setKeyMgmt(int keyMgmtMask) {
+        synchronized (mLock) {
+            final String methodStr = "setKeyMgmt";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setKeyMgmt(keyMgmtMask);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setProto(int protoMask) {
+        synchronized (mLock) {
+            final String methodStr = "setProto";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setProto(protoMask);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setAuthAlg(int authAlgMask) {
+        synchronized (mLock) {
+            final String methodStr = "setAuthAlg";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setAuthAlg(authAlgMask);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setGroupCipher(int groupCipherMask) {
+        synchronized (mLock) {
+            final String methodStr = "setGroupCipher";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setGroupCipher(groupCipherMask);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setPairwiseCipher(int pairwiseCipherMask) {
+        synchronized (mLock) {
+            final String methodStr = "setPairwiseCipher";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =
+                        mISupplicantStaNetwork.setPairwiseCipher(pairwiseCipherMask);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setPskPassphrase(String psk) {
+        synchronized (mLock) {
+            final String methodStr = "setPskPassphrase";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setPskPassphrase(psk);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setPsk(byte[] psk) {
+        synchronized (mLock) {
+            final String methodStr = "setPsk";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setPsk(psk);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setWepKey(int keyIdx, java.util.ArrayList<Byte> wepKey) {
+        synchronized (mLock) {
+            final String methodStr = "setWepKey";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setWepKey(keyIdx, wepKey);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setWepTxKeyIdx(int keyIdx) {
+        synchronized (mLock) {
+            final String methodStr = "setWepTxKeyIdx";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setWepTxKeyIdx(keyIdx);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setRequirePmf(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "setRequirePmf";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setRequirePmf(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setUpdateIdentifier(int identifier) {
+        synchronized (mLock) {
+            final String methodStr = "setUpdateIdentifier";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setUpdateIdentifier(identifier);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapMethod(int method) {
+        synchronized (mLock) {
+            final String methodStr = "setEapMethod";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapMethod(method);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapPhase2Method(int method) {
+        synchronized (mLock) {
+            final String methodStr = "setEapPhase2Method";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapPhase2Method(method);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapIdentity(java.util.ArrayList<Byte> identity) {
+        synchronized (mLock) {
+            final String methodStr = "setEapIdentity";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapIdentity(identity);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapAnonymousIdentity(java.util.ArrayList<Byte> identity) {
+        synchronized (mLock) {
+            final String methodStr = "setEapAnonymousIdentity";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapAnonymousIdentity(identity);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapPassword(java.util.ArrayList<Byte> password) {
+        synchronized (mLock) {
+            final String methodStr = "setEapPassword";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapPassword(password);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapCACert(String path) {
+        synchronized (mLock) {
+            final String methodStr = "setEapCACert";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapCACert(path);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapCAPath(String path) {
+        synchronized (mLock) {
+            final String methodStr = "setEapCAPath";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapCAPath(path);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapClientCert(String path) {
+        synchronized (mLock) {
+            final String methodStr = "setEapClientCert";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapClientCert(path);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapPrivateKeyId(String id) {
+        synchronized (mLock) {
+            final String methodStr = "setEapPrivateKeyId";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapPrivateKeyId(id);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapSubjectMatch(String match) {
+        synchronized (mLock) {
+            final String methodStr = "setEapSubjectMatch";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapSubjectMatch(match);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapAltSubjectMatch(String match) {
+        synchronized (mLock) {
+            final String methodStr = "setEapAltSubjectMatch";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapAltSubjectMatch(match);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapEngine(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "setEapEngine";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapEngine(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapEngineID(String id) {
+        synchronized (mLock) {
+            final String methodStr = "setEapEngineID";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapEngineID(id);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapDomainSuffixMatch(String match) {
+        synchronized (mLock) {
+            final String methodStr = "setEapDomainSuffixMatch";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setEapDomainSuffixMatch(match);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setEapProactiveKeyCaching(boolean enable) {
+        synchronized (mLock) {
+            final String methodStr = "setEapProactiveKeyCaching";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setProactiveKeyCaching(enable);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean setIdStr(String idString) {
+        synchronized (mLock) {
+            final String methodStr = "setIdStr";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.setIdStr(idString);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getSsid() {
+        synchronized (mLock) {
+            final String methodStr = "getSsid";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getSsid((SupplicantStatus status,
+                        java.util.ArrayList<Byte> ssidValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mSsid = ssidValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getBssid() {
+        synchronized (mLock) {
+            final String methodStr = "getBssid";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getBssid((SupplicantStatus status,
+                        byte[/* 6 */] bssidValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mBssid = bssidValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getScanSsid() {
+        synchronized (mLock) {
+            final String methodStr = "getScanSsid";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getScanSsid((SupplicantStatus status,
+                        boolean enabledValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mScanSsid = enabledValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getKeyMgmt() {
+        synchronized (mLock) {
+            final String methodStr = "getKeyMgmt";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getKeyMgmt((SupplicantStatus status,
+                        int keyMgmtMaskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mKeyMgmtMask = keyMgmtMaskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getProto() {
+        synchronized (mLock) {
+            final String methodStr = "getProto";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getProto((SupplicantStatus status, int protoMaskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mProtoMask = protoMaskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getAuthAlg() {
+        synchronized (mLock) {
+            final String methodStr = "getAuthAlg";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getAuthAlg((SupplicantStatus status,
+                        int authAlgMaskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mAuthAlgMask = authAlgMaskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getGroupCipher() {
+        synchronized (mLock) {
+            final String methodStr = "getGroupCipher";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getGroupCipher((SupplicantStatus status,
+                        int groupCipherMaskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mGroupCipherMask = groupCipherMaskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getPairwiseCipher() {
+        synchronized (mLock) {
+            final String methodStr = "getPairwiseCipher";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getPairwiseCipher((SupplicantStatus status,
+                        int pairwiseCipherMaskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mPairwiseCipherMask = pairwiseCipherMaskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getPskPassphrase() {
+        synchronized (mLock) {
+            final String methodStr = "getPskPassphrase";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getPskPassphrase((SupplicantStatus status,
+                        String pskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mPskPassphrase = pskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getPsk() {
+        synchronized (mLock) {
+            final String methodStr = "getPsk";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getPsk((SupplicantStatus status, byte[] pskValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mPsk = pskValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getWepKey(int keyIdx) {
+        synchronized (mLock) {
+            final String methodStr = "keyIdx";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getWepKey(keyIdx, (SupplicantStatus status,
+                        java.util.ArrayList<Byte> wepKeyValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mWepKey = wepKeyValue;
+                    } else {
+                        Log.e(TAG, methodStr + ",  failed: " + status.debugMessage);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getWepTxKeyIdx() {
+        synchronized (mLock) {
+            final String methodStr = "getWepTxKeyIdx";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getWepTxKeyIdx((SupplicantStatus status,
+                        int keyIdxValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mWepTxKeyIdx = keyIdxValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getRequirePmf() {
+        synchronized (mLock) {
+            final String methodStr = "getRequirePmf";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getRequirePmf((SupplicantStatus status,
+                        boolean enabledValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mRequirePmf = enabledValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapMethod() {
+        synchronized (mLock) {
+            final String methodStr = "getEapMethod";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapMethod((SupplicantStatus status,
+                        int methodValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapMethod = methodValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapPhase2Method() {
+        synchronized (mLock) {
+            final String methodStr = "getEapPhase2Method";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapPhase2Method((SupplicantStatus status,
+                        int methodValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapPhase2Method = methodValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapIdentity() {
+        synchronized (mLock) {
+            final String methodStr = "getEapIdentity";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapIdentity((SupplicantStatus status,
+                        ArrayList<Byte> identityValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapIdentity = identityValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapAnonymousIdentity() {
+        synchronized (mLock) {
+            final String methodStr = "getEapAnonymousIdentity";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapAnonymousIdentity((SupplicantStatus status,
+                        ArrayList<Byte> identityValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapAnonymousIdentity = identityValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * A wrapping method for getEapAnonymousIdentity().
+     * This get anonymous identity from supplicant and returns it as a string.
+     *
+     * @return anonymous identity string if succeeds, null otherwise.
+     */
+    public String fetchEapAnonymousIdentity() {
+        if (!getEapAnonymousIdentity()) {
+            return null;
+        }
+        return NativeUtil.stringFromByteArrayList(mEapAnonymousIdentity);
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapPassword() {
+        synchronized (mLock) {
+            final String methodStr = "getEapPassword";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapPassword((SupplicantStatus status,
+                        ArrayList<Byte> passwordValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapPassword = passwordValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapCACert() {
+        synchronized (mLock) {
+            final String methodStr = "getEapCACert";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapCACert((SupplicantStatus status, String pathValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapCACert = pathValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapCAPath() {
+        synchronized (mLock) {
+            final String methodStr = "getEapCAPath";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapCAPath((SupplicantStatus status, String pathValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapCAPath = pathValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapClientCert() {
+        synchronized (mLock) {
+            final String methodStr = "getEapClientCert";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapClientCert((SupplicantStatus status,
+                        String pathValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapClientCert = pathValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapPrivateKeyId() {
+        synchronized (mLock) {
+            final String methodStr = "getEapPrivateKeyId";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapPrivateKeyId((SupplicantStatus status,
+                        String idValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapPrivateKeyId = idValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapSubjectMatch() {
+        synchronized (mLock) {
+            final String methodStr = "getEapSubjectMatch";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapSubjectMatch((SupplicantStatus status,
+                        String matchValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapSubjectMatch = matchValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapAltSubjectMatch() {
+        synchronized (mLock) {
+            final String methodStr = "getEapAltSubjectMatch";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapAltSubjectMatch((SupplicantStatus status,
+                        String matchValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapAltSubjectMatch = matchValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapEngine() {
+        synchronized (mLock) {
+            final String methodStr = "getEapEngine";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapEngine((SupplicantStatus status,
+                        boolean enabledValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapEngine = enabledValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapEngineID() {
+        synchronized (mLock) {
+            final String methodStr = "getEapEngineID";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapEngineID((SupplicantStatus status, String idValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapEngineID = idValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getEapDomainSuffixMatch() {
+        synchronized (mLock) {
+            final String methodStr = "getEapDomainSuffixMatch";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getEapDomainSuffixMatch((SupplicantStatus status,
+                        String matchValue) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mEapDomainSuffixMatch = matchValue;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean getIdStr() {
+        synchronized (mLock) {
+            final String methodStr = "getIdStr";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                MutableBoolean statusOk = new MutableBoolean(false);
+                mISupplicantStaNetwork.getIdStr((SupplicantStatus status, String idString) -> {
+                    statusOk.value = status.code == SupplicantStatusCode.SUCCESS;
+                    if (statusOk.value) {
+                        this.mIdStr = idString;
+                    } else {
+                        checkStatusAndLogFailure(status, methodStr);
+                    }
+                });
+                return statusOk.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean enable(boolean noConnect) {
+        synchronized (mLock) {
+            final String methodStr = "enable";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.enable(noConnect);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean disable() {
+        synchronized (mLock) {
+            final String methodStr = "disable";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.disable();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Trigger a connection to this network.
+     *
+     * @return true if it succeeds, false otherwise.
+     */
+    public boolean select() {
+        synchronized (mLock) {
+            final String methodStr = "select";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =  mISupplicantStaNetwork.select();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Send GSM auth response.
+     *
+     * @param paramsStr Response params as a string.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendNetworkEapSimGsmAuthResponse(String paramsStr) {
+        try {
+            Matcher match = GSM_AUTH_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
+            ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params =
+                    new ArrayList<>();
+            while (match.find()) {
+                if (match.groupCount() != 2) {
+                    Log.e(TAG, "Malformed gsm auth response params: " + paramsStr);
+                    return false;
+                }
+                ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams param =
+                        new ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams();
+                byte[] kc = NativeUtil.hexStringToByteArray(match.group(1));
+                if (kc == null || kc.length != param.kc.length) {
+                    Log.e(TAG, "Invalid kc value: " + match.group(1));
+                    return false;
+                }
+                byte[] sres = NativeUtil.hexStringToByteArray(match.group(2));
+                if (sres == null || sres.length != param.sres.length) {
+                    Log.e(TAG, "Invalid sres value: " + match.group(2));
+                    return false;
+                }
+                System.arraycopy(kc, 0, param.kc, 0, param.kc.length);
+                System.arraycopy(sres, 0, param.sres, 0, param.sres.length);
+                params.add(param);
+            }
+            // The number of kc/sres pairs can either be 2 or 3 depending on the request.
+            if (params.size() > 3 || params.size() < 2) {
+                Log.e(TAG, "Malformed gsm auth response params: " + paramsStr);
+                return false;
+            }
+            return sendNetworkEapSimGsmAuthResponse(params);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + paramsStr, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean sendNetworkEapSimGsmAuthResponse(
+            ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params) {
+        synchronized (mLock) {
+            final String methodStr = "sendNetworkEapSimGsmAuthResponse";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =
+                        mISupplicantStaNetwork.sendNetworkEapSimGsmAuthResponse(params);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    public boolean sendNetworkEapSimGsmAuthFailure() {
+        synchronized (mLock) {
+            final String methodStr = "sendNetworkEapSimGsmAuthFailure";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaNetwork.sendNetworkEapSimGsmAuthFailure();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /**
+     * Send UMTS auth response.
+     *
+     * @param paramsStr Response params as a string.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendNetworkEapSimUmtsAuthResponse(String paramsStr) {
+        try {
+            Matcher match = UMTS_AUTH_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
+            if (!match.find() || match.groupCount() != 3) {
+                Log.e(TAG, "Malformed umts auth response params: " + paramsStr);
+                return false;
+            }
+            ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams params =
+                    new ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams();
+            byte[] ik = NativeUtil.hexStringToByteArray(match.group(1));
+            if (ik == null || ik.length != params.ik.length) {
+                Log.e(TAG, "Invalid ik value: " + match.group(1));
+                return false;
+            }
+            byte[] ck = NativeUtil.hexStringToByteArray(match.group(2));
+            if (ck == null || ck.length != params.ck.length) {
+                Log.e(TAG, "Invalid ck value: " + match.group(2));
+                return false;
+            }
+            byte[] res = NativeUtil.hexStringToByteArray(match.group(3));
+            if (res == null || res.length == 0) {
+                Log.e(TAG, "Invalid res value: " + match.group(3));
+                return false;
+            }
+            System.arraycopy(ik, 0, params.ik, 0, params.ik.length);
+            System.arraycopy(ck, 0, params.ck, 0, params.ck.length);
+            for (byte b : res) {
+                params.res.add(b);
+            }
+            return sendNetworkEapSimUmtsAuthResponse(params);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + paramsStr, e);
+            return false;
+        }
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean sendNetworkEapSimUmtsAuthResponse(
+            ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams params) {
+        synchronized (mLock) {
+            final String methodStr = "sendNetworkEapSimUmtsAuthResponse";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =
+                        mISupplicantStaNetwork.sendNetworkEapSimUmtsAuthResponse(params);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /**
+     * Send UMTS auts response.
+     *
+     * @param paramsStr Response params as a string.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendNetworkEapSimUmtsAutsResponse(String paramsStr) {
+        try {
+            Matcher match = UMTS_AUTS_RESPONSE_PARAMS_PATTERN.matcher(paramsStr);
+            if (!match.find() || match.groupCount() != 1) {
+                Log.e(TAG, "Malformed umts auts response params: " + paramsStr);
+                return false;
+            }
+            byte[] auts = NativeUtil.hexStringToByteArray(match.group(1));
+            if (auts == null || auts.length != 14) {
+                Log.e(TAG, "Invalid auts value: " + match.group(1));
+                return false;
+            }
+            return sendNetworkEapSimUmtsAutsResponse(auts);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + paramsStr, e);
+            return false;
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean sendNetworkEapSimUmtsAutsResponse(byte[/* 14 */] auts) {
+        synchronized (mLock) {
+            final String methodStr = "sendNetworkEapSimUmtsAutsResponse";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =
+                        mISupplicantStaNetwork.sendNetworkEapSimUmtsAutsResponse(auts);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    public boolean sendNetworkEapSimUmtsAuthFailure() {
+        synchronized (mLock) {
+            final String methodStr = "sendNetworkEapSimUmtsAuthFailure";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status = mISupplicantStaNetwork.sendNetworkEapSimUmtsAuthFailure();
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+    /**
+     * Send eap identity response.
+     *
+     * @param identityStr Identity as a string.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean sendNetworkEapIdentityResponse(String identityStr) {
+        try {
+            ArrayList<Byte> identity = NativeUtil.stringToByteArrayList(identityStr);
+            return sendNetworkEapIdentityResponse(identity);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + identityStr, e);
+            return false;
+        }
+    }
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private boolean sendNetworkEapIdentityResponse(ArrayList<Byte> identity) {
+        synchronized (mLock) {
+            final String methodStr = "sendNetworkEapIdentityResponse";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
+            try {
+                SupplicantStatus status =
+                        mISupplicantStaNetwork.sendNetworkEapIdentityResponse(identity);
+                return checkStatusAndLogFailure(status, methodStr);
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Retrieve the NFC token for this network.
+     *
+     * @return Hex string corresponding to the NFC token or null for failure.
+     */
+    public String getWpsNfcConfigurationToken() {
+        ArrayList<Byte> token = getWpsNfcConfigurationTokenInternal();
+        if (token == null) {
+            return null;
+        }
+        return NativeUtil.hexStringFromByteArray(NativeUtil.byteArrayFromArrayList(token));
+    }
+
+    /** See ISupplicantStaNetwork.hal for documentation */
+    private ArrayList<Byte> getWpsNfcConfigurationTokenInternal() {
+        synchronized (mLock) {
+            final String methodStr = "getWpsNfcConfigurationToken";
+            if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return null;
+            final Mutable<ArrayList<Byte>> gotToken = new Mutable<>();
+            try {
+                mISupplicantStaNetwork.getWpsNfcConfigurationToken(
+                        (SupplicantStatus status, ArrayList<Byte> token) -> {
+                            if (checkStatusAndLogFailure(status, methodStr)) {
+                                gotToken.value = token;
+                            }
+                        });
+            } catch (RemoteException e) {
+                handleRemoteException(e, methodStr);
+            }
+            return gotToken.value;
+        }
+    }
+
+    /**
+     * Returns true if provided status code is SUCCESS, logs debug message and returns false
+     * otherwise
+     */
+    private boolean checkStatusAndLogFailure(SupplicantStatus status, final String methodStr) {
+        if (status.code != SupplicantStatusCode.SUCCESS) {
+            Log.e(TAG, "ISupplicantStaNetwork." + methodStr + " failed: "
+                    + SupplicantStaIfaceHal.supplicantStatusCodeToString(status.code) + ", "
+                    + status.debugMessage);
+            return false;
+        } else {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "ISupplicantStaNetwork." + methodStr + " succeeded");
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Helper function to log callbacks.
+     */
+    private void logCallback(final String methodStr) {
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "ISupplicantStaNetworkCallback." + methodStr + " received");
+        }
+    }
+
+    /**
+     * Returns false if ISupplicantStaNetwork is null, and logs failure of methodStr
+     */
+    private boolean checkISupplicantStaNetworkAndLogFailure(final String methodStr) {
+        if (mISupplicantStaNetwork == null) {
+            Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaNetwork is null");
+            return false;
+        }
+        return true;
+    }
+
+    private void handleRemoteException(RemoteException e, String methodStr) {
+        mISupplicantStaNetwork = null;
+        Log.e(TAG, "ISupplicantStaNetwork." + methodStr + " failed with exception", e);
+    }
+
+    /**
+     * Adds FT flags for networks if the device supports it.
+     */
+    private BitSet addFastTransitionFlags(BitSet keyManagementFlags) {
+        if (!mSystemSupportsFastBssTransition) {
+            return keyManagementFlags;
+        }
+        BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
+        if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_PSK);
+        }
+        if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
+            modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_EAP);
+        }
+        return modifiedFlags;
+    }
+
+    /**
+     * Removes FT flags for networks if the device supports it.
+     */
+    private BitSet removeFastTransitionFlags(BitSet keyManagementFlags) {
+        BitSet modifiedFlags = (BitSet) keyManagementFlags.clone();
+        modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_PSK);
+        modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_EAP);
+        return modifiedFlags;
+    }
+
+    /**
+     * Creates the JSON encoded network extra using the map of string key, value pairs.
+     */
+    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 null;
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "Unable to serialize networkExtra: " + e.toString());
+            return null;
+        }
+        return encoded;
+    }
+
+    /**
+     * Parse the network extra JSON encoded string to a map of string key, value pairs.
+     */
+    public static Map<String, String> parseNetworkExtra(String encoded) {
+        if (TextUtils.isEmpty(encoded)) {
+            return null;
+        }
+        try {
+            // 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
+            // constructor will thrown a JSONException and the method will return null.
+            final JSONObject json = new JSONObject(URLDecoder.decode(encoded, "UTF-8"));
+            final Map<String, String> values = new HashMap<>();
+            final Iterator<?> it = json.keys();
+            while (it.hasNext()) {
+                final String key = (String) it.next();
+                final Object value = json.get(key);
+                if (value instanceof String) {
+                    values.put(key, (String) value);
+                }
+            }
+            return values;
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "Unable to deserialize networkExtra: " + e.toString());
+            return null;
+        } catch (JSONException e) {
+            // This is not necessarily an error. This exception will also occur if we encounter a
+            // legacy FQDN stored as a plain string. We want to return null in this case as no JSON
+            // dictionary of extras was found.
+            return null;
+        }
+    }
+
+    private static class Mutable<E> {
+        public E value;
+
+        Mutable() {
+            value = null;
+        }
+
+        Mutable(E value) {
+            this.value = value;
+        }
+    }
+
+    private class SupplicantStaNetworkHalCallback extends ISupplicantStaNetworkCallback.Stub {
+        /**
+         * Current configured network's framework network id.
+         */
+        private final int mFramewokNetworkId;
+        /**
+         * Current configured network's ssid.
+         */
+        private final String mSsid;
+
+        SupplicantStaNetworkHalCallback(int framewokNetworkId, String ssid) {
+            mFramewokNetworkId = framewokNetworkId;
+            mSsid = ssid;
+        }
+
+        @Override
+        public void onNetworkEapSimGsmAuthRequest(
+                ISupplicantStaNetworkCallback.NetworkRequestEapSimGsmAuthParams params) {
+            logCallback("onNetworkEapSimGsmAuthRequest");
+            synchronized (mLock) {
+                String[] data = new String[params.rands.size()];
+                int i = 0;
+                for (byte[] rand : params.rands) {
+                    data[i++] = NativeUtil.hexStringFromByteArray(rand);
+                }
+                mWifiMonitor.broadcastNetworkGsmAuthRequestEvent(
+                        mIfaceName, mFramewokNetworkId, mSsid, data);
+            }
+        }
+
+        @Override
+        public void onNetworkEapSimUmtsAuthRequest(
+                ISupplicantStaNetworkCallback.NetworkRequestEapSimUmtsAuthParams params) {
+            logCallback("onNetworkEapSimUmtsAuthRequest");
+            synchronized (mLock) {
+                String randHex = NativeUtil.hexStringFromByteArray(params.rand);
+                String autnHex = NativeUtil.hexStringFromByteArray(params.autn);
+                String[] data = {randHex, autnHex};
+                mWifiMonitor.broadcastNetworkUmtsAuthRequestEvent(
+                        mIfaceName, mFramewokNetworkId, mSsid, data);
+            }
+        }
+
+        @Override
+        public void onNetworkEapIdentityRequest() {
+            logCallback("onNetworkEapIdentityRequest");
+            synchronized (mLock) {
+                mWifiMonitor.broadcastNetworkIdentityRequestEvent(
+                        mIfaceName, mFramewokNetworkId, mSsid);
+            }
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/SupplicantStateTracker.java b/service/java/com/android/server/wifi/SupplicantStateTracker.java
index a7c21bf..cac7f06 100644
--- a/service/java/com/android/server/wifi/SupplicantStateTracker.java
+++ b/service/java/com/android/server/wifi/SupplicantStateTracker.java
@@ -26,7 +26,6 @@
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -49,12 +48,21 @@
     private static final String TAG = "SupplicantStateTracker";
     private static boolean DBG = false;
     private final WifiConfigManager mWifiConfigManager;
+    private FrameworkFacade mFacade;
     private final IBatteryStats mBatteryStats;
     /* Indicates authentication failure in supplicant broadcast.
      * TODO: enhance auth failure reporting to include notification
      * for all type of failures: EAP, WPS & WPA networks */
     private boolean mAuthFailureInSupplicantBroadcast = false;
 
+    /* Authentication failure reason
+     * see {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_NONE},
+     *     {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_TIMEOUT},
+     *     {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_WRONG_PSWD},
+     *     {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_EAP_FAILURE}
+     */
+    private int mAuthFailureReason;
+
     /* Maximum retries on a authentication failure notification */
     private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
 
@@ -71,6 +79,7 @@
     private final State mInactiveState = new InactiveState();
     private final State mDisconnectState = new DisconnectedState();
     private final State mScanState = new ScanState();
+    private final State mConnectionActiveState = new ConnectionActiveState();
     private final State mHandshakeState = new HandshakeState();
     private final State mCompletedState = new CompletedState();
     private final State mDormantState = new DormantState();
@@ -87,20 +96,25 @@
         return getCurrentState().getName();
     }
 
-    public SupplicantStateTracker(Context c, WifiConfigManager wcs, Handler t) {
+    public SupplicantStateTracker(Context c, WifiConfigManager wcs,
+            FrameworkFacade facade, Handler t) {
         super(TAG, t.getLooper());
 
         mContext = c;
         mWifiConfigManager = wcs;
-        mBatteryStats = (IBatteryStats)ServiceManager.getService(BatteryStats.SERVICE_NAME);
+        mFacade = facade;
+        mBatteryStats = mFacade.getBatteryService();
+        // CHECKSTYLE:OFF IndentationCheck
         addState(mDefaultState);
             addState(mUninitializedState, mDefaultState);
             addState(mInactiveState, mDefaultState);
             addState(mDisconnectState, mDefaultState);
-            addState(mScanState, mDefaultState);
-            addState(mHandshakeState, mDefaultState);
-            addState(mCompletedState, mDefaultState);
-            addState(mDormantState, mDefaultState);
+            addState(mConnectionActiveState, mDefaultState);
+                addState(mScanState, mConnectionActiveState);
+                addState(mHandshakeState, mConnectionActiveState);
+                addState(mCompletedState, mConnectionActiveState);
+                addState(mDormantState, mConnectionActiveState);
+        // CHECKSTYLE:ON IndentationCheck
 
         setInitialState(mUninitializedState);
         setLogRecSize(50);
@@ -118,9 +132,7 @@
 
         /* If other networks disabled during connection, enable them */
         if (mNetworksDisabledDuringConnect) {
-            mWifiConfigManager.enableAllNetworks();
-            mNetworksDisabledDuringConnect = false;
-        }
+            mNetworksDisabledDuringConnect = false; }
         /* update network status */
         mWifiConfigManager.updateNetworkSelectionStatus(netId, disableReason);
     }
@@ -167,6 +179,11 @@
     }
 
     private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
+        sendSupplicantStateChangedBroadcast(state, failedAuth, WifiManager.ERROR_AUTH_FAILURE_NONE);
+    }
+
+    private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth,
+            int reasonCode) {
         int supplState;
         switch (state) {
             case DISCONNECTED: supplState = BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED; break;
@@ -200,8 +217,11 @@
         intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
         if (failedAuth) {
             intent.putExtra(
-                WifiManager.EXTRA_SUPPLICANT_ERROR,
-                WifiManager.ERROR_AUTHENTICATING);
+                    WifiManager.EXTRA_SUPPLICANT_ERROR,
+                    WifiManager.ERROR_AUTHENTICATING);
+            intent.putExtra(
+                    WifiManager.EXTRA_SUPPLICANT_ERROR_REASON,
+                    reasonCode);
         }
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
@@ -221,12 +241,15 @@
             switch (message.what) {
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                     mAuthFailureInSupplicantBroadcast = true;
+                    mAuthFailureReason = message.arg2;
                     break;
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
                     SupplicantState state = stateChangeResult.state;
-                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
+                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast,
+                            mAuthFailureReason);
                     mAuthFailureInSupplicantBroadcast = false;
+                    mAuthFailureReason = WifiManager.ERROR_AUTH_FAILURE_NONE;
                     transitionOnSupplicantStateChange(stateChangeResult);
                     break;
                 case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
@@ -280,6 +303,19 @@
          }
     }
 
+    /* Meta-state that processes supplicant disconnections and broadcasts this event. */
+    class ConnectionActiveState extends State {
+        @Override
+        public boolean processMessage(Message message) {
+            if (message.what == WifiStateMachine.CMD_RESET_SUPPLICANT_STATE) {
+                sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
+            }
+
+            /* Let parent states handle the state possible transition. */
+            return NOT_HANDLED;
+        }
+    }
+
     class HandshakeState extends State {
         /**
          * The max number of the WPA supplicant loop iterations before we
@@ -315,7 +351,7 @@
                         }
                         mLoopDetectIndex = state.ordinal();
                         sendSupplicantStateChangedBroadcast(state,
-                                mAuthFailureInSupplicantBroadcast);
+                                mAuthFailureInSupplicantBroadcast, mAuthFailureReason);
                     } else {
                         //Have the DefaultState handle the transition
                         return NOT_HANDLED;
@@ -334,7 +370,6 @@
              if (DBG) Log.d(TAG, getName() + "\n");
              /* Reset authentication failure count */
              if (mNetworksDisabledDuringConnect) {
-                 mWifiConfigManager.enableAllNetworks();
                  mNetworksDisabledDuringConnect = false;
              }
         }
@@ -345,7 +380,8 @@
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
                     SupplicantState state = stateChangeResult.state;
-                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
+                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast,
+                            mAuthFailureReason);
                     /* Ignore any connecting state in completed state. Group re-keying
                      * events and other auth events that do not affect connectivity are
                      * ignored
@@ -355,10 +391,6 @@
                     }
                     transitionOnSupplicantStateChange(stateChangeResult);
                     break;
-                case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
-                    sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
-                    transitionTo(mUninitializedState);
-                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -378,6 +410,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         super.dump(fd, pw, args);
         pw.println("mAuthFailureInSupplicantBroadcast " + mAuthFailureInSupplicantBroadcast);
+        pw.println("mAuthFailureReason " + mAuthFailureReason);
         pw.println("mNetworksDisabledDuringConnect " + mNetworksDisabledDuringConnect);
         pw.println();
     }
diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java
index bcd8d03..9c90bcf 100644
--- a/service/java/com/android/server/wifi/WifiApConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiApConfigStore.java
@@ -32,6 +32,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Random;
 import java.util.UUID;
 
 /**
@@ -46,6 +47,9 @@
 
     private static final int AP_CONFIG_FILE_VERSION = 2;
 
+    private static final int RAND_SSID_INT_MIN = 1000;
+    private static final int RAND_SSID_INT_MAX = 9999;
+
     private WifiConfiguration mWifiApConfig = null;
 
     private ArrayList<Integer> mAllowed2GChannel = null;
@@ -191,11 +195,33 @@
     private WifiConfiguration getDefaultApConfiguration() {
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = mContext.getResources().getString(
-                R.string.wifi_tether_configure_ssid_default);
+                R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid();
         config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
         String randomUUID = UUID.randomUUID().toString();
         //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
         config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
         return config;
     }
+
+    private static int getRandomIntForDefaultSsid() {
+        Random random = new Random();
+        return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN;
+    }
+
+    /**
+     * Generate a temporary WPA2 based configuration for use by the local only hotspot.
+     * This config is not persisted and will not be stored by the WifiApConfigStore.
+     */
+    public static WifiConfiguration generateLocalOnlyHotspotConfig(Context context) {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = context.getResources().getString(
+              R.string.wifi_localhotspot_configure_ssid_default) + "_"
+                      + getRandomIntForDefaultSsid();
+        config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
+        config.networkId = WifiConfiguration.LOCAL_ONLY_NETWORK_ID;
+        String randomUUID = UUID.randomUUID().toString();
+        // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+        config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
+        return config;
+    }
 }
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..60c3b48
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiBackupRestore.java
@@ -0,0 +1,776 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.IpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.os.Process;
+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.NativeUtil;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+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";
+
+    private final WifiPermissionsUtil mWifiPermissionsUtil;
+    /**
+     * 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;
+
+    public WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil) {
+        mWifiPermissionsUtil = wifiPermissionsUtil;
+    }
+
+    /**
+     * 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 new byte[0];
+        }
+
+        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 new byte[0];
+    }
+
+    /**
+     * 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()) {
+                continue;
+            }
+            if (!mWifiPermissionsUtil.checkConfigOverridePermission(configuration.creatorUid)) {
+                Log.d(TAG, "Ignoring network from an app with no config override permission: "
+                        + 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 = "id_str";
+
+        /**
+         * 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 processing 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);
+                    if (idString != null) {
+                        Map<String, String> extras =
+                                SupplicantStaNetworkHal.parseNetworkExtra(
+                                        NativeUtil.removeEnclosingQuotes(idString));
+                        String configKey = extras.get(
+                                SupplicantStaNetworkHal.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());
+                        }
+                        // For wpa_supplicant backup data, parse out the creatorUid to ensure that
+                        // these networks were created by system apps.
+                        int creatorUid =
+                                Integer.parseInt(extras.get(
+                                        SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID));
+                        if (creatorUid >= Process.FIRST_APPLICATION_UID) {
+                            Log.d(TAG, "Ignoring network from non-system app: "
+                                    + configuration.configKey());
+                            return null;
+                        }
+                    }
+                }
+                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 5eff02e..25a5a20 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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.
@@ -16,8 +16,7 @@
 
 package com.android.server.wifi;
 
-import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
-
+import android.app.ActivityManager;
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ContentResolver;
@@ -25,1234 +24,1118 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.net.IpConfiguration;
-import android.net.IpConfiguration.IpAssignment;
-import android.net.IpConfiguration.ProxySettings;
-import android.net.NetworkInfo.DetailedState;
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
-import android.net.wifi.PasspointManagementObjectDefinition;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiConfiguration.Status;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
-import android.net.wifi.WpsInfo;
-import android.net.wifi.WpsResult;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.os.SystemClock;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.security.KeyStore;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
-import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
-import com.android.server.net.DelayedDiskWrite;
-import com.android.server.net.IpConfigStore;
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.ANQPFactory;
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.hotspot2.ANQPData;
-import com.android.server.wifi.hotspot2.AnqpCache;
-import com.android.server.wifi.hotspot2.IconEvent;
-import com.android.server.wifi.hotspot2.NetworkDetail;
-import com.android.server.wifi.hotspot2.PasspointMatch;
-import com.android.server.wifi.hotspot2.SupplicantBridge;
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
-import com.android.server.wifi.hotspot2.pps.Credential;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
+import com.android.server.wifi.WifiConfigStoreLegacy.WifiConfigStoreDataLegacy;
+import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.util.ScanResultUtil;
+import com.android.server.wifi.util.TelephonyUtil;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
-import org.xml.sax.SAXException;
+import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.BufferedReader;
-import java.io.DataOutputStream;
-import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.security.cert.X509Certificate;
-import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.zip.CRC32;
-import java.util.zip.Checksum;
-
 
 /**
- * This class provides the API to manage configured
- * wifi networks. The API is not thread safe is being
- * used only from WifiStateMachine.
+ * 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.
  *
- * It deals with the following
- * - Add/update/remove a WifiConfiguration
- *   The configuration contains two types of information.
- *     = IP and proxy configuration that is handled by WifiConfigManager and
- *       is saved to disk on any change.
+ * 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.
  *
- *       The format of configuration file is as follows:
- *       <version>
- *       <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
- *       <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
- *       ..
- *
- *       (key, value) pairs for a given network are grouped together and can
- *       be in any order. A EOS at the end of a set of (key, value) pairs
- *       indicates that the next set of (key, value) pairs are for a new
- *       network. A network is identified by a unique ID_KEY. If there is no
- *       ID_KEY in the (key, value) pairs, the data is discarded.
- *
- *       An invalid version on read would result in discarding the contents of
- *       the file. On the next write, the latest version is written to file.
- *
- *       Any failures during read or write to the configuration file are ignored
- *       without reporting to the user since the likelihood of these errors are
- *       low and the impact on connectivity is low.
- *
- *     = SSID & security details that is pushed to the supplicant.
- *       supplicant saves these details to the disk on calling
- *       saveConfigCommand().
- *
- *       We have two kinds of APIs exposed:
- *        > public API calls that provide fine grained control
- *          - enableNetwork, disableNetwork, addOrUpdateNetwork(),
- *          removeNetwork(). For these calls, the config is not persisted
- *          to the disk. (TODO: deprecate these calls in WifiManager)
- *        > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
- *          These calls persist the supplicant config to disk.
- *
- * - Maintain a list of configured networks for quick access
- *
+ * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
  */
 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;
-    public static final int MAX_TX_PACKET_FOR_PARTIAL_SCANS = 40;
-    public static final int MAX_RX_PACKET_FOR_PARTIAL_SCANS = 80;
-    public static final boolean ROAM_ON_ANY = false;
-    public static final int MAX_NUM_SCAN_CACHE_ENTRIES = 128;
-    private static final boolean DBG = true;
-    private static final String PPS_FILE = "/data/misc/wifi/PerProviderSubscription.conf";
-    private static final String IP_CONFIG_FILE =
-            Environment.getDataDirectory() + "/misc/wifi/ipconfig.txt";
-
-    // The Wifi verbose log is provided as a way to persist the verbose logging settings
-    // for testing purpose.
-    // It is not intended for normal use.
-    private static final String WIFI_VERBOSE_LOGS_KEY = "WIFI_VERBOSE_LOGS";
-
-    // As we keep deleted PSK WifiConfiguration for a while, the PSK of
-    // those deleted WifiConfiguration is set to this random unused PSK
-    private static final String DELETED_CONFIG_PSK = "Mjkd86jEMGn79KhKll298Uu7-deleted";
-
     /**
-     * The maximum number of times we will retry a connection to an access point
-     * for which we have failed in acquiring an IP address from DHCP. A value of
-     * N means that we will make N+1 connection attempts in all.
-     * <p>
-     * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
-     * value if a Settings value is not present.
+     * String used to mask passwords to public interface.
      */
-    private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
-
+    @VisibleForTesting
+    public static final String PASSWORD_MASK = "*";
     /**
-     * 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.
+     * Package name for SysUI. This is used to lookup the UID of SysUI which is used to allow
+     * Quick settings to modify network configurations.
      */
-    private static final int[] NETWORK_SELECTION_DISABLE_THRESHOLD = {
+    @VisibleForTesting
+    public static final String SYSUI_PACKAGE_NAME = "com.android.systemui";
+    /**
+     * 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 (deprecated)
+            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_WIFI_MANAGER
+            1   //  threshold for DISABLED_BY_USER_SWITCH
     };
-
     /**
-     * Timeout for each kind of error. After the timeout minutes, unblock the network again.
+     * 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}.
      */
-    private static final int[] NETWORK_SELECTION_DISABLE_TIMEOUT = {
+    @VisibleForTesting
+    public static final int[] NETWORK_SELECTION_DISABLE_TIMEOUT_MS = {
             Integer.MAX_VALUE,  // threshold for NETWORK_SELECTION_ENABLE
-            15,                 // threshold for DISABLED_BAD_LINK (deprecated)
-            5,                  // threshold for DISABLED_ASSOCIATION_REJECTION
-            5,                  // threshold for DISABLED_AUTHENTICATION_FAILURE
-            5,                  // threshold for DISABLED_DHCP_FAILURE
-            5,                  // threshold for DISABLED_DNS_FAILURE
+            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_WIFI_MANAGER
+            Integer.MAX_VALUE   // threshold for DISABLED_BY_USER_SWITCH
     };
-
-    public final AtomicBoolean mEnableAutoJoinWhenAssociated = new AtomicBoolean();
-    public final AtomicBoolean mEnableChipWakeUpWhenAssociated = new AtomicBoolean(true);
-    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();
-
-    public boolean mEnableLinkDebouncing;
-    public boolean mEnableWifiCellularHandoverUserTriggeredAdjustment;
-    public int mNetworkSwitchingBlackListPeriodMs;
-    public int mBadLinkSpeed24;
-    public int mBadLinkSpeed5;
-    public int mGoodLinkSpeed24;
-    public int mGoodLinkSpeed5;
-
-    // These fields are non-final for testing.
-    public AtomicInteger mThresholdQualifiedRssi5 = new AtomicInteger();
-    public AtomicInteger mThresholdMinimumRssi5 = new AtomicInteger();
-    public AtomicInteger mThresholdSaturatedRssi24 = new AtomicInteger();
-    public AtomicInteger mThresholdMinimumRssi24 = new AtomicInteger();
-    public AtomicInteger mCurrentNetworkBoost = new AtomicInteger();
-    public AtomicInteger mBandAward5Ghz = new AtomicInteger();
-
-    // Indicates whether the system is capable of 802.11r fast BSS transition.
-    private boolean mSystemSupportsFastBssTransition = false;
-
     /**
-     * Framework keeps a list of ephemeral SSIDs that where deleted by user,
-     * so as, framework knows not to autojoin 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.
+     * Interface for other modules to listen to the saved network updated
+     * events.
      */
-    public Set<String> mDeletedEphemeralSSIDs = new HashSet<String>();
-
-    /* configured networks with network id as the key */
-    private final ConfigurationMap mConfiguredNetworks;
-
-    /*
-     * Stores whether carrier networks are configured.
-     * This information is provided externally from the CarrierConfig.
-     */
-    private boolean mHasCarrierConfiguredNetworks;
-
-    private final LocalLog mLocalLog;
-    private final KeyStore mKeyStore;
-    private final WifiNetworkHistory mWifiNetworkHistory;
-    private final WifiConfigStore mWifiConfigStore;
-    private final AnqpCache mAnqpCache;
-    private final SupplicantBridge mSupplicantBridge;
-    private final SupplicantBridgeCallbacks mSupplicantBridgeCallbacks;
-    private final PasspointManagementObjectManager mMOManager;
-    private final boolean mEnableOsuQueries;
-    private final SIMAccessor mSIMAccessor;
-    private final UserManager mUserManager;
-    private final Object mActiveScanDetailLock = new Object();
-
-    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 */
-    private ConcurrentHashMap<Integer, ScanDetailCache> mScanDetailCaches;
-
-    /* Tracks the highest priority of configured networks */
-    private int mLastPriority = -1;
-
-    /**
-     * The mLastSelectedConfiguration is used to remember which network
-     * was selected last by the user.
-     * The connection to this network may not be successful, as well
-     * the selection (i.e. network priority) might not be persisted.
-     * WiFi state machine is the only object that sets this variable.
-     */
-    private String mLastSelectedConfiguration = null;
-    private long mLastSelectedTimeStamp =
-            WifiConfiguration.NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
-
-    /*
-     * Lost config list, whenever we read a config from networkHistory.txt that was not in
-     * wpa_supplicant.conf
-     */
-    private HashSet<String> mLostConfigsDbg = new HashSet<String>();
-
-    private ScanDetail mActiveScanDetail;   // ScanDetail associated with active network
-
-    private class SupplicantBridgeCallbacks implements SupplicantBridge.SupplicantBridgeCallbacks {
-        @Override
-        public void notifyANQPResponse(ScanDetail scanDetail,
-                                       Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
-            updateAnqpCache(scanDetail, anqpElements);
-            if (anqpElements == null || anqpElements.isEmpty()) {
-                return;
-            }
-            scanDetail.propagateANQPInfo(anqpElements);
-
-            Map<HomeSP, PasspointMatch> matches = matchNetwork(scanDetail, false);
-            Log.d(Utils.hs2LogTag(getClass()), scanDetail.getSSID() + " pass 2 matches: "
-                    + toMatchString(matches));
-
-            cacheScanResultForPasspointConfigs(scanDetail, matches, null);
-        }
-        @Override
-        public void notifyIconFailed(long bssid) {
-            Intent intent = new Intent(WifiManager.PASSPOINT_ICON_RECEIVED_ACTION);
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            intent.putExtra(WifiManager.EXTRA_PASSPOINT_ICON_BSSID, bssid);
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-        }
-
-    }
-
-    WifiConfigManager(Context context, WifiNative wifiNative, FrameworkFacade facade, Clock clock,
-            UserManager userManager, KeyStore keyStore) {
-        mContext = context;
-        mFacade = facade;
-        mClock = clock;
-        mKeyStore = keyStore;
-        mUserManager = userManager;
-
-        if (mShowNetworks) {
-            mLocalLog = wifiNative.getLocalLog();
-        } else {
-            mLocalLog = null;
-        }
-
-        mOnlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
-                R.bool.config_wifi_only_link_same_credential_configurations);
-        mMaxNumActiveChannelsForPartialScans.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels));
-        mEnableLinkDebouncing = mContext.getResources().getBoolean(
-                R.bool.config_wifi_enable_disconnection_debounce);
-        mBandAward5Ghz.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_5GHz_preference_boost_factor));
-        mThresholdMinimumRssi5.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz));
-        mThresholdQualifiedRssi5.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz));
-        mThresholdSaturatedRssi5.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz));
-        mThresholdMinimumRssi24.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz));
-        mThresholdQualifiedRssi24.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz));
-        mThresholdSaturatedRssi24.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz));
-        mEnableWifiCellularHandoverUserTriggeredAdjustment = mContext.getResources().getBoolean(
-                R.bool.config_wifi_framework_cellular_handover_enable_user_triggered_adjustment);
-        mBadLinkSpeed24 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_link_speed_24);
-        mBadLinkSpeed5 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_bad_link_speed_5);
-        mGoodLinkSpeed24 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_link_speed_24);
-        mGoodLinkSpeed5 = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_wifi_score_good_link_speed_5);
-        mEnableAutoJoinWhenAssociated.set(mContext.getResources().getBoolean(
-                R.bool.config_wifi_framework_enable_associated_network_selection));
-        mCurrentNetworkBoost.set(mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_current_network_boost));
-        mNetworkSwitchingBlackListPeriodMs = mContext.getResources().getInteger(
-                R.integer.config_wifi_network_switching_blacklist_time);
-        mSystemSupportsFastBssTransition = mContext.getResources().getBoolean(
-                R.bool.config_wifi_fast_bss_transition_enabled);
-
-        boolean hs2on = mContext.getResources().getBoolean(R.bool.config_wifi_hotspot2_enabled);
-        Log.d(Utils.hs2LogTag(getClass()), "Passpoint is " + (hs2on ? "enabled" : "disabled"));
-
-        mConfiguredNetworks = new ConfigurationMap(userManager);
-        mMOManager = new PasspointManagementObjectManager(new File(PPS_FILE), hs2on);
-        mEnableOsuQueries = true;
-        mAnqpCache = new AnqpCache(mClock);
-        mSupplicantBridgeCallbacks = new SupplicantBridgeCallbacks();
-        mSupplicantBridge = new SupplicantBridge(wifiNative, mSupplicantBridgeCallbacks);
-        mScanDetailCaches = new ConcurrentHashMap<>(16, 0.75f, 2);
-        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);
-    }
-
-    public void trimANQPCache(boolean all) {
-        mAnqpCache.clear(all, DBG);
-    }
-
-    void enableVerboseLogging(int verbose) {
-        mEnableVerboseLogging.set(verbose);
-        if (verbose > 0) {
-            sVDBG = true;
-            mShowNetworks = true;
-        } else {
-            sVDBG = false;
-        }
-        if (verbose > 1) {
-            sVVDBG = true;
-        } else {
-            sVVDBG = false;
-        }
-    }
-
-    /**
-     * Fetch the list of configured networks
-     * and enable all stored networks in supplicant.
-     */
-    void loadAndEnableAllNetworks() {
-        if (DBG) log("Loading config and enabling all networks ");
-        loadConfiguredNetworks();
-        enableAllNetworks();
-    }
-
-    int getConfiguredNetworksSize() {
-        return mConfiguredNetworks.sizeForCurrentUser();
-    }
-
-    /**
-     * Fetch the list of currently saved networks (i.e. all configured networks, excluding
-     * ephemeral networks).
-     * @param pskMap Map of preSharedKeys, keyed by the configKey of the configuration the
-     * preSharedKeys belong to
-     * @return List of networks
-     */
-    private List<WifiConfiguration> getSavedNetworks(Map<String, String> pskMap) {
-        List<WifiConfiguration> networks = new ArrayList<>();
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            WifiConfiguration newConfig = new WifiConfiguration(config);
-            // When updating this condition, update WifiStateMachine's CONNECT_NETWORK handler to
-            // correctly handle updating existing configs that are filtered out here.
-            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;
-            }
-
-            if (pskMap != null && config.allowedKeyManagement != null
-                    && config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
-                    && pskMap.containsKey(config.configKey(true))) {
-                newConfig.preSharedKey = pskMap.get(config.configKey(true));
-            }
-            networks.add(newConfig);
-        }
-        return networks;
-    }
-
-    /**
-     * This function returns all configuration, and is used for debug and creating bug reports.
-     */
-    private List<WifiConfiguration> getAllConfiguredNetworks() {
-        List<WifiConfiguration> networks = new ArrayList<>();
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            WifiConfiguration newConfig = new WifiConfiguration(config);
-            networks.add(newConfig);
-        }
-        return networks;
-    }
-
-    /**
-     * Fetch the list of currently saved networks (i.e. all configured networks, excluding
-     * ephemeral networks).
-     * @return List of networks
-     */
-    public List<WifiConfiguration> getSavedNetworks() {
-        return getSavedNetworks(null);
-    }
-
-    /**
-     * Check if Carrier networks have ben configured.
-     * @return true if carrier networks are present else false.
-     */
-    public boolean hasCarrierNetworks() {
-        return mHasCarrierConfiguredNetworks;
-    }
-
-    /**
-     * Set true/false depending on whether Carrier networks have been configured.
-     * @param hasCarrierNetworks if Carrier networks have been configured.
-     */
-    public void setHasCarrierNetworks(boolean hasCarrierNetworks) {
-        mHasCarrierConfiguredNetworks = hasCarrierNetworks;
-    }
-
-    /**
-     * Fetch the list of currently saved networks (i.e. all configured networks, excluding
-     * ephemeral networks), filled with real preSharedKeys.
-     * @return List of networks
-     */
-    List<WifiConfiguration> getPrivilegedSavedNetworks() {
-        Map<String, String> pskMap = getCredentialsByConfigKeyMap();
-        List<WifiConfiguration> configurations = getSavedNetworks(pskMap);
-        for (WifiConfiguration configuration : configurations) {
-            try {
-                configuration
-                        .setPasspointManagementObjectTree(mMOManager.getMOTree(configuration.FQDN));
-            } catch (IOException ioe) {
-                Log.w(TAG, "Failed to parse MO from " + configuration.FQDN + ": " + ioe);
-            }
-        }
-        return configurations;
-    }
-
-    /**
-     * Fetch the list of networkId's which are hidden in current user's configuration.
-     * @return List of networkIds
-     */
-    public Set<Integer> getHiddenConfiguredNetworkIds() {
-        return mConfiguredNetworks.getHiddenNetworkIdsForCurrentUser();
-    }
-
-    /**
-     * Find matching network for this scanResult
-     */
-    WifiConfiguration getMatchingConfig(ScanResult scanResult) {
-        if (scanResult == null) {
-            return null;
-        }
-        for (Map.Entry entry : mScanDetailCaches.entrySet()) {
-            Integer netId = (Integer) entry.getKey();
-            ScanDetailCache cache = (ScanDetailCache) entry.getValue();
-            WifiConfiguration config = getWifiConfiguration(netId);
-            if (config == null) {
-                continue;
-            }
-            if (cache.get(scanResult.BSSID) != null) {
-                return config;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Fetch the preSharedKeys for all networks.
-     * @return a map from configKey to preSharedKey.
-     */
-    private Map<String, String> getCredentialsByConfigKeyMap() {
-        return readNetworkVariablesFromSupplicantFile("psk");
-    }
-
-    /**
-     * 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) {
-        WifiConfiguration config = getWifiConfiguration(info.getNetworkId());
-        if (config != null && getScanDetailCache(config) != null) {
-            ScanDetail scanDetail = getScanDetailCache(config).getScanDetail(info.getBSSID());
-            if (scanDetail != null) {
-                ScanResult result = scanDetail.getScanResult();
-                long previousSeen = result.seen;
-                int previousRssi = result.level;
-
-                // Update the scan result
-                scanDetail.setSeen();
-                result.level = info.getRssi();
-
-                // Average the RSSI value
-                result.averageRssi(previousRssi, previousSeen,
-                        WifiQualifiedNetworkSelector.SCAN_RESULT_MAXIMUNM_AGE);
-                if (sVDBG) {
-                    logd("updateConfiguration freq=" + result.frequency
-                            + " BSSID=" + result.BSSID
-                            + " RSSI=" + result.level
-                            + " " + config.configKey());
-                }
-            }
-        }
-    }
-
-    /**
-     * get the Wificonfiguration for this netId
-     *
-     * @return Wificonfiguration
-     */
-    public WifiConfiguration getWifiConfiguration(int netId) {
-        return mConfiguredNetworks.getForCurrentUser(netId);
-    }
-
-    /**
-     * Get the Wificonfiguration for this key
-     * @return Wificonfiguration
-     */
-    public WifiConfiguration getWifiConfiguration(String key) {
-        return mConfiguredNetworks.getByConfigKeyForCurrentUser(key);
-    }
-
-    /**
-     * Enable all networks (if disabled time expire) and save config. This will be a no-op if the
-     * list of configured networks indicates all networks as being enabled
-     */
-    void enableAllNetworks() {
-        boolean networkEnabledStateChanged = false;
-
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            if (config != null && !config.ephemeral
-                    && !config.getNetworkSelectionStatus().isNetworkEnabled()) {
-                if (tryEnableQualifiedNetwork(config)) {
-                    networkEnabledStateChanged = true;
-                }
-            }
-        }
-
-        if (networkEnabledStateChanged) {
-            saveConfig();
-            sendConfiguredNetworksChangedBroadcast();
-        }
-    }
-
-    private boolean setNetworkPriorityNative(WifiConfiguration config, int priority) {
-        return mWifiConfigStore.setNetworkPriority(config, priority);
-    }
-
-    private boolean setSSIDNative(WifiConfiguration config, String ssid) {
-        return mWifiConfigStore.setNetworkSSID(config, ssid);
-    }
-
-    public boolean updateLastConnectUid(WifiConfiguration config, int uid) {
-        if (config != null) {
-            if (config.lastConnectUid != uid) {
-                config.lastConnectUid = uid;
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Selects the specified network for connection. This involves
-     * updating the priority of all the networks and enabling the given
-     * network while disabling others.
-     *
-     * Selecting a network will leave the other networks disabled and
-     * a call to enableAllNetworks() needs to be issued upon a connection
-     * or a failure event from supplicant
-     *
-     * @param config network to select for connection
-     * @param updatePriorities makes config highest priority network
-     * @return false if the network id is invalid
-     */
-    boolean selectNetwork(WifiConfiguration config, boolean updatePriorities, int uid) {
-        if (sVDBG) localLogNetwork("selectNetwork", config.networkId);
-        if (config.networkId == INVALID_NETWORK_ID) return false;
-        if (!WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                mUserManager.getProfiles(mCurrentUserId))) {
-            loge("selectNetwork " + Integer.toString(config.networkId) + ": Network config is not "
-                    + "visible to current user.");
-            return false;
-        }
-
-        // Reset the priority of each network at start or if it goes too high.
-        if (mLastPriority == -1 || mLastPriority > 1000000) {
-            if (updatePriorities) {
-                for (WifiConfiguration config2 : mConfiguredNetworks.valuesForCurrentUser()) {
-                    if (config2.networkId != INVALID_NETWORK_ID) {
-                        setNetworkPriorityNative(config2, 0);
-                    }
-                }
-            }
-            mLastPriority = 0;
-        }
-
-        // Set to the highest priority and save the configuration.
-        if (updatePriorities) {
-            setNetworkPriorityNative(config, ++mLastPriority);
-        }
-
-        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
-            // selectQualifiedNetwork.selectQualifiedNetwork(), when the qualified network selected
-            // is a Passpoint network.
-            logd("Setting SSID for WPA supplicant network " + config.networkId + " to "
-                    + config.SSID);
-            setSSIDNative(config, config.SSID);
-        }
-
-        mWifiConfigStore.enableHS20(config.isPasspoint());
-
-        if (updatePriorities) {
-            saveConfig();
-        }
-
-        updateLastConnectUid(config, uid);
-
-        writeKnownNetworkHistory();
-
-        /* Enable the given network while disabling all other networks */
-        selectNetworkWithoutBroadcast(config.networkId);
-
-       /* Avoid saving the config & sending a broadcast to prevent settings
-        * from displaying a disabled list of networks */
-        return true;
-    }
-
-    /**
-     * Add/update the specified configuration and save config
-     *
-     * @param config WifiConfiguration to be saved
-     * @return network update result
-     */
-    NetworkUpdateResult saveNetwork(WifiConfiguration config, int uid) {
-        WifiConfiguration conf;
-
-        // A new network cannot have null SSID
-        if (config == null || (config.networkId == INVALID_NETWORK_ID && config.SSID == null)) {
-            return new NetworkUpdateResult(INVALID_NETWORK_ID);
-        }
-
-        if (!WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                mUserManager.getProfiles(mCurrentUserId))) {
-            return new NetworkUpdateResult(INVALID_NETWORK_ID);
-        }
-
-        if (sVDBG) localLogNetwork("WifiConfigManager: saveNetwork netId", config.networkId);
-        if (sVDBG) {
-            logd("WifiConfigManager saveNetwork,"
-                    + " size=" + Integer.toString(mConfiguredNetworks.sizeForAllUsers())
-                    + " (for all users)"
-                    + " SSID=" + config.SSID
-                    + " Uid=" + Integer.toString(config.creatorUid)
-                    + "/" + Integer.toString(config.lastUpdateUid));
-        }
-
-        if (mDeletedEphemeralSSIDs.remove(config.SSID)) {
-            if (sVDBG) {
-                logd("WifiConfigManager: removed from ephemeral blacklist: " + config.SSID);
-            }
-            // NOTE: This will be flushed to disk as part of the addOrUpdateNetworkNative call
-            // below, since we're creating/modifying a config.
-        }
-
-        boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
-        NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid);
-        int netId = result.getNetworkId();
-
-        if (sVDBG) 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);
-
-                // reenable autojoin, since new information has been provided
-                updateNetworkSelectionStatus(netId,
-                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-            }
-            if (sVDBG) {
-                logd("WifiConfigManager: saveNetwork got config back netId="
-                        + Integer.toString(netId)
-                        + " uid=" + Integer.toString(config.creatorUid));
-            }
-        }
-
-        saveConfig();
-        sendConfiguredNetworksChangedBroadcast(
-                conf,
-                result.isNewNetwork()
-                        ? WifiManager.CHANGE_REASON_ADDED
-                        : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
-        return result;
-    }
-
-    void noteRoamingFailure(WifiConfiguration config, int reason) {
-        if (config == null) return;
-        config.lastRoamingFailure = mClock.currentTimeMillis();
-        config.roamingFailureBlackListTimeMilli =
-                2 * (config.roamingFailureBlackListTimeMilli + 1000);
-        if (config.roamingFailureBlackListTimeMilli > mNetworkSwitchingBlackListPeriodMs) {
-            config.roamingFailureBlackListTimeMilli = mNetworkSwitchingBlackListPeriodMs;
-        }
-        config.lastRoamingFailureReason = reason;
-    }
-
-    void saveWifiConfigBSSID(WifiConfiguration config, String bssid) {
-        mWifiConfigStore.setNetworkBSSID(config, bssid);
-    }
-
-
-    void updateStatus(int netId, DetailedState state) {
-        if (netId != INVALID_NETWORK_ID) {
-            WifiConfiguration config = mConfiguredNetworks.getForAllUsers(netId);
-            if (config == null) return;
-            switch (state) {
-                case CONNECTED:
-                    config.status = Status.CURRENT;
-                    //we successfully connected, hence remove the blacklist
-                    updateNetworkSelectionStatus(netId,
-                            WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-                    break;
-                case DISCONNECTED:
-                    //If network is already disabled, keep the status
-                    if (config.status == Status.CURRENT) {
-                        config.status = Status.ENABLED;
-                    }
-                    break;
-                default:
-                    //do nothing, retain the existing state
-                    break;
-            }
-        }
-    }
-
-
-    /**
-     * Disable an ephemeral SSID for the purpose of auto-joining thru scored.
-     * This SSID will never be scored anymore.
-     * The only way to "un-disable it" is if the user create a network for that SSID and then
-     * forget it.
-     *
-     * @param ssid caller must ensure that the SSID passed thru this API match
-     *            the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
-     * @return the {@link WifiConfiguration} corresponding to this SSID, if any, so that we can
-     *         disconnect if this is the current network.
-     */
-    WifiConfiguration disableEphemeralNetwork(String ssid) {
-        if (ssid == null) {
-            return null;
-        }
-
-        WifiConfiguration foundConfig = mConfiguredNetworks.getEphemeralForCurrentUser(ssid);
-
-        mDeletedEphemeralSSIDs.add(ssid);
-        logd("Forget ephemeral SSID " + ssid + " num=" + mDeletedEphemeralSSIDs.size());
-
-        if (foundConfig != null) {
-            logd("Found ephemeral config in disableEphemeralNetwork: " + foundConfig.networkId);
-        }
-
-        writeKnownNetworkHistory();
-        return foundConfig;
-    }
-
-    /**
-     * Forget the specified network and save config
-     *
-     * @param netId network to forget
-     * @return {@code true} if it succeeds, {@code false} otherwise
-     */
-    boolean forgetNetwork(int netId) {
-        if (mShowNetworks) localLogNetwork("forgetNetwork", netId);
-        if (!removeNetwork(netId)) {
-            loge("Failed to forget network " + netId);
-            return false;
-        }
-        saveConfig();
-        writeKnownNetworkHistory();
-        return true;
-    }
-
-    /**
-     * Add/update a network. Note that there is no saveConfig operation.
-     * This function is retained for compatibility with the public
-     * API. The more powerful saveNetwork() is used by the
-     * state machine
-     *
-     * @param config wifi configuration to add/update
-     * @return network Id
-     */
-    int addOrUpdateNetwork(WifiConfiguration config, int uid) {
-        if (config == null || !WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                mUserManager.getProfiles(mCurrentUserId))) {
-            return WifiConfiguration.INVALID_NETWORK_ID;
-        }
-
-        if (mShowNetworks) localLogNetwork("addOrUpdateNetwork id=", config.networkId);
-        if (config.isPasspoint()) {
-            /* create a temporary SSID with providerFriendlyName */
-            Long csum = getChecksum(config.FQDN);
-            config.SSID = csum.toString();
-            config.enterpriseConfig.setDomainSuffixMatch(config.FQDN);
-        }
-
-        NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid);
-        if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
-            WifiConfiguration conf = mConfiguredNetworks.getForCurrentUser(result.getNetworkId());
-            if (conf != null) {
-                sendConfiguredNetworksChangedBroadcast(
-                        conf,
-                        result.isNewNetwork
-                                ? WifiManager.CHANGE_REASON_ADDED
-                                : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
-            }
-        }
-
-        return result.getNetworkId();
-    }
-
-    public int addPasspointManagementObject(String managementObject) {
-        try {
-            mMOManager.addSP(managementObject);
-            return 0;
-        } catch (IOException | SAXException e) {
-            return -1;
-        }
-    }
-
-    public int modifyPasspointMo(String fqdn, List<PasspointManagementObjectDefinition> mos) {
-        try {
-            return mMOManager.modifySP(fqdn, mos);
-        } catch (IOException | SAXException e) {
-            return -1;
-        }
-    }
-
-    public boolean queryPasspointIcon(long bssid, String fileName) {
-        return mSupplicantBridge.doIconQuery(bssid, fileName);
-    }
-
-    public int matchProviderWithCurrentNetwork(String fqdn) {
-        ScanDetail scanDetail = null;
-        synchronized (mActiveScanDetailLock) {
-            scanDetail = mActiveScanDetail;
-        }
-        if (scanDetail == null) {
-            return PasspointMatch.None.ordinal();
-        }
-        HomeSP homeSP = mMOManager.getHomeSP(fqdn);
-        if (homeSP == null) {
-            return PasspointMatch.None.ordinal();
-        }
-
-        ANQPData anqpData = mAnqpCache.getEntry(scanDetail.getNetworkDetail());
-
-        Map<Constants.ANQPElementType, ANQPElement> anqpElements =
-                anqpData != null ? anqpData.getANQPElements() : null;
-
-        return homeSP.match(scanDetail.getNetworkDetail(), anqpElements, mSIMAccessor).ordinal();
-    }
-
-    /**
-     * General PnoNetwork list sorting algorithm:
-     * 1, Place the fully enabled networks first. Among the fully enabled networks,
-     * sort them in the oder determined by the return of |compareConfigurations| method
-     * implementation.
-     * 2. Next place all the temporarily disabled networks. Among the temporarily disabled
-     * networks, sort them in the order determined by the return of |compareConfigurations| method
-     * implementation.
-     * 3. Place the permanently disabled networks last. The order among permanently disabled
-     * networks doesn't matter.
-     */
-    private static class PnoListComparator implements Comparator<WifiConfiguration> {
-
-        public final int ENABLED_NETWORK_SCORE = 3;
-        public final int TEMPORARY_DISABLED_NETWORK_SCORE = 2;
-        public final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1;
-
-        @Override
-        public int compare(WifiConfiguration a, WifiConfiguration b) {
-            int configAScore = getPnoNetworkSortScore(a);
-            int configBScore = getPnoNetworkSortScore(b);
-            if (configAScore == configBScore) {
-                return compareConfigurations(a, b);
-            } else {
-                return Integer.compare(configBScore, configAScore);
-            }
-        }
-
-        // This needs to be implemented by the connected/disconnected PNO list comparator.
-        public int compareConfigurations(WifiConfiguration a, WifiConfiguration b) {
-            return 0;
-        }
-
+    public interface OnSavedNetworkUpdateListener {
         /**
-         * Returns an integer representing a score for each configuration. The scores are assigned
-         * based on the status of the configuration. The scores are assigned according to the order:
-         * Fully enabled network > Temporarily disabled network > Permanently disabled network.
+         * Invoked on saved network being added.
          */
-        private int getPnoNetworkSortScore(WifiConfiguration config) {
-            if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
-                return ENABLED_NETWORK_SCORE;
-            } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
-                return TEMPORARY_DISABLED_NETWORK_SCORE;
-            } else {
-                return PERMANENTLY_DISABLED_NETWORK_SCORE;
-            }
-        }
+        void onSavedNetworkAdded(int networkId);
+        /**
+         * Invoked on saved network being enabled.
+         */
+        void onSavedNetworkEnabled(int networkId);
+        /**
+         * Invoked on saved network being permanently disabled.
+         */
+        void onSavedNetworkPermanentlyDisabled(int networkId);
+        /**
+         * Invoked on saved network being removed.
+         */
+        void onSavedNetworkRemoved(int networkId);
+        /**
+         * Invoked on saved network being temporarily disabled.
+         */
+        void onSavedNetworkTemporarilyDisabled(int networkId);
+        /**
+         * Invoked on saved network being updated.
+         */
+        void onSavedNetworkUpdated(int networkId);
     }
-
     /**
-     * Disconnected PnoNetwork list sorting algorithm:
-     * Place the configurations in descending order of their |numAssociation| values. If networks
-     * have the same |numAssociation|, then sort them in descending order of their |priority|
-     * values.
+     * Max size of scan details to cache in {@link #mScanDetailCaches}.
      */
-    private static final PnoListComparator sDisconnectedPnoListComparator =
-            new PnoListComparator() {
+    @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;
+    /**
+     * Link networks only if they have less than this number of scan cache entries.
+     */
+    @VisibleForTesting
+    public static final int LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES = 6;
+    /**
+     * Link networks only if the bssid in scan results for the networks match in the first
+     * 16 ASCII chars in the bssid string. For example = "af:de:56;34:15:7"
+     */
+    @VisibleForTesting
+    public static final int LINK_CONFIGURATION_BSSID_MATCH_LENGTH = 16;
+    /**
+     * 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 = "WifiConfigManager";
+    /**
+     * Maximum age of scan results that can be used for averaging out RSSI value.
+     */
+    private static final int SCAN_RESULT_MAXIMUM_AGE_MS = 40000;
+    /**
+     * General sorting algorithm of all networks for scanning purposes:
+     * Place the configurations in descending order of their |numAssociation| values. If networks
+     * have the same |numAssociation|, place the configurations with
+     * |lastSeenInQualifiedNetworkSelection| set first.
+     */
+    private static final WifiConfigurationUtil.WifiConfigurationComparator sScanListComparator =
+            new WifiConfigurationUtil.WifiConfigurationComparator() {
                 @Override
-                public int compareConfigurations(WifiConfiguration a, WifiConfiguration b) {
+                public int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b) {
                     if (a.numAssociation != b.numAssociation) {
                         return Long.compare(b.numAssociation, a.numAssociation);
                     } else {
-                        return Integer.compare(b.priority, a.priority);
-                    }
-                }
-            };
-
-    /**
-     * Retrieves an updated list of priorities for all the saved networks before
-     * enabling disconnected PNO (wpa_supplicant based PNO).
-     *
-     * wpa_supplicant uses the priority of networks to build the list of SSID's to monitor
-     * during PNO. If there are a lot of saved networks, this list will be truncated and we
-     * might end up not connecting to the networks we use most frequently. So, We want the networks
-     * to be re-sorted based on the relative |numAssociation| values.
-     *
-     * @return list of networks with updated priorities.
-     */
-    public ArrayList<WifiScanner.PnoSettings.PnoNetwork> retrieveDisconnectedPnoNetworkList() {
-        return retrievePnoNetworkList(sDisconnectedPnoListComparator);
-    }
-
-    /**
-     * Connected PnoNetwork list sorting algorithm:
-     * Place the configurations with |lastSeenInQualifiedNetworkSelection| set first. If networks
-     * have the same value, then sort them in descending order of their |numAssociation|
-     * values.
-     */
-    private static final PnoListComparator sConnectedPnoListComparator =
-            new PnoListComparator() {
-                @Override
-                public int compareConfigurations(WifiConfiguration a, WifiConfiguration b) {
-                    boolean isConfigALastSeen =
-                            a.getNetworkSelectionStatus().getSeenInLastQualifiedNetworkSelection();
-                    boolean isConfigBLastSeen =
-                            b.getNetworkSelectionStatus().getSeenInLastQualifiedNetworkSelection();
-                    if (isConfigALastSeen != isConfigBLastSeen) {
+                        boolean isConfigALastSeen =
+                                a.getNetworkSelectionStatus()
+                                        .getSeenInLastQualifiedNetworkSelection();
+                        boolean isConfigBLastSeen =
+                                b.getNetworkSelectionStatus()
+                                        .getSeenInLastQualifiedNetworkSelection();
                         return Boolean.compare(isConfigBLastSeen, isConfigALastSeen);
-                    } else {
-                        return Long.compare(b.numAssociation, a.numAssociation);
                     }
                 }
             };
 
     /**
-     * Retrieves an updated list of priorities for all the saved networks before
-     * enabling connected PNO (HAL based ePno).
-     *
-     * @return list of networks with updated priorities.
+     * List of external dependencies for WifiConfigManager.
      */
-    public ArrayList<WifiScanner.PnoSettings.PnoNetwork> retrieveConnectedPnoNetworkList() {
-        return retrievePnoNetworkList(sConnectedPnoListComparator);
+    private final Context mContext;
+    private final Clock mClock;
+    private final UserManager mUserManager;
+    private final BackupManagerProxy mBackupManagerProxy;
+    private final TelephonyManager mTelephonyManager;
+    private final WifiKeyStore mWifiKeyStore;
+    private final WifiConfigStore mWifiConfigStore;
+    private final WifiConfigStoreLegacy mWifiConfigStoreLegacy;
+    private final WifiPermissionsUtil mWifiPermissionsUtil;
+    private final WifiPermissionsWrapper mWifiPermissionsWrapper;
+    /**
+     * 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 Map<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;
+    /**
+     * Flag to indicate if only networks with the same psk should be linked.
+     * TODO(b/30706406): Remove this flag if unused.
+     */
+    private final boolean mOnlyLinkSameCredentialConfigurations;
+    /**
+     * Number of channels to scan for during partial scans initiated while connected.
+     */
+    private final int mMaxNumActiveChannelsForPartialScans;
+    /**
+     * Verbose logging flag. Toggled by developer options.
+     */
+    private boolean mVerboseLoggingEnabled = false;
+    /**
+     * Current logged in user ID.
+     */
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    /**
+     * Flag to indicate that the new user's store has not yet been read since user switch.
+     * Initialize this flag to |true| to trigger a read on the first user unlock after
+     * bootup.
+     */
+    private boolean mPendingUnlockStoreRead = true;
+    /**
+     * Flag to indicate if we have performed a read from store at all. This is used to gate
+     * any user unlock/switch operations until we read the store (Will happen if wifi is disabled
+     * when user updates from N to O).
+     */
+    private boolean mPendingStoreRead = true;
+    /**
+     * Flag to indicate if the user unlock was deferred until the store load occurs.
+     */
+    private boolean mDeferredUserUnlockRead = false;
+    /**
+     * This is keeping track of the next network ID to be assigned. Any new networks will be
+     * assigned |mNextNetworkId| as network ID.
+     */
+    private int mNextNetworkId = 0;
+    /**
+     * UID of system UI. This uid is allowed to modify network configurations regardless of which
+     * user is logged in.
+     */
+    private int mSystemUiUid = -1;
+    /**
+     * This is used to remember which network was selected successfully last by an app. This is set
+     * when an app invokes {@link #enableNetwork(int, boolean, int)} with |disableOthers| flag set.
+     * This is the only way for an app to request connection to a specific network using the
+     * {@link WifiManager} API's.
+     */
+    private int mLastSelectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+    private long mLastSelectedTimeStamp =
+            WifiConfiguration.NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
+
+    // Store data for network list and deleted ephemeral SSID list.  Used for serializing
+    // parsing data to/from the config store.
+    private final NetworkListStoreData mNetworkListStoreData;
+    private final DeletedEphemeralSsidsStoreData mDeletedEphemeralSsidsStoreData;
+
+    // Store the saved network update listener.
+    private OnSavedNetworkUpdateListener mListener = null;
+
+    /**
+     * Create new instance of WifiConfigManager.
+     */
+    WifiConfigManager(
+            Context context, Clock clock, UserManager userManager,
+            TelephonyManager telephonyManager, WifiKeyStore wifiKeyStore,
+            WifiConfigStore wifiConfigStore, WifiConfigStoreLegacy wifiConfigStoreLegacy,
+            WifiPermissionsUtil wifiPermissionsUtil,
+            WifiPermissionsWrapper wifiPermissionsWrapper,
+            NetworkListStoreData networkListStoreData,
+            DeletedEphemeralSsidsStoreData deletedEphemeralSsidsStoreData) {
+        mContext = context;
+        mClock = clock;
+        mUserManager = userManager;
+        mBackupManagerProxy = new BackupManagerProxy();
+        mTelephonyManager = telephonyManager;
+        mWifiKeyStore = wifiKeyStore;
+        mWifiConfigStore = wifiConfigStore;
+        mWifiConfigStoreLegacy = wifiConfigStoreLegacy;
+        mWifiPermissionsUtil = wifiPermissionsUtil;
+        mWifiPermissionsWrapper = wifiPermissionsWrapper;
+
+        mConfiguredNetworks = new ConfigurationMap(userManager);
+        mScanDetailCaches = new HashMap<>(16, 0.75f);
+        mDeletedEphemeralSSIDs = new HashSet<>();
+
+        // Register store data for network list and deleted ephemeral SSIDs.
+        mNetworkListStoreData = networkListStoreData;
+        mDeletedEphemeralSsidsStoreData = deletedEphemeralSsidsStoreData;
+        mWifiConfigStore.registerStoreData(mNetworkListStoreData);
+        mWifiConfigStore.registerStoreData(mDeletedEphemeralSsidsStoreData);
+
+        mOnlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
+                R.bool.config_wifi_only_link_same_credential_configurations);
+        mMaxNumActiveChannelsForPartialScans = mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels);
+
+        try {
+            mSystemUiUid = mContext.getPackageManager().getPackageUidAsUser(SYSUI_PACKAGE_NAME,
+                    PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Unable to resolve SystemUI's UID.");
+        }
     }
 
     /**
-     * Create a PnoNetwork object from the provided WifiConfiguration.
-     * @param config Configuration corresponding to the network.
-     * @param newPriority New priority to be assigned to the network.
+     * 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.
      */
-    private static WifiScanner.PnoSettings.PnoNetwork createPnoNetworkFromWifiConfiguration(
-            WifiConfiguration config, int newPriority) {
-        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
-                new WifiScanner.PnoSettings.PnoNetwork(config.SSID);
-        pnoNetwork.networkId = config.networkId;
-        pnoNetwork.priority = newPriority;
-        if (config.hiddenSSID) {
-            pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
-        }
-        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
-        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
-        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
-        } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
-                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
-            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
+    @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 {
-            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
+            mVerboseLoggingEnabled = false;
         }
-        return pnoNetwork;
+        mWifiConfigStore.enableVerboseLogging(mVerboseLoggingEnabled);
+        mWifiKeyStore.enableVerboseLogging(mVerboseLoggingEnabled);
     }
 
     /**
-     * Retrieves an updated list of priorities for all the saved networks before
-     * enabling/disabling PNO.
-     *
-     * @param pnoListComparator The comparator to use for sorting networks
-     * @return list of networks with updated priorities.
+     * 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 ArrayList<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList(
-            PnoListComparator pnoListComparator) {
-        ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
-        ArrayList<WifiConfiguration> wifiConfigurations =
-                new ArrayList<>(mConfiguredNetworks.valuesForCurrentUser());
-        // Remove any permanently disabled networks.
-        Iterator<WifiConfiguration> iter = wifiConfigurations.iterator();
-        while (iter.hasNext()) {
-            WifiConfiguration config = iter.next();
-            if (config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
-                iter.remove();
+    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;
+                }
             }
         }
-        Collections.sort(wifiConfigurations, pnoListComparator);
-        // Let's use the network list size as the highest priority and then go down from there.
-        // So, the most frequently connected network has the highest priority now.
-        int priority = wifiConfigurations.size();
-        for (WifiConfiguration config : wifiConfigurations) {
-            pnoList.add(createPnoNetworkFromWifiConfiguration(config, priority));
-            priority--;
+        if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
+            configuration.enterpriseConfig.setPassword(PASSWORD_MASK);
         }
-        return pnoList;
     }
 
     /**
-     * Remove a network. Note that there is no saveConfig operation.
-     * This function is retained for compatibility with the public
-     * API. The more powerful forgetNetwork() is used by the
-     * state machine for network removal
+     * Helper method to create a copy of the provided internal WifiConfiguration object to be
+     * passed to external modules.
      *
-     * @param netId network to be removed
-     * @return {@code true} if it succeeds, {@code false} otherwise
+     * @param configuration provided WifiConfiguration object.
+     * @param maskPasswords Mask passwords or not.
+     * @return Copy of the WifiConfiguration object.
      */
-    boolean removeNetwork(int netId) {
-        if (mShowNetworks) localLogNetwork("removeNetwork", netId);
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        if (!removeConfigAndSendBroadcastIfNeeded(config)) {
+    private WifiConfiguration createExternalWifiConfiguration(
+            WifiConfiguration configuration, boolean maskPasswords) {
+        WifiConfiguration network = new WifiConfiguration(configuration);
+        if (maskPasswords) {
+            maskPasswordsInWifiConfiguration(network);
+        }
+        return network;
+    }
+
+    /**
+     * 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;
+            }
+            networks.add(createExternalWifiConfiguration(config, maskPasswords));
+        }
+        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.
+        return createExternalWifiConfiguration(config, true);
+    }
+
+    /**
+     * Retrieves the configured network corresponding to the provided config key with password
+     * masked.
+     *
+     * @param configKey configKey of the requested network.
+     * @return WifiConfiguration object if found, null otherwise.
+     */
+    public WifiConfiguration getConfiguredNetwork(String configKey) {
+        WifiConfiguration config = getInternalConfiguredNetwork(configKey);
+        if (config == null) {
+            return null;
+        }
+        // Create a new configuration object with the passwords masked to send out to the external
+        // world.
+        return createExternalWifiConfiguration(config, true);
+    }
+
+    /**
+     * 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.
+        return createExternalWifiConfiguration(config, false);
+    }
+
+    /**
+     * 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;
+        }
+        internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
+        if (internalConfig == null) {
+            Log.e(TAG, "Cannot find network with networkId " + config.networkId
+                    + " or configKey " + config.configKey());
+        }
+        return internalConfig;
+    }
+
+    /**
+     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
+     * provided network ID in our database.
+     */
+    private WifiConfiguration getInternalConfiguredNetwork(int networkId) {
+        if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
+            Log.w(TAG, "Looking up network with invalid networkId -1");
+            return null;
+        }
+        WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(networkId);
+        if (internalConfig == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+        }
+        return internalConfig;
+    }
+
+    /**
+     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
+     * provided configKey in our database.
+     */
+    private WifiConfiguration getInternalConfiguredNetwork(String configKey) {
+        WifiConfiguration internalConfig =
+                mConfiguredNetworks.getByConfigKeyForCurrentUser(configKey);
+        if (internalConfig == null) {
+            Log.e(TAG, "Cannot find network with configKey " + configKey);
+        }
+        return internalConfig;
+    }
+
+    /**
+     * 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 |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 connection attempts.
+     */
+    private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
+        // Passpoint configurations are generated and managed by PasspointManager. They can be
+        // added by either PasspointNetworkEvaluator (for auto connection) or Settings app
+        // (for manual connection), and need to be removed once the connection is completed.
+        // Since it is "owned" by us, so always allow us to modify them.
+        if (config.isPasspoint() && uid == Process.WIFI_UID) {
+            return true;
+        }
+
+        // EAP-SIM/AKA/AKA' network needs framework to update the anonymous identity provided
+        // by authenticator back to the WifiConfiguration object.
+        // Since it is "owned" by us, so always allow us to modify them.
+        if (config.enterpriseConfig != null
+                && uid == Process.WIFI_UID
+                && TelephonyUtil.isSimEapMethod(config.enterpriseConfig.getEapMethod())) {
+            return true;
+        }
+
+        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| holds the |OVERRIDE_CONFIG_WIFI| permission if the caller asks us to
+        // bypass the lockdown checks.
+        if (ignoreLockdown) {
+            return mWifiPermissionsUtil.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;
         }
-        if (config.isPasspoint()) {
-            writePasspointConfigs(config.FQDN, null);
+
+        // 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 || mWifiPermissionsUtil.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 && mWifiPermissionsUtil.checkConfigOverridePermission(uid);
+    }
+
+    /**
+     * Method to check if the provided UID belongs to the current foreground user or some other
+     * app (only SysUI today) running on behalf of the user.
+     * This is used to prevent any background user apps from modifying network configurations.
+     *
+     * @param uid uid of the app.
+     * @return true if the UID belongs to the current foreground app or SystemUI, false otherwise.
+     */
+    private boolean doesUidBelongToCurrentUser(int uid) {
+        return (WifiConfigurationUtil.doesUidBelongToAnyProfile(
+                uid, mUserManager.getProfiles(mCurrentUserId)) || (uid == mSystemUiUid));
+    }
+
+    /**
+     * 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 internalConfig WifiConfiguration object in our internal map.
+     * @param externalConfig WifiConfiguration object provided from the external API.
+     */
+    private void mergeWithInternalWifiConfiguration(
+            WifiConfiguration internalConfig, WifiConfiguration externalConfig) {
+        if (externalConfig.SSID != null) {
+            internalConfig.SSID = externalConfig.SSID;
+        }
+        if (externalConfig.BSSID != null) {
+            internalConfig.BSSID = externalConfig.BSSID.toLowerCase();
+        }
+        internalConfig.hiddenSSID = externalConfig.hiddenSSID;
+        if (externalConfig.preSharedKey != null
+                && !externalConfig.preSharedKey.equals(PASSWORD_MASK)) {
+            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
+                        && !externalConfig.wepKeys[i].equals(PASSWORD_MASK)) {
+                    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.clone();
+        }
+
+        // Copy over all the auth/protocol/key mgmt parameters if set.
+        if (externalConfig.allowedAuthAlgorithms != null
+                && !externalConfig.allowedAuthAlgorithms.isEmpty()) {
+            internalConfig.allowedAuthAlgorithms =
+                    (BitSet) externalConfig.allowedAuthAlgorithms.clone();
+        }
+        if (externalConfig.allowedProtocols != null
+                && !externalConfig.allowedProtocols.isEmpty()) {
+            internalConfig.allowedProtocols = (BitSet) externalConfig.allowedProtocols.clone();
+        }
+        if (externalConfig.allowedKeyManagement != null
+                && !externalConfig.allowedKeyManagement.isEmpty()) {
+            internalConfig.allowedKeyManagement =
+                    (BitSet) externalConfig.allowedKeyManagement.clone();
+        }
+        if (externalConfig.allowedPairwiseCiphers != null
+                && !externalConfig.allowedPairwiseCiphers.isEmpty()) {
+            internalConfig.allowedPairwiseCiphers =
+                    (BitSet) externalConfig.allowedPairwiseCiphers.clone();
+        }
+        if (externalConfig.allowedGroupCiphers != null
+                && !externalConfig.allowedGroupCiphers.isEmpty()) {
+            internalConfig.allowedGroupCiphers =
+                    (BitSet) externalConfig.allowedGroupCiphers.clone();
+        }
+
+        // Copy over the |IpConfiguration| parameters if set.
+        if (externalConfig.getIpConfiguration() != null) {
+            IpConfiguration.IpAssignment ipAssignment = externalConfig.getIpAssignment();
+            if (ipAssignment != IpConfiguration.IpAssignment.UNASSIGNED) {
+                internalConfig.setIpAssignment(ipAssignment);
+                if (ipAssignment == IpConfiguration.IpAssignment.STATIC) {
+                    internalConfig.setStaticIpConfiguration(
+                            new StaticIpConfiguration(externalConfig.getStaticIpConfiguration()));
+                }
+            }
+            IpConfiguration.ProxySettings proxySettings = externalConfig.getProxySettings();
+            if (proxySettings != IpConfiguration.ProxySettings.UNASSIGNED) {
+                internalConfig.setProxySettings(proxySettings);
+                if (proxySettings == IpConfiguration.ProxySettings.PAC
+                        || proxySettings == IpConfiguration.ProxySettings.STATIC) {
+                    internalConfig.setHttpProxy(new ProxyInfo(externalConfig.getHttpProxy()));
+                }
+            }
+        }
+
+        // Copy over the |WifiEnterpriseConfig| parameters if set.
+        if (externalConfig.enterpriseConfig != null) {
+            internalConfig.enterpriseConfig.copyFromExternal(
+                    externalConfig.enterpriseConfig, PASSWORD_MASK);
+        }
+    }
+
+    /**
+     * 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.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);
+
+        configuration.status = WifiConfiguration.Status.DISABLED;
+        configuration.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+    }
+
+    /**
+     * Create a new internal WifiConfiguration object by copying over parameters from the provided
+     * external configuration and set defaults for the appropriate parameters.
+     *
+     * @param externalConfig WifiConfiguration object provided from the external API.
+     * @return New WifiConfiguration object with parameters merged from the provided external
+     * configuration.
+     */
+    private WifiConfiguration createNewInternalWifiConfigurationFromExternal(
+            WifiConfiguration externalConfig, int uid) {
+        WifiConfiguration newInternalConfig = new WifiConfiguration();
+
+        // First allocate a new network ID for the configuration.
+        newInternalConfig.networkId = mNextNetworkId++;
+
+        // First set defaults in the new configuration created.
+        setDefaultsInWifiConfiguration(newInternalConfig);
+
+        // Copy over all the public elements from the provided configuration.
+        mergeWithInternalWifiConfiguration(newInternalConfig, externalConfig);
+
+        // 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.
+        newInternalConfig.requirePMF = externalConfig.requirePMF;
+        newInternalConfig.noInternetAccessExpected = externalConfig.noInternetAccessExpected;
+        newInternalConfig.ephemeral = externalConfig.ephemeral;
+        newInternalConfig.meteredHint = externalConfig.meteredHint;
+        newInternalConfig.useExternalScores = externalConfig.useExternalScores;
+        newInternalConfig.shared = externalConfig.shared;
+
+        // Add debug information for network addition.
+        newInternalConfig.creatorUid = newInternalConfig.lastUpdateUid = uid;
+        newInternalConfig.creatorName = newInternalConfig.lastUpdateName =
+                mContext.getPackageManager().getNameForUid(uid);
+        newInternalConfig.creationTime = newInternalConfig.updateTime =
+                createDebugTimeStampString(mClock.getWallClockMillis());
+
+        return newInternalConfig;
+    }
+
+    /**
+     * Create a new internal WifiConfiguration object by copying over parameters from the provided
+     * external configuration to a copy of the existing internal WifiConfiguration object.
+     *
+     * @param internalConfig WifiConfiguration object in our internal map.
+     * @param externalConfig WifiConfiguration object provided from the external API.
+     * @return Copy of existing WifiConfiguration object with parameters merged from the provided
+     * configuration.
+     */
+    private WifiConfiguration updateExistingInternalWifiConfigurationFromExternal(
+            WifiConfiguration internalConfig, WifiConfiguration externalConfig, int uid) {
+        WifiConfiguration newInternalConfig = new WifiConfiguration(internalConfig);
+
+        // Copy over all the public elements from the provided configuration.
+        mergeWithInternalWifiConfiguration(newInternalConfig, externalConfig);
+
+        // Add debug information for network update.
+        newInternalConfig.lastUpdateUid = uid;
+        newInternalConfig.lastUpdateName = mContext.getPackageManager().getNameForUid(uid);
+        newInternalConfig.updateTime = createDebugTimeStampString(mClock.getWallClockMillis());
+
+        return newInternalConfig;
+    }
+
+    /**
+     * 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());
+        }
+        WifiConfiguration newInternalConfig = null;
+
+        // First check if we already have a network with the provided network id or configKey.
+        WifiConfiguration existingInternalConfig = getInternalConfiguredNetwork(config);
+        // No existing network found. So, potentially a network add.
+        if (existingInternalConfig == null) {
+            newInternalConfig = createNewInternalWifiConfigurationFromExternal(config, uid);
+            // Since the original config provided may have had an empty
+            // {@link WifiConfiguration#allowedKeyMgmt} field, check again if we already have a
+            // network with the the same configkey.
+            existingInternalConfig = getInternalConfiguredNetwork(newInternalConfig.configKey());
+        }
+        // Existing network found. So, a network update.
+        if (existingInternalConfig != null) {
+            // 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(
+                            existingInternalConfig, config, uid);
+        }
+
+        // Only add networks with proxy settings if the user has permission to
+        if (WifiConfigurationUtil.hasProxyChanged(existingInternalConfig, newInternalConfig)
+                && !canModifyProxySettings(uid)) {
+            Log.e(TAG, "UID " + uid + " does not have permission to modify proxy Settings "
+                    + config.configKey() + ". Must have OVERRIDE_WIFI_CONFIG,"
+                    + " or be device or profile owner.");
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+
+        // Update the keys for non-Passpoint enterprise networks.  For Passpoint, the certificates
+        // and keys are installed at the time the provider is installed.
+        if (config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE
+                && !config.isPasspoint()) {
+            if (!(mWifiKeyStore.updateNetworkKeys(newInternalConfig, existingInternalConfig))) {
+                return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+            }
+        }
+
+        boolean newNetwork = (existingInternalConfig == null);
+        // This is needed to inform IpManager about any IP configuration changes.
+        boolean hasIpChanged =
+                newNetwork || WifiConfigurationUtil.hasIpChanged(
+                        existingInternalConfig, newInternalConfig);
+        boolean hasProxyChanged =
+                newNetwork || WifiConfigurationUtil.hasProxyChanged(
+                        existingInternalConfig, newInternalConfig);
+        // Reset the |hasEverConnected| flag if the credential parameters changed in this update.
+        boolean hasCredentialChanged =
+                newNetwork || WifiConfigurationUtil.hasCredentialChanged(
+                        existingInternalConfig, newInternalConfig);
+        if (hasCredentialChanged) {
+            newInternalConfig.getNetworkSelectionStatus().setHasEverConnected(false);
+        }
+
+        // Add it to our internal map. This will replace any existing network configuration for
+        // updates.
+        mConfiguredNetworks.put(newInternalConfig);
+
+        if (mDeletedEphemeralSSIDs.remove(config.SSID)) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Removed from ephemeral blacklist: " + config.SSID);
+            }
+        }
+
+        // Stage the backup of the SettingsProvider package which backs this up.
+        mBackupManagerProxy.notifyDataChanged();
+
+        NetworkUpdateResult result =
+                new NetworkUpdateResult(hasIpChanged, hasProxyChanged, hasCredentialChanged);
+        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/modification.
+     * @return NetworkUpdateResult object representing status of the update.
+     */
+    public NetworkUpdateResult addOrUpdateNetwork(WifiConfiguration config, int uid) {
+        if (!doesUidBelongToCurrentUser(uid)) {
+            Log.e(TAG, "UID " + uid + " not visible to the current user");
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        if (config == null) {
+            Log.e(TAG, "Cannot add/update network with null config");
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        if (mPendingStoreRead) {
+            Log.e(TAG, "Cannot add/update network before store is read!");
+            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);
+        // Unless the added network is ephemeral or Passpoint, persist the network update/addition.
+        if (!config.ephemeral && !config.isPasspoint()) {
+            saveToStore(true);
+            if (mListener != null) {
+                if (result.isNewNetwork()) {
+                    mListener.onSavedNetworkAdded(newConfig.networkId);
+                } else {
+                    mListener.onSavedNetworkUpdated(newConfig.networkId);
+                }
+            }
+        }
+        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 for non-Passpoint networks.
+        if (!config.isPasspoint() && config.enterpriseConfig != null
+                && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
+            mWifiKeyStore.removeKeys(config.enterpriseConfig);
+        }
+
+        removeConnectChoiceFromAllNetworks(config.configKey());
+        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;
     }
 
-    private static Long getChecksum(String source) {
-        Checksum csum = new CRC32();
-        csum.update(source.getBytes(), 0, source.getBytes().length);
-        return csum.getValue();
-    }
-
-    private boolean removeConfigWithoutBroadcast(WifiConfiguration config) {
+    /**
+     * Removes the specified network configuration from our database.
+     *
+     * @param networkId network ID of the provided network.
+     * @param uid       UID of the app requesting the network deletion.
+     * @return true if successful, false otherwise.
+     */
+    public boolean removeNetwork(int networkId, int uid) {
+        if (!doesUidBelongToCurrentUser(uid)) {
+            Log.e(TAG, "UID " + uid + " not visible to the current user");
+            return false;
+        }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
             return false;
         }
-        if (!mWifiConfigStore.removeNetwork(config)) {
-            loge("Failed to remove network " + config.networkId);
+        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+            Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
+                    + config.configKey());
             return false;
         }
-        if (config.configKey().equals(mLastSelectedConfiguration)) {
-            mLastSelectedConfiguration = null;
+        if (!removeNetworkInternal(config)) {
+            Log.e(TAG, "Failed to remove network " + config.getPrintableSsid());
+            return false;
         }
-        mConfiguredNetworks.remove(config.networkId);
-        mScanDetailCaches.remove(config.networkId);
+        if (networkId == mLastSelectedNetworkId) {
+            clearLastSelectedNetwork();
+        }
+        sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+        // Unless the removed network is ephemeral or Passpoint, persist the network removal.
+        if (!config.ephemeral && !config.isPasspoint()) {
+            saveToStore(true);
+            if (mListener != null) mListener.onSavedNetworkRemoved(networkId);
+        }
         return true;
     }
 
-    private boolean removeConfigAndSendBroadcastIfNeeded(WifiConfiguration config) {
-        if (!removeConfigWithoutBroadcast(config)) {
-            return false;
-        }
-        String key = config.configKey();
-        if (sVDBG) {
-            logd("removeNetwork " + " key=" + key + " config.id=" + config.networkId);
-        }
-        writeIpAndProxyConfigurations();
-        sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
-        if (!config.ephemeral) {
-            removeUserSelectionPreference(key);
-        }
-        writeKnownNetworkHistory();
-        return true;
-    }
-
-    private void removeUserSelectionPreference(String configKey) {
-        if (DBG) {
-            Log.d(TAG, "removeUserSelectionPreference: key is " + configKey);
-        }
-        if (configKey == null) {
-            return;
-        }
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
-            String connectChoice = status.getConnectChoice();
-            if (connectChoice != null && connectChoice.equals(configKey)) {
-                Log.d(TAG, "remove connect choice:" + connectChoice + " from " + config.SSID
-                        + " : " + config.networkId);
-                status.setConnectChoice(null);
-                status.setConnectChoiceTimestamp(WifiConfiguration.NetworkSelectionStatus
-                            .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
-            }
-        }
-    }
-
-    /*
-     * Remove all networks associated with an application
+    /**
+     * Remove all networks associated with an application.
      *
      * @param app Application info of the package of networks to remove.
      * @return the {@link Set} of networks that were removed by this call. Networks which matched
@@ -1262,7 +1145,6 @@
         if (app == null || app.packageName == null) {
             return Collections.<Integer>emptySet();
         }
-
         Log.d(TAG, "Remove all networks for app " + app);
         Set<Integer> removedNetworks = new ArraySet<>();
         WifiConfiguration[] copiedConfigs =
@@ -1274,11 +1156,10 @@
             localLog("Removing network " + config.SSID
                     + ", application \"" + app.packageName + "\" uninstalled"
                     + " from user " + UserHandle.getUserId(app.uid));
-            if (removeNetwork(config.networkId)) {
+            if (removeNetwork(config.networkId, mSystemUiUid)) {
                 removedNetworks.add(config.networkId);
             }
         }
-        saveConfig();
         return removedNetworks;
     }
 
@@ -1299,7 +1180,7 @@
                 continue;
             }
             localLog("Removing network " + config.SSID + ", user " + userId + " removed");
-            if (removeNetwork(config.networkId)) {
+            if (removeNetwork(config.networkId, mSystemUiUid)) {
                 removedNetworks.add(config.networkId);
             }
         }
@@ -1307,1336 +1188,677 @@
     }
 
     /**
-     * Enable a network. Note that there is no saveConfig operation.
-     * This function is retained for compatibility with the public
-     * API. The more powerful selectNetwork()/saveNetwork() is used by the
-     * state machine for connecting to a network
-     *
-     * @param config network to be enabled
-     * @return {@code true} if it succeeds, {@code false} otherwise
+     * Helper method to mark a network enabled for network selection.
      */
-    boolean enableNetwork(WifiConfiguration config, boolean disableOthers, int uid) {
+    private void setNetworkSelectionEnabled(WifiConfiguration config) {
+        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        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();
+        if (mListener != null) mListener.onSavedNetworkEnabled(config.networkId);
+    }
+
+    /**
+     * Helper method to mark a network temporarily disabled for network selection.
+     */
+    private void setNetworkSelectionTemporarilyDisabled(
+            WifiConfiguration config, int disableReason) {
+        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        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);
+        if (mListener != null) mListener.onSavedNetworkTemporarilyDisabled(config.networkId);
+    }
+
+    /**
+     * Helper method to mark a network permanently disabled for network selection.
+     */
+    private void setNetworkSelectionPermanentlyDisabled(
+            WifiConfiguration config, int disableReason) {
+        NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        status.setDisableTime(
+                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+        status.setNetworkSelectionDisableReason(disableReason);
+        if (mListener != null) mListener.onSavedNetworkPermanentlyDisabled(config.networkId);
+    }
+
+    /**
+     * 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(config);
+            setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
+        } else if (reason < NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
+            setNetworkSelectionTemporarilyDisabled(config, reason);
+        } else {
+            setNetworkSelectionPermanentlyDisabled(config, reason);
+            setNetworkStatus(config, WifiConfiguration.Status.DISABLED);
+        }
+        localLog("setNetworkSelectionStatus: configKey=" + config.configKey()
+                + " networkStatus=" + networkStatus.getNetworkStatusString() + " disableReason="
+                + networkStatus.getNetworkDisableReasonString() + " at="
+                + createDebugTimeStampString(mClock.getWallClockMillis()));
+        saveToStore(false);
+        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 Network Selector.
+     * 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) {
             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);
-            }
-            updateLastConnectUid(config, uid);
-            writeKnownNetworkHistory();
-            sendConfiguredNetworksChangedBroadcast();
-        } else {
-            if (sVDBG) 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());
-    }
-
-    /**
-     * Disable a network in wpa_supplicant.
-     */
-    boolean disableNetworkNative(WifiConfiguration config) {
-        return mWifiConfigStore.disableNetwork(config);
-    }
-
-    /**
-     * Disable all networks in wpa_supplicant.
-     */
-    void disableAllNetworksNative() {
-        mWifiConfigStore.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));
-    }
-
-    /**
-     * Update a network according to the update reason and its current state
-     * @param netId The network ID of the network need update
-     * @param reason The reason to update the network
-     * @return false if no change made to the input configure file, can due to error or need not
-     *         true the input config file has been changed
-     */
-    boolean updateNetworkSelectionStatus(int netId, int reason) {
-        WifiConfiguration config = getWifiConfiguration(netId);
         return updateNetworkSelectionStatus(config, reason);
     }
 
     /**
-     * Update a network according to the update reason and its current state
-     * @param config the network need update
-     * @param reason The reason to update the network
-     * @return false if no change made to the input configure file, can due to error or need not
-     *         true the input config file has been changed
+     * Update whether a network is currently not recommended by {@link RecommendedNetworkEvaluator}.
+     *
+     * @param networkId network ID of the network to be updated
+     * @param notRecommended whether this network is not recommended
+     * @return true if the network is updated, false otherwise
      */
-    boolean updateNetworkSelectionStatus(WifiConfiguration config, int reason) {
+    public boolean updateNetworkNotRecommended(int networkId, boolean notRecommended) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
             return false;
         }
 
-        WifiConfiguration.NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
-        if (reason == WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
-            updateNetworkStatus(config, WifiConfiguration.NetworkSelectionStatus
-                    .NETWORK_SELECTION_ENABLE);
-            localLog("Enable network:" + config.configKey());
-            return true;
+        config.getNetworkSelectionStatus().setNotRecommended(notRecommended);
+        if (mVerboseLoggingEnabled) {
+            localLog("updateNetworkRecommendation: configKey=" + config.configKey()
+                    + " notRecommended=" + notRecommended);
         }
-
-        networkStatus.incrementDisableReasonCounter(reason);
-        if (DBG) {
-            localLog("Network:" + config.SSID + "disable counter of "
-                    + WifiConfiguration.NetworkSelectionStatus.getNetworkDisableReasonString(reason)
-                    + " is: " + networkStatus.getDisableReasonCounter(reason) + "and threshold is: "
-                    + NETWORK_SELECTION_DISABLE_THRESHOLD[reason]);
-        }
-
-        if (networkStatus.getDisableReasonCounter(reason)
-                >= NETWORK_SELECTION_DISABLE_THRESHOLD[reason]) {
-            return updateNetworkStatus(config, reason);
-        }
+        saveToStore(false);
         return true;
     }
 
     /**
-     * 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.
-     * @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
+     * 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.
      */
-    public boolean tryEnableQualifiedNetwork(int networkId) {
-        WifiConfiguration config = getWifiConfiguration(networkId);
-        if (config == null) {
-            localLog("updateQualifiedNetworkstatus invalid network.");
-            return false;
-        }
-        return tryEnableQualifiedNetwork(config);
-    }
-
-    /**
-     * 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
-     */
-    private boolean tryEnableQualifiedNetwork(WifiConfiguration config) {
-        WifiConfiguration.NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
+    private boolean tryEnableNetwork(WifiConfiguration config) {
+        NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
         if (networkStatus.isNetworkTemporaryDisabled()) {
-            //time difference in minutes
-            long timeDifference =
-                    (mClock.elapsedRealtime() - networkStatus.getDisableTime()) / 1000 / 60;
-            if (timeDifference < 0 || timeDifference
-                    >= NETWORK_SELECTION_DISABLE_TIMEOUT[
-                    networkStatus.getNetworkSelectionDisableReason()]) {
-                updateNetworkSelectionStatus(config.networkId,
-                        networkStatus.NETWORK_SELECTION_ENABLE);
-                return true;
+            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;
     }
 
     /**
-     * Update a network's status. Note that there is no saveConfig operation.
-     * @param config network to be updated
-     * @param reason reason code for updated
-     * @return false if no change made to the input configure file, can due to error or need not
-     *         true the input config file has been changed
+     * 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.
      */
-    boolean updateNetworkStatus(WifiConfiguration config, int reason) {
-        localLog("updateNetworkStatus:" + (config == null ? null : config.SSID));
+    public boolean tryEnableNetwork(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
             return false;
         }
+        return tryEnableNetwork(config);
+    }
 
-        WifiConfiguration.NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
-        if (reason < 0 || reason >= WifiConfiguration.NetworkSelectionStatus
-                .NETWORK_SELECTION_DISABLED_MAX) {
-            localLog("Invalid Network disable reason:" + reason);
+    /**
+     * Enable a network using the public {@link WifiManager#enableNetwork(int, boolean)} API.
+     *
+     * @param networkId     network ID of the network that needs the update.
+     * @param disableOthers Whether to disable all other networks or not. This is used to indicate
+     *                      that the app requested connection to a specific network.
+     * @param uid           uid of the app requesting the update.
+     * @return true if it succeeds, false otherwise
+     */
+    public boolean enableNetwork(int networkId, boolean disableOthers, int uid) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Enabling network " + networkId + " (disableOthers " + disableOthers + ")");
+        }
+        if (!doesUidBelongToCurrentUser(uid)) {
+            Log.e(TAG, "UID " + uid + " not visible to the current user");
             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");
-                }
-                return false;
-            }
-            networkStatus.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
-                    .NETWORK_SELECTION_ENABLED);
-            networkStatus.setNetworkSelectionDisableReason(reason);
-            networkStatus.setDisableTime(
-                    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);
-            }
-            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! "
-                            + WifiConfiguration.NetworkSelectionStatus
-                            .getNetworkDisableReasonString(reason));
-                }
-                return false;
-            } else if (networkStatus.isNetworkTemporaryDisabled()
-                    && reason < WifiConfiguration.NetworkSelectionStatus
-                    .DISABLED_TLS_VERSION_MISMATCH) {
-                //alreay temporarily disable
-                if (DBG) {
-                    localLog("Do nothing. Already temporarily disabled! "
-                            + 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));
-            }
-            if (reason < WifiConfiguration.NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
-                networkStatus.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
-                        .NETWORK_SELECTION_TEMPORARY_DISABLED);
-                networkStatus.setDisableTime(mClock.elapsedRealtime());
-            } else {
-                networkStatus.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
-                        .NETWORK_SELECTION_PERMANENTLY_DISABLED);
-            }
-            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);
-            }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            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;
+        }
+        if (!updateNetworkSelectionStatus(
+                networkId, WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)) {
+            return false;
+        }
+        if (disableOthers) {
+            setLastSelectedNetwork(networkId);
+        }
+        saveToStore(true);
         return true;
     }
 
     /**
-     * Save the configured networks in supplicant to disk
-     * @return {@code true} if it succeeds, {@code false} otherwise
+     * 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 true if it succeeds, false otherwise
      */
-    boolean saveConfig() {
-        return mWifiConfigStore.saveConfig();
+    public boolean disableNetwork(int networkId, int uid) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Disabling network " + networkId);
+        }
+        if (!doesUidBelongToCurrentUser(uid)) {
+            Log.e(TAG, "UID " + uid + " not visible to the current user");
+            return false;
+        }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            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;
+        }
+        if (!updateNetworkSelectionStatus(
+                networkId, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER)) {
+            return false;
+        }
+        if (networkId == mLastSelectedNetworkId) {
+            clearLastSelectedNetwork();
+        }
+        saveToStore(true);
+        return true;
     }
 
     /**
-     * Start WPS pin method configuration with pin obtained
-     * from the access point
-     * @param config WPS configuration
-     * @return Wps result containing status and pin
+     * Checks if the |uid| has the necessary permission to force a connection to a network
+     * 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 true if |uid| has the necessary permission to trigger explicit connection to the
+     * network, false otherwise.
+     * Note: This returns true only for the system settings/sysui app which holds the
+     * {@link android.Manifest.permission#OVERRIDE_WIFI_CONFIG} permission. We don't want to let
+     * any other app force connection to a network.
      */
-    WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
-        return mWifiConfigStore.startWpsWithPinFromAccessPoint(
-                config, mConfiguredNetworks.valuesForCurrentUser());
+    public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Update network last connect UID for " + networkId);
+        }
+        if (!doesUidBelongToCurrentUser(uid)) {
+            Log.e(TAG, "UID " + uid + " not visible to the current user");
+            return false;
+        }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            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;
     }
 
     /**
-     * Start WPS pin method configuration with obtained
-     * from the device
-     * @return WpsResult indicating status and pin
+     * Updates a network configuration after a successful connection to it.
+     *
+     * This method updates the following WifiConfiguration elements:
+     * 1. Set the |lastConnected| timestamp.
+     * 2. Increment |numAssociation| counter.
+     * 3. Clear the disable reason counters in the associated |NetworkSelectionStatus|.
+     * 4. Set the hasEverConnected| flag in the associated |NetworkSelectionStatus|.
+     * 5. Sets the status of network as |CURRENT|.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @return true if the network was found, false otherwise.
      */
-    WpsResult startWpsWithPinFromDevice(WpsInfo config) {
-        return mWifiConfigStore.startWpsWithPinFromDevice(
-            config, mConfiguredNetworks.valuesForCurrentUser());
+    public boolean updateNetworkAfterConnect(int networkId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Update network after connect for " + networkId);
+        }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.lastConnected = mClock.getWallClockMillis();
+        config.numAssociation++;
+        config.getNetworkSelectionStatus().clearDisableReasonCounter();
+        config.getNetworkSelectionStatus().setHasEverConnected(true);
+        setNetworkStatus(config, WifiConfiguration.Status.CURRENT);
+        saveToStore(false);
+        return true;
     }
 
     /**
-     * Start WPS push button configuration
-     * @param config WPS configuration
-     * @return WpsResult indicating status and pin
+     * Updates a network configuration after disconnection from it.
+     *
+     * This method updates the following WifiConfiguration elements:
+     * 1. Set the |lastDisConnected| timestamp.
+     * 2. Sets the status of network back to |ENABLED|.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @return true if the network was found, false otherwise.
      */
-    WpsResult startWpsPbc(WpsInfo config) {
-        return mWifiConfigStore.startWpsPbc(
-            config, mConfiguredNetworks.valuesForCurrentUser());
+    public boolean updateNetworkAfterDisconnect(int networkId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Update network after disconnect for " + networkId);
+        }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.lastDisconnected = mClock.getWallClockMillis();
+        // If the network hasn't been disabled, mark it back as
+        // enabled after disconnection.
+        if (config.status == WifiConfiguration.Status.CURRENT) {
+            setNetworkStatus(config, WifiConfiguration.Status.ENABLED);
+        }
+        saveToStore(false);
+        return true;
     }
 
     /**
-     * Fetch the static IP configuration for a given network id
+     * Set default GW MAC address for the provided network.
+     *
+     * @param networkId  network ID corresponding to the network.
+     * @param macAddress MAC address of the gateway to be set.
+     * @return true if the network was found, false otherwise.
      */
-    StaticIpConfiguration getStaticIpConfiguration(int netId) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        if (config != null) {
-            return config.getStaticIpConfiguration();
+    public boolean setNetworkDefaultGwMacAddress(int networkId, String macAddress) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
         }
-        return null;
+        config.defaultGwMacAddress = macAddress;
+        return true;
     }
 
     /**
-     * Set the static IP configuration for a given network id
+     * Clear the {@link NetworkSelectionStatus#mCandidate},
+     * {@link NetworkSelectionStatus#mCandidateScore} &
+     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
+     *
+     * This is invoked by Network Selector at the start of every selection procedure to clear all
+     * configured networks' scan-result-candidates.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @return true if the network was found, false otherwise.
      */
-    void setStaticIpConfiguration(int netId, StaticIpConfiguration staticIpConfiguration) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        if (config != null) {
-            config.setStaticIpConfiguration(staticIpConfiguration);
+    public boolean clearNetworkCandidateScanResult(int networkId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Clear network candidate scan result for " + networkId);
         }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.getNetworkSelectionStatus().setCandidate(null);
+        config.getNetworkSelectionStatus().setCandidateScore(Integer.MIN_VALUE);
+        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(false);
+        return true;
     }
 
     /**
-     * set default GW MAC address
+     * Set the {@link NetworkSelectionStatus#mCandidate},
+     * {@link NetworkSelectionStatus#mCandidateScore} &
+     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
+     *
+     * This is invoked by Network Selector when it sees a network during network selection procedure
+     * to set the scan result candidate.
+     *
+     * @param networkId  network ID corresponding to the network.
+     * @param scanResult Candidate ScanResult associated with this network.
+     * @param score      Score assigned to the candidate.
+     * @return true if the network was found, false otherwise.
      */
-    void setDefaultGwMacAddress(int netId, String macAddress) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        if (config != null) {
-            //update defaultGwMacAddress
-            config.defaultGwMacAddress = macAddress;
+    public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Set network candidate scan result " + scanResult + " for " + networkId);
         }
-    }
-
-
-    /**
-     * Fetch the proxy properties for a given network id
-     * @param netId id
-     * @return ProxyInfo for the network id
-     */
-    ProxyInfo getProxyProperties(int netId) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        if (config != null) {
-            return config.getHttpProxy();
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
         }
-        return null;
+        config.getNetworkSelectionStatus().setCandidate(scanResult);
+        config.getNetworkSelectionStatus().setCandidateScore(score);
+        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
+        return true;
     }
 
     /**
-     * Return if the specified network is using static IP
-     * @param netId id
-     * @return {@code true} if using static ip for netId
+     * Iterate through all the saved networks and remove the provided configuration from the
+     * {@link NetworkSelectionStatus#mConnectChoice} from them.
+     *
+     * This is invoked when a network is removed from our records.
+     *
+     * @param connectChoiceConfigKey ConfigKey corresponding to the network that is being removed.
      */
-    boolean isUsingStaticIp(int netId) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        if (config != null && config.getIpAssignment() == IpAssignment.STATIC) {
-            return true;
+    private void removeConnectChoiceFromAllNetworks(String connectChoiceConfigKey) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Removing connect choice from all networks " + connectChoiceConfigKey);
         }
-        return false;
-    }
-
-    boolean isEphemeral(int netId) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        return config != null && config.ephemeral;
-    }
-
-    boolean getMeteredHint(int netId) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
-        return config != null && config.meteredHint;
-    }
-
-    /**
-     * Should be called when a single network configuration is made.
-     * @param network The network configuration that 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 sendConfiguredNetworksChangedBroadcast(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);
-        intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network);
-        intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    /**
-     * Should be called when multiple network configuration changes are made.
-     */
-    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);
-    }
-
-    void loadConfiguredNetworks() {
-
-        final Map<String, WifiConfiguration> configs = new HashMap<>();
-        final SparseArray<Map<String, String>> networkExtras = new SparseArray<>();
-        mLastPriority = mWifiConfigStore.loadNetworks(configs, networkExtras);
-
-        readNetworkHistory(configs);
-        readPasspointConfig(configs, networkExtras);
-
-        // We are only now updating mConfiguredNetworks for two reasons:
-        // 1) The information required to compute configKeys is spread across wpa_supplicant.conf
-        //    and networkHistory.txt. Thus, we had to load both files first.
-        // 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) {
-                    log("Ignoring network " + config.networkId + " because the configKey loaded "
-                            + "from wpa_supplicant.conf is not valid.");
-                }
-                mWifiConfigStore.removeNetwork(config);
-                continue;
-            }
-            mConfiguredNetworks.put(config);
-        }
-
-        readIpAndProxyConfigurations();
-
-        sendConfiguredNetworksChangedBroadcast();
-
-        if (mShowNetworks) {
-            localLog("loadConfiguredNetworks loaded " + mConfiguredNetworks.sizeForAllUsers()
-                    + " networks (for all users)");
-        }
-
-        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(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
-        }
-    }
-
-    private void logContents(String file) {
-        localLogAndLogcat("--- Begin " + file + " ---");
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new FileReader(file));
-            for (String line = reader.readLine(); line != null; line = reader.readLine()) {
-                localLogAndLogcat(line);
-            }
-        } catch (FileNotFoundException e) {
-            localLog("Could not open " + file + ", " + e);
-            Log.w(TAG, "Could not open " + file + ", " + e);
-        } catch (IOException e) {
-            localLog("Could not read " + file + ", " + e);
-            Log.w(TAG, "Could not read " + file + ", " + e);
-        } finally {
-            try {
-                if (reader != null) {
-                    reader.close();
-                }
-            } catch (IOException e) {
-                // Just ignore the fact that we couldn't close
-            }
-        }
-        localLogAndLogcat("--- End " + file + " Contents ---");
-    }
-
-    private Map<String, String> readNetworkVariablesFromSupplicantFile(String key) {
-        return mWifiConfigStore.readNetworkVariablesFromSupplicantFile(key);
-    }
-
-    private String readNetworkVariableFromSupplicantFile(String configKey, String key) {
-        long start = SystemClock.elapsedRealtimeNanos();
-        Map<String, String> data = mWifiConfigStore.readNetworkVariablesFromSupplicantFile(key);
-        long end = SystemClock.elapsedRealtimeNanos();
-
-        if (sVDBG) {
-            localLog("readNetworkVariableFromSupplicantFile configKey=[" + configKey + "] key="
-                    + key + " duration=" + (long) (end - start));
-        }
-        return data.get(configKey);
-    }
-
-    boolean needsUnlockedKeyStore() {
-
-        // Any network using certificates to authenticate access requires
-        // unlocked key store; unless the certificates can be stored with
-        // hardware encryption
-
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-
-            if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP)
-                    && config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
-
-                if (needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    void readPasspointConfig(Map<String, WifiConfiguration> configs,
-            SparseArray<Map<String, String>> networkExtras) {
-        List<HomeSP> homeSPs;
-        try {
-            homeSPs = mMOManager.loadAllSPs();
-        } catch (IOException e) {
-            loge("Could not read " + PPS_FILE + " : " + e);
+        if (connectChoiceConfigKey == null) {
             return;
         }
-
-        int matchedConfigs = 0;
-        for (HomeSP homeSp : homeSPs) {
-            String fqdn = homeSp.getFQDN();
-            Log.d(TAG, "Looking for " + fqdn);
-            for (WifiConfiguration config : configs.values()) {
-                Log.d(TAG, "Testing " + config.SSID);
-
-                if (config.enterpriseConfig == null) {
-                    continue;
-                }
-                final String configFqdn =
-                        networkExtras.get(config.networkId).get(WifiConfigStore.ID_STRING_KEY_FQDN);
-                if (configFqdn != null && configFqdn.equals(fqdn)) {
-                    Log.d(TAG, "Matched " + configFqdn + " with " + config.networkId);
-                    ++matchedConfigs;
-                    config.FQDN = fqdn;
-                    config.providerFriendlyName = homeSp.getFriendlyName();
-
-                    HashSet<Long> roamingConsortiumIds = homeSp.getRoamingConsortiums();
-                    config.roamingConsortiumIds = new long[roamingConsortiumIds.size()];
-                    int i = 0;
-                    for (long id : roamingConsortiumIds) {
-                        config.roamingConsortiumIds[i] = id;
-                        i++;
-                    }
-                    IMSIParameter imsiParameter = homeSp.getCredential().getImsi();
-                    config.enterpriseConfig.setPlmn(
-                            imsiParameter != null ? imsiParameter.toString() : null);
-                    config.enterpriseConfig.setRealm(homeSp.getCredential().getRealm());
-                }
+        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
+            WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+            String connectChoice = status.getConnectChoice();
+            if (TextUtils.equals(connectChoice, connectChoiceConfigKey)) {
+                Log.d(TAG, "remove connect choice:" + connectChoice + " from " + config.SSID
+                        + " : " + config.networkId);
+                clearNetworkConnectChoice(config.networkId);
             }
         }
-
-        Log.d(TAG, "loaded " + matchedConfigs + " passpoint configs");
-    }
-
-    public void writePasspointConfigs(final String fqdn, final HomeSP homeSP) {
-        mWriter.write(PPS_FILE, new DelayedDiskWrite.Writer() {
-            @Override
-            public void onWriteCalled(DataOutputStream out) throws IOException {
-                try {
-                    if (homeSP != null) {
-                        mMOManager.addSP(homeSP);
-                    } else {
-                        mMOManager.removeSP(fqdn);
-                    }
-                } catch (IOException e) {
-                    loge("Could not write " + PPS_FILE + " : " + e);
-                }
-            }
-        }, false);
     }
 
     /**
-     *  Write network history, WifiConfigurations and mScanDetailCaches to file.
+     * Clear the {@link NetworkSelectionStatus#mConnectChoice} &
+     * {@link NetworkSelectionStatus#mConnectChoiceTimestamp} for the provided network.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @return true if the network was found, false otherwise.
      */
-    private void readNetworkHistory(Map<String, WifiConfiguration> configs) {
-        mWifiNetworkHistory.readNetworkHistory(configs,
-                mScanDetailCaches,
-                mDeletedEphemeralSSIDs);
+    public boolean clearNetworkConnectChoice(int networkId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Clear network connect choice for " + networkId);
+        }
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.getNetworkSelectionStatus().setConnectChoice(null);
+        config.getNetworkSelectionStatus().setConnectChoiceTimestamp(
+                NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+        saveToStore(false);
+        return true;
     }
 
     /**
-     *  Read Network history from file, merge it into mConfiguredNetowrks and mScanDetailCaches
+     * Set the {@link NetworkSelectionStatus#mConnectChoice} &
+     * {@link NetworkSelectionStatus#mConnectChoiceTimestamp} for the provided network.
+     *
+     * This is invoked by Network Selector when the user overrides the currently connected network
+     * choice.
+     *
+     * @param networkId              network ID corresponding to the network.
+     * @param connectChoiceConfigKey ConfigKey corresponding to the network which was chosen over
+     *                               this network.
+     * @param timestamp              timestamp at which the choice was made.
+     * @return true if the network was found, false otherwise.
      */
-    public void writeKnownNetworkHistory() {
-        final List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
-            networks.add(new WifiConfiguration(config));
+    public boolean setNetworkConnectChoice(
+            int networkId, String connectChoiceConfigKey, long timestamp) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Set network connect choice " + connectChoiceConfigKey + " for " + networkId);
         }
-        mWifiNetworkHistory.writeKnownNetworkHistory(networks,
-                mScanDetailCaches,
-                mDeletedEphemeralSSIDs);
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.getNetworkSelectionStatus().setConnectChoice(connectChoiceConfigKey);
+        config.getNetworkSelectionStatus().setConnectChoiceTimestamp(timestamp);
+        saveToStore(false);
+        return true;
     }
 
-    public void setAndEnableLastSelectedConfiguration(int netId) {
-        if (sVDBG) {
-            logd("setLastSelectedConfiguration " + Integer.toString(netId));
+    /**
+     * Increments the number of no internet access reports in the provided network.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @return true if the network was found, false otherwise.
+     */
+    public boolean incrementNetworkNoInternetAccessReports(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
         }
-        if (netId == WifiConfiguration.INVALID_NETWORK_ID) {
-            mLastSelectedConfiguration = null;
-            mLastSelectedTimeStamp = -1;
-        } else {
-            WifiConfiguration selected = getWifiConfiguration(netId);
-            if (selected == null) {
-                mLastSelectedConfiguration = null;
-                mLastSelectedTimeStamp = -1;
-            } else {
-                mLastSelectedConfiguration = selected.configKey();
-                mLastSelectedTimeStamp = mClock.elapsedRealtime();
-                updateNetworkSelectionStatus(netId,
-                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-                if (sVDBG) {
-                    logd("setLastSelectedConfiguration now: " + mLastSelectedConfiguration);
-                }
-            }
-        }
+        config.numNoInternetAccessReports++;
+        return true;
     }
 
-    public void setLatestUserSelectedConfiguration(WifiConfiguration network) {
-        if (network != null) {
-            mLastSelectedConfiguration = network.configKey();
-            mLastSelectedTimeStamp = mClock.elapsedRealtime();
+    /**
+     * Sets the internet access is validated or not in the provided network.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @param validated Whether access is validated or not.
+     * @return true if the network was found, false otherwise.
+     */
+    public boolean setNetworkValidatedInternetAccess(int networkId, boolean validated) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
         }
+        config.validatedInternetAccess = validated;
+        config.numNoInternetAccessReports = 0;
+        saveToStore(false);
+        return true;
     }
 
-    public String getLastSelectedConfiguration() {
-        return mLastSelectedConfiguration;
+    /**
+     * Sets whether the internet access is expected or not in the provided network.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @param expected  Whether access is expected or not.
+     * @return true if the network was found, false otherwise.
+     */
+    public boolean setNetworkNoInternetAccessExpected(int networkId, boolean expected) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.noInternetAccessExpected = expected;
+        return true;
     }
 
+    /**
+     * Helper method to clear out the {@link #mNextNetworkId} user/app network selection. This
+     * is done when either the corresponding network is either removed or disabled.
+     */
+    private void clearLastSelectedNetwork() {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Clearing last selected network");
+        }
+        mLastSelectedNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+        mLastSelectedTimeStamp = NetworkSelectionStatus.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
+    }
+
+    /**
+     * Helper method to mark a network as the last selected one by an app/user. This is set
+     * when an app invokes {@link #enableNetwork(int, boolean, int)} with |disableOthers| flag set.
+     * This is used by network selector to assign a special bonus during network selection.
+     */
+    private void setLastSelectedNetwork(int networkId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Setting last selected network to " + networkId);
+        }
+        mLastSelectedNetworkId = networkId;
+        mLastSelectedTimeStamp = mClock.getElapsedSinceBootMillis();
+    }
+
+    /**
+     * Retrieve the network Id corresponding to the last network that was explicitly selected by
+     * an app/user.
+     *
+     * @return network Id corresponding to the last selected network.
+     */
+    public int getLastSelectedNetwork() {
+        return mLastSelectedNetworkId;
+    }
+
+    /**
+     * Retrieve the configKey corresponding to the last network that was explicitly selected by
+     * an app/user.
+     *
+     * @return network Id corresponding to the last selected network.
+     */
+    public String getLastSelectedNetworkConfigKey() {
+        if (mLastSelectedNetworkId == WifiConfiguration.INVALID_NETWORK_ID) {
+            return "";
+        }
+        WifiConfiguration config = getInternalConfiguredNetwork(mLastSelectedNetworkId);
+        if (config == null) {
+            return "";
+        }
+        return config.configKey();
+    }
+
+    /**
+     * Retrieve the time stamp at which a network was explicitly selected by an app/user.
+     *
+     * @return timestamp in milliseconds from boot when this was set.
+     */
     public long getLastSelectedTimeStamp() {
         return mLastSelectedTimeStamp;
     }
 
-    public boolean isLastSelectedConfiguration(WifiConfiguration config) {
-        return (mLastSelectedConfiguration != null
-                && config != null
-                && mLastSelectedConfiguration.equals(config.configKey()));
+    /**
+     * 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.
+     */
+    public ScanDetailCache getScanDetailCacheForNetwork(int networkId) {
+        return mScanDetailCaches.get(networkId);
     }
 
-    private void writeIpAndProxyConfigurations() {
-        final SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>();
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
-            if (!config.ephemeral) {
-                networks.put(configKey(config), config.getIpConfiguration());
-            }
-        }
-
-        mIpconfigStore.writeIpAndProxyConfigurations(IP_CONFIG_FILE, networks);
-    }
-
-    private void readIpAndProxyConfigurations() {
-        SparseArray<IpConfiguration> networks =
-                mIpconfigStore.readIpAndProxyConfigurations(IP_CONFIG_FILE);
-
-        if (networks == null || networks.size() == 0) {
-            // IpConfigStore.readIpAndProxyConfigurations has already logged an error.
-            return;
-        }
-
-        for (int i = 0; i < networks.size(); i++) {
-            int id = networks.keyAt(i);
-            WifiConfiguration config = mConfiguredNetworks.getByConfigKeyIDForAllUsers(id);
-            // This is the only place the map is looked up through a (dangerous) hash-value!
-
-            if (config == null || config.ephemeral) {
-                logd("configuration found for missing network, nid=" + id
-                        + ", ignored, networks.size=" + Integer.toString(networks.size()));
-            } else {
-                config.setIpConfiguration(networks.valueAt(i));
-            }
-        }
-    }
-
-    private NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config, int uid) {
-        /*
-         * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
-         * network configuration. Otherwise, the networkId should
-         * refer to an existing configuration.
-         */
-
-        if (sVDBG) localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
-        if (config.isPasspoint() && !mMOManager.isEnabled()) {
-            Log.e(TAG, "Passpoint is not enabled");
-            return new NetworkUpdateResult(INVALID_NETWORK_ID);
-        }
-
-        boolean newNetwork = false;
-        boolean existingMO = false;
-        WifiConfiguration currentConfig;
-        // networkId of INVALID_NETWORK_ID means we want to create a new network
-        if (config.networkId == INVALID_NETWORK_ID) {
-            // Try to fetch the existing config using configKey
-            currentConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
-            if (currentConfig != null) {
-                config.networkId = currentConfig.networkId;
-            } else {
-                if (mMOManager.getHomeSP(config.FQDN) != null) {
-                    logd("addOrUpdateNetworkNative passpoint " + config.FQDN
-                            + " was found, but no network Id");
-                    existingMO = true;
-                }
-                newNetwork = true;
-            }
-        } else {
-            // Fetch the existing config using networkID
-            currentConfig = mConfiguredNetworks.getForCurrentUser(config.networkId);
-        }
-
-        // originalConfig is used to check for credential and config changes that would cause
-        // HasEverConnected to be set to false.
-        WifiConfiguration originalConfig = new WifiConfiguration(currentConfig);
-
-        if (!mWifiConfigStore.addOrUpdateNetwork(config, currentConfig,
-                    mSystemSupportsFastBssTransition)) {
-            return new NetworkUpdateResult(INVALID_NETWORK_ID);
-        }
-        int netId = config.networkId;
-        String savedConfigKey = config.configKey();
-
-        /* An update of the network variables requires reading them
-         * back from the supplicant to update mConfiguredNetworks.
-         * This is because some of the variables (SSID, wep keys &
-         * passphrases) reflect different values when read back than
-         * when written. For example, wep key is stored as * irrespective
-         * of the value sent to the supplicant.
-         */
-        if (currentConfig == null) {
-            currentConfig = new WifiConfiguration();
-            currentConfig.setIpAssignment(IpAssignment.DHCP);
-            currentConfig.setProxySettings(ProxySettings.NONE);
-            currentConfig.networkId = netId;
-            if (config != null) {
-                // Carry over the creation parameters
-                currentConfig.selfAdded = config.selfAdded;
-                currentConfig.didSelfAdd = config.didSelfAdd;
-                currentConfig.ephemeral = config.ephemeral;
-                currentConfig.meteredHint = config.meteredHint;
-                currentConfig.useExternalScores = config.useExternalScores;
-                currentConfig.lastConnectUid = config.lastConnectUid;
-                currentConfig.lastUpdateUid = config.lastUpdateUid;
-                currentConfig.creatorUid = config.creatorUid;
-                currentConfig.creatorName = config.creatorName;
-                currentConfig.lastUpdateName = config.lastUpdateName;
-                currentConfig.peerWifiConfiguration = config.peerWifiConfiguration;
-                currentConfig.FQDN = config.FQDN;
-                currentConfig.providerFriendlyName = config.providerFriendlyName;
-                currentConfig.roamingConsortiumIds = config.roamingConsortiumIds;
-                currentConfig.validatedInternetAccess = config.validatedInternetAccess;
-                currentConfig.numNoInternetAccessReports = config.numNoInternetAccessReports;
-                currentConfig.updateTime = config.updateTime;
-                currentConfig.creationTime = config.creationTime;
-                currentConfig.shared = config.shared;
-                currentConfig.isCarrierNetwork = config.isCarrierNetwork;
-            }
-            if (DBG) {
-                log("created new config netId=" + Integer.toString(netId)
-                        + " uid=" + Integer.toString(currentConfig.creatorUid)
-                        + " name=" + currentConfig.creatorName);
-            }
-        }
-
-        /* save HomeSP object for passpoint networks */
-        HomeSP homeSP = null;
-
-        if (!existingMO && config.isPasspoint()) {
-            try {
-                if (config.updateIdentifier == null) {   // Only create an MO for r1 networks
-                    Credential credential =
-                            new Credential(config.enterpriseConfig, mKeyStore, !newNetwork);
-                    HashSet<Long> roamingConsortiumIds = new HashSet<Long>();
-                    for (Long roamingConsortiumId : config.roamingConsortiumIds) {
-                        roamingConsortiumIds.add(roamingConsortiumId);
-                    }
-
-                    homeSP = new HomeSP(Collections.<String, Long>emptyMap(), config.FQDN,
-                            roamingConsortiumIds, Collections.<String>emptySet(),
-                            Collections.<Long>emptySet(), Collections.<Long>emptyList(),
-                            config.providerFriendlyName, null, credential);
-
-                    log("created a homeSP object for " + config.networkId + ":" + config.SSID);
-                }
-
-                /* fix enterprise config properties for passpoint */
-                currentConfig.enterpriseConfig.setRealm(config.enterpriseConfig.getRealm());
-                currentConfig.enterpriseConfig.setPlmn(config.enterpriseConfig.getPlmn());
-            } catch (IOException ioe) {
-                Log.e(TAG, "Failed to create Passpoint config: " + ioe);
-                return new NetworkUpdateResult(INVALID_NETWORK_ID);
-            }
-        }
-
-        if (uid != WifiConfiguration.UNKNOWN_UID) {
-            if (newNetwork) {
-                currentConfig.creatorUid = uid;
-            } else {
-                currentConfig.lastUpdateUid = uid;
-            }
-        }
-
-        // For debug, record the time the configuration was modified
-        StringBuilder sb = new StringBuilder();
-        sb.append("time=");
-        Calendar c = Calendar.getInstance();
-        c.setTimeInMillis(mClock.currentTimeMillis());
-        sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
-
-        if (newNetwork) {
-            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.
-            currentConfig.ephemeral = false;
-            if (DBG) {
-                log("remove ephemeral status netId=" + Integer.toString(netId)
-                        + " " + currentConfig.configKey());
-            }
-        }
-
-        if (sVDBG) 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)) {
-                loge("Failed to set network metadata. Removing config " + config.networkId);
-                mWifiConfigStore.removeNetwork(config);
-                return new NetworkUpdateResult(INVALID_NETWORK_ID);
-            }
-        }
-
-        boolean passwordChanged = false;
-        // check passed in config to see if it has more than a password set.
-        if (!newNetwork && config.preSharedKey != null && !config.preSharedKey.equals("*")) {
-            passwordChanged = true;
-        }
-
-        if (newNetwork || passwordChanged || wasCredentialChange(originalConfig, currentConfig)) {
-            currentConfig.getNetworkSelectionStatus().setHasEverConnected(false);
-        }
-
-        // Persist configuration paramaters that are not saved by supplicant.
-        if (config.lastUpdateName != null) {
-            currentConfig.lastUpdateName = config.lastUpdateName;
-        }
-        if (config.lastUpdateUid != WifiConfiguration.UNKNOWN_UID) {
-            currentConfig.lastUpdateUid = config.lastUpdateUid;
-        }
-
-        mConfiguredNetworks.put(currentConfig);
-
-        NetworkUpdateResult result =
-                writeIpAndProxyConfigurationsOnChange(currentConfig, config, newNetwork);
-        result.setIsNewNetwork(newNetwork);
-        result.setNetworkId(netId);
-
-        if (homeSP != null) {
-            writePasspointConfigs(null, homeSP);
-        }
-
-        saveConfig();
-        writeKnownNetworkHistory();
-
-        return result;
-    }
-
-    private boolean wasBitSetUpdated(BitSet originalBitSet, BitSet currentBitSet) {
-        if (originalBitSet != null && currentBitSet != null) {
-            // both configs have values set, check if they are different
-            if (!originalBitSet.equals(currentBitSet)) {
-                // the BitSets are different
-                return true;
-            }
-        } else if (originalBitSet != null || currentBitSet != null) {
-            return true;
-        }
-        return false;
-    }
-
-    private boolean wasCredentialChange(WifiConfiguration originalConfig,
-            WifiConfiguration currentConfig) {
-        // Check if any core WifiConfiguration parameters changed that would impact new connections
-        if (originalConfig == null) {
-            return true;
-        }
-
-        if (wasBitSetUpdated(originalConfig.allowedKeyManagement,
-                currentConfig.allowedKeyManagement)) {
-            return true;
-        }
-
-        if (wasBitSetUpdated(originalConfig.allowedProtocols, currentConfig.allowedProtocols)) {
-            return true;
-        }
-
-        if (wasBitSetUpdated(originalConfig.allowedAuthAlgorithms,
-                currentConfig.allowedAuthAlgorithms)) {
-            return true;
-        }
-
-        if (wasBitSetUpdated(originalConfig.allowedPairwiseCiphers,
-                currentConfig.allowedPairwiseCiphers)) {
-            return true;
-        }
-
-        if (wasBitSetUpdated(originalConfig.allowedGroupCiphers,
-                currentConfig.allowedGroupCiphers)) {
-            return true;
-        }
-
-        if (originalConfig.wepKeys != null && currentConfig.wepKeys != null) {
-            if (originalConfig.wepKeys.length == currentConfig.wepKeys.length) {
-                for (int i = 0; i < originalConfig.wepKeys.length; i++) {
-                    if (!Objects.equals(originalConfig.wepKeys[i], currentConfig.wepKeys[i])) {
-                        return true;
-                    }
-                }
-            } else {
-                return true;
-            }
-        }
-
-        if (originalConfig.hiddenSSID != currentConfig.hiddenSSID) {
-            return true;
-        }
-
-        if (originalConfig.requirePMF != currentConfig.requirePMF) {
-            return true;
-        }
-
-        if (wasEnterpriseConfigChange(originalConfig.enterpriseConfig,
-                currentConfig.enterpriseConfig)) {
-            return true;
-        }
-        return false;
-    }
-
-
-    protected boolean wasEnterpriseConfigChange(WifiEnterpriseConfig originalEnterpriseConfig,
-            WifiEnterpriseConfig currentEnterpriseConfig) {
-        if (originalEnterpriseConfig != null && currentEnterpriseConfig != null) {
-            if (originalEnterpriseConfig.getEapMethod() != currentEnterpriseConfig.getEapMethod()) {
-                return true;
-            }
-
-            if (originalEnterpriseConfig.getPhase2Method()
-                    != currentEnterpriseConfig.getPhase2Method()) {
-                return true;
-            }
-
-            X509Certificate[] originalCaCerts = originalEnterpriseConfig.getCaCertificates();
-            X509Certificate[] currentCaCerts = currentEnterpriseConfig.getCaCertificates();
-
-            if (originalCaCerts != null && currentCaCerts != null) {
-                if (originalCaCerts.length == currentCaCerts.length) {
-                    for (int i = 0; i < originalCaCerts.length; i++) {
-                        if (!originalCaCerts[i].equals(currentCaCerts[i])) {
-                            return true;
-                        }
-                    }
-                } else {
-                    // number of aliases is different, so the configs are different
-                    return true;
-                }
-            } else {
-                // one of the enterprise configs may have aliases
-                if (originalCaCerts != null || currentCaCerts != null) {
-                    return true;
-                }
-            }
-        } else {
-            // One of the configs may have an enterpriseConfig
-            if (originalEnterpriseConfig != null || currentEnterpriseConfig != null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public WifiConfiguration getWifiConfigForHomeSP(HomeSP homeSP) {
-        WifiConfiguration config = mConfiguredNetworks.getByFQDNForCurrentUser(homeSP.getFQDN());
-        if (config == null) {
-            Log.e(TAG, "Could not find network for homeSP " + homeSP.getFQDN());
-        }
-        return config;
-    }
-
-    public HomeSP getHomeSPForConfig(WifiConfiguration config) {
-        WifiConfiguration storedConfig = mConfiguredNetworks.getForCurrentUser(config.networkId);
-        return storedConfig != null && storedConfig.isPasspoint()
-                ? mMOManager.getHomeSP(storedConfig.FQDN)
-                : null;
-    }
-
-    public ScanDetailCache getScanDetailCache(WifiConfiguration config) {
+    /**
+     * 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 = mScanDetailCaches.get(config.networkId);
+        ScanDetailCache cache = getScanDetailCacheForNetwork(config.networkId);
         if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            cache = new ScanDetailCache(config);
+            cache = new ScanDetailCache(
+                    config, SCAN_CACHE_ENTRIES_MAX_SIZE, SCAN_CACHE_ENTRIES_TRIM_SIZE);
             mScanDetailCaches.put(config.networkId, cache);
         }
         return cache;
     }
 
     /**
-     * This function run thru the Saved WifiConfigurations and check if some should be linked.
-     * @param config
+     * 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.
      */
-    public void linkConfiguration(WifiConfiguration config) {
-        if (!WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                mUserManager.getProfiles(mCurrentUserId))) {
-            logd("linkConfiguration: Attempting to link config " + config.configKey()
-                    + " that is not visible to the current user.");
-            return;
-        }
-
-        if (getScanDetailCache(config) != null && getScanDetailCache(config).size() > 6) {
-            // Ignore configurations with large number of BSSIDs
-            return;
-        }
-        if (!config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
-            // Only link WPA_PSK config
-            return;
-        }
-        for (WifiConfiguration link : mConfiguredNetworks.valuesForCurrentUser()) {
-            boolean doLink = false;
-
-            if (link.configKey().equals(config.configKey())) {
-                continue;
-            }
-
-            if (link.ephemeral) {
-                continue;
-            }
-
-            // Autojoin will be allowed to dynamically jump from a linked configuration
-            // to another, hence only link configurations that have equivalent level of security
-            if (!link.allowedKeyManagement.equals(config.allowedKeyManagement)) {
-                continue;
-            }
-
-            ScanDetailCache linkedScanDetailCache = getScanDetailCache(link);
-            if (linkedScanDetailCache != null && linkedScanDetailCache.size() > 6) {
-                // Ignore configurations with large number of BSSIDs
-                continue;
-            }
-
-            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) {
-                        logd("linkConfiguration link due to same gw " + link.SSID
-                                + " and " + config.SSID + " GW " + config.defaultGwMacAddress);
-                    }
-                    doLink = true;
-                }
-            } else {
-                // We do not know BOTH default gateways hence we will try to link
-                // hoping that WifiConfigurations are indeed behind the same gateway.
-                // once both WifiConfiguration have been tried and thus once both efault gateways
-                // are known we will revisit the choice of linking them
-                if ((getScanDetailCache(config) != null)
-                        && (getScanDetailCache(config).size() <= 6)) {
-
-                    for (String abssid : getScanDetailCache(config).keySet()) {
-                        for (String bbssid : linkedScanDetailCache.keySet()) {
-                            if (sVVDBG) {
-                                logd("linkConfiguration try to link due to DBDC BSSID match "
-                                        + link.SSID + " and " + config.SSID + " bssida " + abssid
-                                        + " bssidb " + bbssid);
-                            }
-                            if (abssid.regionMatches(true, 0, bbssid, 0, 16)) {
-                                // If first 16 ascii characters of BSSID matches,
-                                // we assume this is a DBDC
-                                doLink = true;
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (doLink && mOnlyLinkSameCredentialConfigurations) {
-                String apsk =
-                        readNetworkVariableFromSupplicantFile(link.configKey(), "psk");
-                String bpsk =
-                        readNetworkVariableFromSupplicantFile(config.configKey(), "psk");
-                if (apsk == null || bpsk == null
-                        || TextUtils.isEmpty(apsk) || TextUtils.isEmpty(apsk)
-                        || apsk.equals("*") || apsk.equals(DELETED_CONFIG_PSK)
-                        || !apsk.equals(bpsk)) {
-                    doLink = false;
-                }
-            }
-
-            if (doLink) {
-                if (sVDBG) {
-                    logd("linkConfiguration: will link " + link.configKey()
-                            + " and " + config.configKey());
-                }
-                if (link.linkedConfigurations == null) {
-                    link.linkedConfigurations = new HashMap<String, Integer>();
-                }
-                if (config.linkedConfigurations == null) {
-                    config.linkedConfigurations = new HashMap<String, Integer>();
-                }
-                if (link.linkedConfigurations.get(config.configKey()) == null) {
-                    link.linkedConfigurations.put(config.configKey(), Integer.valueOf(1));
-                }
-                if (config.linkedConfigurations.get(link.configKey()) == null) {
-                    config.linkedConfigurations.put(link.configKey(), Integer.valueOf(1));
-                }
-            } else {
-                if (link.linkedConfigurations != null
-                        && (link.linkedConfigurations.get(config.configKey()) != null)) {
-                    if (sVDBG) {
-                        logd("linkConfiguration: un-link " + config.configKey()
-                                + " from " + link.configKey());
-                    }
-                    link.linkedConfigurations.remove(config.configKey());
-                }
-                if (config.linkedConfigurations != null
-                        && (config.linkedConfigurations.get(link.configKey()) != null)) {
-                    if (sVDBG) {
-                        logd("linkConfiguration: un-link " + link.configKey()
-                                + " from " + config.configKey());
-                    }
-                    config.linkedConfigurations.remove(link.configKey());
-                }
-            }
-        }
-    }
-
-    public HashSet<Integer> makeChannelList(WifiConfiguration config, int age) {
-        if (config == null) {
-            return null;
-        }
-        long now_ms = mClock.currentTimeMillis();
-
-        HashSet<Integer> channels = new HashSet<Integer>();
-
-        //get channels for this configuration, if there are at least 2 BSSIDs
-        if (getScanDetailCache(config) == null && config.linkedConfigurations == null) {
-            return null;
-        }
-
-        if (sVDBG) {
-            StringBuilder dbg = new StringBuilder();
-            dbg.append("makeChannelList age=" + Integer.toString(age)
-                    + " for " + config.configKey()
-                    + " max=" + mMaxNumActiveChannelsForPartialScans);
-            if (getScanDetailCache(config) != null) {
-                dbg.append(" bssids=" + getScanDetailCache(config).size());
-            }
-            if (config.linkedConfigurations != null) {
-                dbg.append(" linked=" + config.linkedConfigurations.size());
-            }
-            logd(dbg.toString());
-        }
-
-        int numChannels = 0;
-        if (getScanDetailCache(config) != null && getScanDetailCache(config).size() > 0) {
-            for (ScanDetail scanDetail : getScanDetailCache(config).values()) {
-                ScanResult result = scanDetail.getScanResult();
-                //TODO : cout active and passive channels separately
-                if (numChannels > mMaxNumActiveChannelsForPartialScans.get()) {
-                    break;
-                }
-                if (sVDBG) {
-                    boolean test = (now_ms - result.seen) < age;
-                    logd("has " + result.BSSID + " freq=" + Integer.toString(result.frequency)
-                            + " age=" + Long.toString(now_ms - result.seen) + " ?=" + test);
-                }
-                if (((now_ms - result.seen) < age)) {
-                    channels.add(result.frequency);
-                    numChannels++;
-                }
-            }
-        }
-
-        //get channels for linked configurations
-        if (config.linkedConfigurations != null) {
-            for (String key : config.linkedConfigurations.keySet()) {
-                WifiConfiguration linked = getWifiConfiguration(key);
-                if (linked == null) {
-                    continue;
-                }
-                if (getScanDetailCache(linked) == null) {
-                    continue;
-                }
-                for (ScanDetail scanDetail : getScanDetailCache(linked).values()) {
-                    ScanResult result = scanDetail.getScanResult();
-                    if (sVDBG) {
-                        logd("has link: " + result.BSSID
-                                + " freq=" + Integer.toString(result.frequency)
-                                + " age=" + Long.toString(now_ms - result.seen));
-                    }
-                    if (numChannels > mMaxNumActiveChannelsForPartialScans.get()) {
-                        break;
-                    }
-                    if (((now_ms - result.seen) < age)) {
-                        channels.add(result.frequency);
-                        numChannels++;
-                    }
-                }
-            }
-        }
-        return channels;
-    }
-
-    private Map<HomeSP, PasspointMatch> matchPasspointNetworks(ScanDetail scanDetail) {
-        // Nothing to do if no Hotspot 2.0 provider is configured.
-        if (!mMOManager.isConfigured()) {
-            return null;
-        }
-        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
-        if (!networkDetail.hasInterworking()) {
-            return null;
-        }
-        updateAnqpCache(scanDetail, networkDetail.getANQPElements());
-
-        Map<HomeSP, PasspointMatch> matches = matchNetwork(scanDetail, true);
-        Log.d(Utils.hs2LogTag(getClass()), scanDetail.getSSID()
-                + " pass 1 matches: " + toMatchString(matches));
-        return matches;
-    }
-
-    private Map<HomeSP, PasspointMatch> matchNetwork(ScanDetail scanDetail, boolean query) {
-        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
-
-        ANQPData anqpData = mAnqpCache.getEntry(networkDetail);
-
-        Map<Constants.ANQPElementType, ANQPElement> anqpElements =
-                anqpData != null ? anqpData.getANQPElements() : null;
-
-        boolean queried = !query;
-        Collection<HomeSP> homeSPs = mMOManager.getLoadedSPs().values();
-        Map<HomeSP, PasspointMatch> matches = new HashMap<>(homeSPs.size());
-        Log.d(Utils.hs2LogTag(getClass()), "match nwk " + scanDetail.toKeyString()
-                + ", anqp " + (anqpData != null ? "present" : "missing")
-                + ", query " + query + ", home sps: " + homeSPs.size());
-
-        for (HomeSP homeSP : homeSPs) {
-            PasspointMatch match = homeSP.match(networkDetail, anqpElements, mSIMAccessor);
-
-            Log.d(Utils.hs2LogTag(getClass()), " -- "
-                    + homeSP.getFQDN() + ": match " + match + ", queried " + queried);
-
-            if ((match == PasspointMatch.Incomplete || mEnableOsuQueries) && !queried) {
-                boolean matchSet = match == PasspointMatch.Incomplete;
-                boolean osu = mEnableOsuQueries;
-                List<Constants.ANQPElementType> querySet =
-                        ANQPFactory.buildQueryList(networkDetail, matchSet, osu);
-                if (networkDetail.queriable(querySet)) {
-                    querySet = mAnqpCache.initiate(networkDetail, querySet);
-                    if (querySet != null) {
-                        mSupplicantBridge.startANQP(scanDetail, querySet);
-                    }
-                }
-                queried = true;
-            }
-            matches.put(homeSP, match);
-        }
-        return matches;
-    }
-
-    public Map<Constants.ANQPElementType, ANQPElement> getANQPData(NetworkDetail network) {
-        ANQPData data = mAnqpCache.getEntry(network);
-        return data != null ? data.getANQPElements() : null;
-    }
-
-    public SIMAccessor getSIMAccessor() {
-        return mSIMAccessor;
-    }
-
-    public void notifyANQPDone(Long bssid, boolean success) {
-        mSupplicantBridge.notifyANQPDone(bssid, success);
-    }
-
-    public void notifyIconReceived(IconEvent iconEvent) {
-        Intent intent = new Intent(WifiManager.PASSPOINT_ICON_RECEIVED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_PASSPOINT_ICON_BSSID, iconEvent.getBSSID());
-        intent.putExtra(WifiManager.EXTRA_PASSPOINT_ICON_FILE, iconEvent.getFileName());
-        try {
-            intent.putExtra(WifiManager.EXTRA_PASSPOINT_ICON_DATA,
-                    mSupplicantBridge.retrieveIcon(iconEvent));
-        } catch (IOException ioe) {
-            /* Simply omit the icon data as a failure indication */
-        }
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-
-    }
-
-    private void updateAnqpCache(ScanDetail scanDetail,
-                                 Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
-        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
-
-        if (anqpElements == null) {
-            // Try to pull cached data if query failed.
-            ANQPData data = mAnqpCache.getEntry(networkDetail);
-            if (data != null) {
-                scanDetail.propagateANQPInfo(data.getANQPElements());
-            }
-            return;
-        }
-
-        mAnqpCache.update(networkDetail, anqpElements);
-    }
-
-    private static String toMatchString(Map<HomeSP, PasspointMatch> matches) {
-        StringBuilder sb = new StringBuilder();
-        for (Map.Entry<HomeSP, PasspointMatch> entry : matches.entrySet()) {
-            sb.append(' ').append(entry.getKey().getFQDN()).append("->").append(entry.getValue());
-        }
-        return sb.toString();
-    }
-
-    private void cacheScanResultForPasspointConfigs(ScanDetail scanDetail,
-            Map<HomeSP, PasspointMatch> matches,
-            List<WifiConfiguration> associatedWifiConfigurations) {
-
-        for (Map.Entry<HomeSP, PasspointMatch> entry : matches.entrySet()) {
-            PasspointMatch match = entry.getValue();
-            if (match == PasspointMatch.HomeProvider || match == PasspointMatch.RoamingProvider) {
-                WifiConfiguration config = getWifiConfigForHomeSP(entry.getKey());
-                if (config != null) {
-                    cacheScanResultForConfig(config, scanDetail, entry.getValue());
-                    if (associatedWifiConfigurations != null) {
-                        associatedWifiConfigurations.add(config);
-                    }
-                } else {
-                    Log.w(Utils.hs2LogTag(getClass()), "Failed to find config for '"
-                            + entry.getKey().getFQDN() + "'");
-                    /* perhaps the configuration was deleted?? */
-                }
-            }
-        }
-    }
-
-    private void cacheScanResultForConfig(
-            WifiConfiguration config, ScanDetail scanDetail, PasspointMatch passpointMatch) {
-
+    private void saveToScanDetailCacheForNetwork(
+            WifiConfiguration config, ScanDetail scanDetail) {
         ScanResult scanResult = scanDetail.getScanResult();
 
-        ScanDetailCache scanDetailCache = getScanDetailCache(config);
+        ScanDetailCache scanDetailCache = getOrCreateScanDetailCacheForNetwork(config);
         if (scanDetailCache == null) {
-            Log.w(TAG, "Could not allocate scan cache for " + config.SSID);
+            Log.e(TAG, "Could not allocate scan cache for " + config.getPrintableSsid());
             return;
         }
 
@@ -2647,671 +1869,462 @@
             scanResult.blackListTimestamp = result.blackListTimestamp;
             scanResult.numIpConfigFailures = result.numIpConfigFailures;
             scanResult.numConnection = result.numConnection;
-            scanResult.isAutoJoinCandidate = result.isAutoJoinCandidate;
         }
-
         if (config.ephemeral) {
             // For an ephemeral Wi-Fi config, the ScanResult should be considered
             // untrusted.
             scanResult.untrusted = true;
         }
 
-        if (scanDetailCache.size() > (MAX_NUM_SCAN_CACHE_ENTRIES + 64)) {
-            long now_dbg = 0;
-            if (sVVDBG) {
-                logd(" Will trim config " + config.configKey()
-                        + " size " + scanDetailCache.size());
+        // Add the scan detail to this network's scan detail cache.
+        scanDetailCache.put(scanDetail);
 
-                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));
-        } else {
-            scanDetailCache.put(scanDetail);
-        }
-
-        // Since we added a scan result to this configuration, re-attempt linking
-        linkConfiguration(config);
-    }
-
-    private boolean isEncryptionWep(String encryption) {
-        return encryption.contains("WEP");
-    }
-
-    private boolean isEncryptionPsk(String encryption) {
-        return encryption.contains("PSK");
-    }
-
-    private boolean isEncryptionEap(String encryption) {
-        return encryption.contains("EAP");
-    }
-
-    public boolean isOpenNetwork(String encryption) {
-        if (!isEncryptionWep(encryption) && !isEncryptionPsk(encryption)
-                && !isEncryptionEap(encryption)) {
-            return true;
-        }
-        return false;
-    }
-
-    public boolean isOpenNetwork(ScanResult scan) {
-        String scanResultEncrypt = scan.capabilities;
-        return isOpenNetwork(scanResultEncrypt);
-    }
-
-    public boolean isOpenNetwork(WifiConfiguration config) {
-        String configEncrypt = config.configKey();
-        return isOpenNetwork(configEncrypt);
+        // Since we added a scan result to this configuration, re-attempt linking.
+        // TODO: Do we really need to do this after every scan result?
+        attemptNetworkLinking(config);
     }
 
     /**
-     * Get saved WifiConfiguration associated with a scan detail.
-     * @param scanDetail input a scanDetail from the scan result
-     * @return WifiConfiguration WifiConfiguration associated with this scanDetail, null if none
+     * 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.
      */
-    public List<WifiConfiguration> getSavedNetworkFromScanDetail(ScanDetail scanDetail) {
+    private WifiConfiguration getSavedNetworkForScanDetail(ScanDetail scanDetail) {
         ScanResult scanResult = scanDetail.getScanResult();
         if (scanResult == null) {
+            Log.e(TAG, "No scan result found in scan detail");
             return null;
         }
-        List<WifiConfiguration> savedWifiConfigurations = new ArrayList<>();
-        String ssid = "\"" + scanResult.SSID + "\"";
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            if (config.SSID == null || !config.SSID.equals(ssid)) {
-                continue;
-            }
-            if (DBG) {
-                localLog("getSavedNetworkFromScanDetail(): try " + config.configKey()
-                        + " SSID=" + config.SSID + " " + scanResult.SSID + " "
-                        + scanResult.capabilities);
-            }
-            String scanResultEncrypt = scanResult.capabilities;
-            String configEncrypt = config.configKey();
-            if (isEncryptionWep(scanResultEncrypt) && isEncryptionWep(configEncrypt)
-                    || (isEncryptionPsk(scanResultEncrypt) && isEncryptionPsk(configEncrypt))
-                    || (isEncryptionEap(scanResultEncrypt) && isEncryptionEap(configEncrypt))
-                    || (isOpenNetwork(scanResultEncrypt) && isOpenNetwork(configEncrypt))) {
-                savedWifiConfigurations.add(config);
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config)) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "getSavedNetworkFromScanDetail Found " + config.configKey()
+                            + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
+                }
+                return config;
             }
         }
-        return savedWifiConfigurations;
+        return null;
     }
 
     /**
-     * Create a mapping between the scandetail and the Wificonfiguration it associated with
-     * because Passpoint, one BSSID can associated with multiple SSIDs
+     * 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
-     * @param isConnectingOrConnected input a boolean to indicate if WiFi is connecting or conncted
-     * This is used for avoiding ANQP request
-     * @return List<WifiConfiguration> a list of WifiConfigurations associated to this scanDetail
+     * @return WifiConfiguration object representing the network corresponding to the scanDetail,
+     * null if none exists.
      */
-    public List<WifiConfiguration> updateSavedNetworkWithNewScanDetail(ScanDetail scanDetail,
-            boolean isConnectingOrConnected) {
-        ScanResult scanResult = scanDetail.getScanResult();
-        if (scanResult == null) {
+    public WifiConfiguration getSavedNetworkForScanDetailAndCache(ScanDetail scanDetail) {
+        WifiConfiguration network = getSavedNetworkForScanDetail(scanDetail);
+        if (network == null) {
             return null;
         }
-        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
-        List<WifiConfiguration> associatedWifiConfigurations = new ArrayList<>();
-        if (networkDetail.hasInterworking() && !isConnectingOrConnected) {
-            Map<HomeSP, PasspointMatch> matches = matchPasspointNetworks(scanDetail);
-            if (matches != null) {
-                cacheScanResultForPasspointConfigs(scanDetail, matches,
-                        associatedWifiConfigurations);
-                //Do not return here. A BSSID can belong to both passpoint network and non-passpoint
-                //Network
-            }
+        saveToScanDetailCacheForNetwork(network, scanDetail);
+        // 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
+        // Frame), these scanResult DTIM's are negative and ignored.
+        // Used for metrics collection.
+        if (scanDetail.getNetworkDetail() != null
+                && scanDetail.getNetworkDetail().getDtimInterval() > 0) {
+            network.dtimInterval = scanDetail.getNetworkDetail().getDtimInterval();
         }
-        List<WifiConfiguration> savedConfigurations = getSavedNetworkFromScanDetail(scanDetail);
-        if (savedConfigurations != null) {
-            for (WifiConfiguration config : savedConfigurations) {
-                cacheScanResultForConfig(config, scanDetail, null);
-                associatedWifiConfigurations.add(config);
+        return createExternalWifiConfiguration(network, true);
+    }
+
+    /**
+     * Update the scan detail cache associated with current connected network with latest
+     * RSSI value in the provided WifiInfo.
+     * This is invoked when we get an RSSI poll update after connection.
+     *
+     * @param info WifiInfo instance pointing to the current connected network.
+     */
+    public void updateScanDetailCacheFromWifiInfo(WifiInfo info) {
+        WifiConfiguration config = getInternalConfiguredNetwork(info.getNetworkId());
+        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(info.getNetworkId());
+        if (config != null && scanDetailCache != null) {
+            ScanDetail scanDetail = scanDetailCache.getScanDetail(info.getBSSID());
+            if (scanDetail != null) {
+                ScanResult result = scanDetail.getScanResult();
+                long previousSeen = result.seen;
+                int previousRssi = result.level;
+                // Update the scan result
+                scanDetail.setSeen();
+                result.level = info.getRssi();
+                // Average the RSSI value
+                result.averageRssi(previousRssi, previousSeen, SCAN_RESULT_MAXIMUM_AGE_MS);
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Updating scan detail cache freq=" + result.frequency
+                            + " BSSID=" + result.BSSID
+                            + " RSSI=" + result.level
+                            + " for " + config.configKey());
+                }
             }
         }
-        if (associatedWifiConfigurations.size() == 0) {
-            return null;
-        } else {
-            return associatedWifiConfigurations;
-        }
     }
 
     /**
-     * Handles the switch to a different foreground user:
-     * - Removes all ephemeral networks
-     * - Disables private network configurations belonging to the previous foreground user
-     * - Enables private network configurations belonging to the new foreground user
+     * Save the ScanDetail to the ScanDetailCache of the given network.  This is used
+     * by {@link com.android.server.wifi.hotspot2.PasspointNetworkEvaluator} for caching
+     * ScanDetail for newly created {@link WifiConfiguration} for Passpoint network.
      *
-     * @param userId The identifier of the new foreground user, after the switch.
-     *
-     * TODO(b/26785736): Terminate background users if the new foreground user has one or more
-     * private network configurations.
+     * @param networkId The ID of the network to save ScanDetail to
+     * @param scanDetail The ScanDetail to cache
      */
-    public void handleUserSwitch(int userId) {
-        mCurrentUserId = userId;
-        Set<WifiConfiguration> ephemeralConfigs = new HashSet<>();
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            if (config.ephemeral) {
-                ephemeralConfigs.add(config);
-            }
-        }
-        if (!ephemeralConfigs.isEmpty()) {
-            for (WifiConfiguration config : ephemeralConfigs) {
-                removeConfigWithoutBroadcast(config);
-            }
-            saveConfig();
-            writeKnownNetworkHistory();
-        }
-
-        final List<WifiConfiguration> hiddenConfigurations =
-                mConfiguredNetworks.handleUserSwitch(mCurrentUserId);
-        for (WifiConfiguration network : hiddenConfigurations) {
-            disableNetworkNative(network);
-        }
-        enableAllNetworks();
-
-        // TODO(b/26785746): This broadcast is unnecessary if either of the following is true:
-        // * The user switch did not change the list of visible networks
-        // * The user switch revealed additional networks that were temporarily disabled and got
-        //   re-enabled now (because enableAllNetworks() sent the same broadcast already).
-        sendConfiguredNetworksChangedBroadcast();
-    }
-
-    public int getCurrentUserId() {
-        return mCurrentUserId;
-    }
-
-    public boolean isCurrentUserProfile(int userId) {
-        if (userId == mCurrentUserId) {
-            return true;
-        }
-        final UserInfo parent = mUserManager.getProfileParent(userId);
-        return parent != null && parent.id == mCurrentUserId;
-    }
-
-    /* Compare current and new configuration and write to file on change */
-    private NetworkUpdateResult writeIpAndProxyConfigurationsOnChange(
-            WifiConfiguration currentConfig,
-            WifiConfiguration newConfig,
-            boolean isNewNetwork) {
-        boolean ipChanged = false;
-        boolean proxyChanged = false;
-
-        switch (newConfig.getIpAssignment()) {
-            case STATIC:
-                if (currentConfig.getIpAssignment() != newConfig.getIpAssignment()) {
-                    ipChanged = true;
-                } else {
-                    ipChanged = !Objects.equals(
-                            currentConfig.getStaticIpConfiguration(),
-                            newConfig.getStaticIpConfiguration());
-                }
-                break;
-            case DHCP:
-                if (currentConfig.getIpAssignment() != newConfig.getIpAssignment()) {
-                    ipChanged = true;
-                }
-                break;
-            case UNASSIGNED:
-                /* Ignore */
-                break;
-            default:
-                loge("Ignore invalid ip assignment during write");
-                break;
-        }
-
-        switch (newConfig.getProxySettings()) {
-            case STATIC:
-            case PAC:
-                ProxyInfo newHttpProxy = newConfig.getHttpProxy();
-                ProxyInfo currentHttpProxy = currentConfig.getHttpProxy();
-
-                if (newHttpProxy != null) {
-                    proxyChanged = !newHttpProxy.equals(currentHttpProxy);
-                } else {
-                    proxyChanged = (currentHttpProxy != null);
-                }
-                break;
-            case NONE:
-                if (currentConfig.getProxySettings() != newConfig.getProxySettings()) {
-                    proxyChanged = true;
-                }
-                break;
-            case UNASSIGNED:
-                /* Ignore */
-                break;
-            default:
-                loge("Ignore invalid proxy configuration during write");
-                break;
-        }
-
-        if (ipChanged) {
-            currentConfig.setIpAssignment(newConfig.getIpAssignment());
-            currentConfig.setStaticIpConfiguration(newConfig.getStaticIpConfiguration());
-            log("IP config changed SSID = " + currentConfig.SSID);
-            if (currentConfig.getStaticIpConfiguration() != null) {
-                log(" static configuration: "
-                        + currentConfig.getStaticIpConfiguration().toString());
-            }
-        }
-
-        if (proxyChanged) {
-            currentConfig.setProxySettings(newConfig.getProxySettings());
-            currentConfig.setHttpProxy(newConfig.getHttpProxy());
-            log("proxy changed SSID = " + currentConfig.SSID);
-            if (currentConfig.getHttpProxy() != null) {
-                log(" proxyProperties: " + currentConfig.getHttpProxy().toString());
-            }
-        }
-
-        if (ipChanged || proxyChanged || isNewNetwork) {
-            if (sVDBG) {
-                logd("writeIpAndProxyConfigurationsOnChange: " + currentConfig.SSID + " -> "
-                        + newConfig.SSID + " path: " + IP_CONFIG_FILE);
-            }
-            writeIpAndProxyConfigurations();
-        }
-        return new NetworkUpdateResult(ipChanged, proxyChanged);
-    }
-
-    /**
-     * Read the variables from the supplicant daemon that are needed to
-     * fill in the WifiConfiguration object.
-     *
-     * @param config the {@link WifiConfiguration} object to be filled in.
-     */
-    private void readNetworkVariables(WifiConfiguration config) {
-        mWifiConfigStore.readNetworkVariables(config);
-    }
-
-    /* return the allowed key management based on a scan result */
-
-    public WifiConfiguration wifiConfigurationFromScanResult(ScanResult result) {
-
-        WifiConfiguration config = new WifiConfiguration();
-
-        config.SSID = "\"" + result.SSID + "\"";
-
-        if (sVDBG) {
-            logd("WifiConfiguration from scan results "
-                    + config.SSID + " cap " + result.capabilities);
-        }
-
-        if (result.capabilities.contains("PSK") || result.capabilities.contains("EAP")
-                || result.capabilities.contains("WEP")) {
-            if (result.capabilities.contains("PSK")) {
-                config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
-            }
-
-            if (result.capabilities.contains("EAP")) {
-                config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
-                config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
-            }
-
-            if (result.capabilities.contains("WEP")) {
-                config.allowedKeyManagement.set(KeyMgmt.NONE);
-                config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
-                config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
-            }
-        } else {
-            config.allowedKeyManagement.set(KeyMgmt.NONE);
-        }
-
-        return config;
-    }
-
-    public WifiConfiguration wifiConfigurationFromScanResult(ScanDetail scanDetail) {
-        ScanResult result = scanDetail.getScanResult();
-        return wifiConfigurationFromScanResult(result);
-    }
-
-    /* Returns a unique for a given configuration */
-    private static int configKey(WifiConfiguration config) {
-        String key = config.configKey();
-        return key.hashCode();
-    }
-
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("Dump of WifiConfigManager");
-        pw.println("mLastPriority " + mLastPriority);
-        pw.println("Configured networks");
-        for (WifiConfiguration conf : getAllConfiguredNetworks()) {
-            pw.println(conf);
-        }
-        pw.println();
-        if (mLostConfigsDbg != null && mLostConfigsDbg.size() > 0) {
-            pw.println("LostConfigs: ");
-            for (String s : mLostConfigsDbg) {
-                pw.println(s);
-            }
-        }
-
-        if (mMOManager.isConfigured()) {
-            pw.println("Begin dump of ANQP Cache");
-            mAnqpCache.dump(pw);
-            pw.println("End dump of ANQP Cache");
-        }
-    }
-
-    public String getConfigFile() {
-        return IP_CONFIG_FILE;
-    }
-
-    protected void logd(String s) {
-        Log.d(TAG, s);
-    }
-
-    protected void loge(String s) {
-        loge(s, false);
-    }
-
-    protected void loge(String s, boolean stack) {
-        if (stack) {
-            Log.e(TAG, s + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
-        } else {
-            Log.e(TAG, s);
-        }
-    }
-
-    private void logKernelTime() {
-        long kernelTimeMs = System.nanoTime() / (1000 * 1000);
-        StringBuilder builder = new StringBuilder();
-        builder.append("kernel time = ")
-                .append(kernelTimeMs / 1000)
-                .append(".")
-                .append(kernelTimeMs % 1000)
-                .append("\n");
-        localLog(builder.toString());
-    }
-
-    protected void log(String s) {
-        Log.d(TAG, s);
-    }
-
-    private void localLog(String s) {
-        if (mLocalLog != null) {
-            mLocalLog.log(s);
-        }
-    }
-
-    private void localLogAndLogcat(String s) {
-        localLog(s);
-        Log.d(TAG, s);
-    }
-
-    private void localLogNetwork(String s, int netId) {
-        if (mLocalLog == null) {
+    public void updateScanDetailForNetwork(int networkId, ScanDetail scanDetail) {
+        WifiConfiguration network = getInternalConfiguredNetwork(networkId);
+        if (network == null) {
             return;
         }
-
-        WifiConfiguration config;
-        synchronized (mConfiguredNetworks) {             // !!! Useless synchronization
-            config = mConfiguredNetworks.getForAllUsers(netId);
-        }
-
-        if (config != null) {
-            mLocalLog.log(s + " " + config.getPrintableSsid() + " " + netId
-                    + " status=" + config.status
-                    + " key=" + config.configKey());
-        } else {
-            mLocalLog.log(s + " " + netId);
-        }
+        saveToScanDetailCacheForNetwork(network, scanDetail);
     }
 
-    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");
+    /**
+     * Helper method to check if the 2 provided networks can be linked or not.
+     * Networks are considered for linking if:
+     * 1. Share the same GW MAC address.
+     * 2. Scan results for the networks have AP's with MAC address which differ only in the last
+     * nibble.
+     *
+     * @param network1         WifiConfiguration corresponding to network 1.
+     * @param network2         WifiConfiguration corresponding to network 2.
+     * @param scanDetailCache1 ScanDetailCache entry for network 1.
+     * @param scanDetailCache1 ScanDetailCache entry for network 2.
+     * @return true if the networks should be linked, false if the networks should be unlinked.
+     */
+    private boolean shouldNetworksBeLinked(
+            WifiConfiguration network1, WifiConfiguration network2,
+            ScanDetailCache scanDetailCache1, ScanDetailCache scanDetailCache2) {
+        // TODO (b/30706406): Link networks only with same passwords if the
+        // |mOnlyLinkSameCredentialConfigurations| flag is set.
+        if (mOnlyLinkSameCredentialConfigurations) {
+            if (!TextUtils.equals(network1.preSharedKey, network2.preSharedKey)) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "shouldNetworksBeLinked unlink due to password mismatch");
                 }
-            } else {
-                Slog.e(TAG, "Could not load client certificate " + Credentials
-                        .USER_CERTIFICATE + client);
-                mNeedsSoftwareKeystore = true;
+                return false;
             }
-
-        } catch(CertificateException e) {
-            Slog.e(TAG, "Could not read certificates");
-            mCaCert = null;
-            mClientCertificate = null;
         }
-        */
-
+        if (network1.defaultGwMacAddress != null && network2.defaultGwMacAddress != null) {
+            // If both default GW are known, link only if they are equal
+            if (network1.defaultGwMacAddress.equals(network2.defaultGwMacAddress)) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "shouldNetworksBeLinked link due to same gw " + network2.SSID
+                            + " and " + network1.SSID + " GW " + network1.defaultGwMacAddress);
+                }
+                return true;
+            }
+        } else {
+            // We do not know BOTH default gateways hence we will try to link
+            // hoping that WifiConfigurations are indeed behind the same gateway.
+            // once both WifiConfiguration have been tried and thus once both default gateways
+            // are known we will revisit the choice of linking them.
+            if (scanDetailCache1 != null && scanDetailCache2 != null) {
+                for (String abssid : scanDetailCache1.keySet()) {
+                    for (String bbssid : scanDetailCache2.keySet()) {
+                        if (abssid.regionMatches(
+                                true, 0, bbssid, 0, LINK_CONFIGURATION_BSSID_MATCH_LENGTH)) {
+                            // If first 16 ASCII characters of BSSID matches,
+                            // we assume this is a DBDC.
+                            if (mVerboseLoggingEnabled) {
+                                Log.v(TAG, "shouldNetworksBeLinked link due to DBDC BSSID match "
+                                        + network2.SSID + " and " + network1.SSID
+                                        + " bssida " + abssid + " bssidb " + bbssid);
+                            }
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
         return false;
     }
 
     /**
-     * Resets all sim networks from the network list.
-     */
-    public void resetSimNetworks() {
-        mWifiConfigStore.resetSimNetworks(mConfiguredNetworks.valuesForCurrentUser());
-    }
-
-    boolean isNetworkConfigured(WifiConfiguration config) {
-        // Check if either we have a network Id or a WifiConfiguration
-        // matching the one we are trying to add.
-
-        if (config.networkId != INVALID_NETWORK_ID) {
-            return (mConfiguredNetworks.getForCurrentUser(config.networkId) != null);
-        }
-
-        return (mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey()) != null);
-    }
-
-    /**
-     * Checks if uid has access to modify the configuration corresponding to networkId.
+     * Helper methods to link 2 networks together.
      *
-     * The conditions checked are, in descending priority order:
-     * - Disallow modification if the the configuration is not visible to the uid.
-     * - Allow modification if the uid represents the Device Owner app.
-     * - Allow modification if both of the following are true:
-     *   - The uid represents the configuration's creator or an app holding OVERRIDE_CONFIG_WIFI.
-     *   - The modification is only for administrative annotation (e.g. when connecting) or the
-     *     configuration is not lockdown eligible (which currently means that it was not last
-     *     updated by the DO).
-     * - Allow modification if configuration lockdown is explicitly disabled and the uid represents
-     *   an app holding OVERRIDE_CONFIG_WIFI.
-     * - In all other cases, disallow modification.
+     * @param network1 WifiConfiguration corresponding to network 1.
+     * @param network2 WifiConfiguration corresponding to network 2.
      */
-    boolean canModifyNetwork(int uid, int networkId, boolean onlyAnnotate) {
-        WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(networkId);
-
-        if (config == null) {
-            loge("canModifyNetwork: cannot find config networkId " + networkId);
-            return false;
+    private void linkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "linkNetworks will link " + network2.configKey()
+                    + " and " + network1.configKey());
         }
-
-        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
-                DevicePolicyManagerInternal.class);
-
-        final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
-                DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-
-        if (isUidDeviceOwner) {
-            return true;
+        if (network2.linkedConfigurations == null) {
+            network2.linkedConfigurations = new HashMap<>();
         }
-
-        final boolean isCreator = (config.creatorUid == uid);
-
-        if (onlyAnnotate) {
-            return isCreator || checkConfigOverridePermission(uid);
+        if (network1.linkedConfigurations == null) {
+            network1.linkedConfigurations = new HashMap<>();
         }
-
-        // Check if device has DPM capability. If it has and dpmi is still null, then we
-        // treat this case with suspicion and bail out.
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-                && dpmi == null) {
-            return false;
-        }
-
-        // WiFi config lockdown related logic. At this point we know uid NOT to be a Device Owner.
-
-        final boolean isConfigEligibleForLockdown = dpmi != null && dpmi.isActiveAdminWithPolicy(
-                config.creatorUid, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
-        if (!isConfigEligibleForLockdown) {
-            return isCreator || checkConfigOverridePermission(uid);
-        }
-
-        final ContentResolver resolver = mContext.getContentResolver();
-        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
-                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
-        return !isLockdownFeatureEnabled && checkConfigOverridePermission(uid);
+        // TODO (b/30638473): This needs to become a set instead of map, but it will need
+        // public interface changes and need some migration of existing store data.
+        network2.linkedConfigurations.put(network1.configKey(), 1);
+        network1.linkedConfigurations.put(network2.configKey(), 1);
     }
 
     /**
-     * Saves the network and set the candidate.
-     * @param config WifiConfiguration to save.
-     * @param scanResult ScanResult to be used as the network selection candidate.
-     * @return WifiConfiguration that was saved and with the status updated.
+     * Helper methods to unlink 2 networks from each other.
+     *
+     * @param network1 WifiConfiguration corresponding to network 1.
+     * @param network2 WifiConfiguration corresponding to network 2.
      */
-    public WifiConfiguration saveNetworkAndSetCandidate(WifiConfiguration config,
-                                                        ScanResult scanResult) {
-        saveNetwork(config, WifiConfiguration.UNKNOWN_UID);
-
-        config.getNetworkSelectionStatus().setCandidate(scanResult);
-        return config;
+    private void unlinkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
+        if (network2.linkedConfigurations != null
+                && (network2.linkedConfigurations.get(network1.configKey()) != null)) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "unlinkNetworks un-link " + network1.configKey()
+                        + " from " + network2.configKey());
+            }
+            network2.linkedConfigurations.remove(network1.configKey());
+        }
+        if (network1.linkedConfigurations != null
+                && (network1.linkedConfigurations.get(network2.configKey()) != null)) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "unlinkNetworks un-link " + network2.configKey()
+                        + " from " + network1.configKey());
+            }
+            network1.linkedConfigurations.remove(network2.configKey());
+        }
     }
 
+    /**
+     * This method runs through all the saved networks and checks if the provided network can be
+     * linked with any of them.
+     *
+     * @param config WifiConfiguration object corresponding to the network that needs to be
+     *               checked for potential links.
+     */
+    private void attemptNetworkLinking(WifiConfiguration config) {
+        // Only link WPA_PSK config.
+        if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            return;
+        }
+        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
+        // Ignore configurations with large number of BSSIDs.
+        if (scanDetailCache != null
+                && scanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
+            return;
+        }
+        for (WifiConfiguration linkConfig : getInternalConfiguredNetworks()) {
+            if (linkConfig.configKey().equals(config.configKey())) {
+                continue;
+            }
+            if (linkConfig.ephemeral) {
+                continue;
+            }
+            // Network Selector will be allowed to dynamically jump from a linked configuration
+            // to another, hence only link configurations that have WPA_PSK security type.
+            if (!linkConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                continue;
+            }
+            ScanDetailCache linkScanDetailCache =
+                    getScanDetailCacheForNetwork(linkConfig.networkId);
+            // Ignore configurations with large number of BSSIDs.
+            if (linkScanDetailCache != null
+                    && linkScanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
+                continue;
+            }
+            // Check if the networks should be linked/unlinked.
+            if (shouldNetworksBeLinked(
+                    config, linkConfig, scanDetailCache, linkScanDetailCache)) {
+                linkNetworks(config, linkConfig);
+            } else {
+                unlinkNetworks(config, linkConfig);
+            }
+        }
+    }
 
     /**
-     * Get the Scan Result candidate.
-     * @param config WifiConfiguration to get status for.
-     * @return scanResult which is the selection candidate.
+     * Helper method to fetch list of channels for a network from the associated ScanResult's cache
+     * and add it to the provided channel as long as the size of the set is less than
+     * |maxChannelSetSize|.
+     *
+     * @param channelSet        Channel set holding all the channels for the network.
+     * @param scanDetailCache   ScanDetailCache entry associated with the network.
+     * @param nowInMillis       current timestamp to be used for age comparison.
+     * @param ageInMillis       only consider scan details whose timestamps are earlier than this
+     *                          value.
+     * @param maxChannelSetSize Maximum number of channels to be added to the set.
+     * @return false if the list is full, true otherwise.
      */
-    public ScanResult getScanResultCandidate(WifiConfiguration config) {
+    private boolean addToChannelSetForNetworkFromScanDetailCache(
+            Set<Integer> channelSet, ScanDetailCache scanDetailCache,
+            long nowInMillis, long ageInMillis, int maxChannelSetSize) {
+        if (scanDetailCache != null && scanDetailCache.size() > 0) {
+            for (ScanDetail scanDetail : scanDetailCache.values()) {
+                ScanResult result = scanDetail.getScanResult();
+                boolean valid = (nowInMillis - result.seen) < ageInMillis;
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "fetchChannelSetForNetwork has " + result.BSSID + " freq "
+                            + result.frequency + " age " + (nowInMillis - result.seen)
+                            + " ?=" + valid);
+                }
+                if (valid) {
+                    channelSet.add(result.frequency);
+                }
+                if (channelSet.size() >= maxChannelSetSize) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Retrieve a set of channels on which AP's for the provided network was seen using the
+     * internal ScanResult's cache {@link #mScanDetailCaches}. This is used for initiating partial
+     * scans for the currently connected network.
+     *
+     * @param networkId       network ID corresponding to the network.
+     * @param ageInMillis     only consider scan details whose timestamps are earlier than this value.
+     * @param homeChannelFreq frequency of the currently connected network.
+     * @return Set containing the frequencies on which this network was found, null if the network
+     * was not found or there are no associated scan details in the cache.
+     */
+    public Set<Integer> fetchChannelSetForNetworkForPartialScan(int networkId, long ageInMillis,
+                                                                int homeChannelFreq) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
             return null;
         }
-        return  config.getNetworkSelectionStatus().getCandidate();
-    }
-
-    /**
-     * Checks if uid has access to modify config.
-     */
-    boolean canModifyNetwork(int uid, WifiConfiguration config, boolean onlyAnnotate) {
-        if (config == null) {
-            loge("canModifyNetowrk recieved null configuration");
-            return false;
+        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(networkId);
+        if (scanDetailCache == null && config.linkedConfigurations == null) {
+            Log.i(TAG, "No scan detail and linked configs associated with networkId " + networkId);
+            return null;
         }
+        if (mVerboseLoggingEnabled) {
+            StringBuilder dbg = new StringBuilder();
+            dbg.append("fetchChannelSetForNetworkForPartialScan ageInMillis ")
+                    .append(ageInMillis)
+                    .append(" for ")
+                    .append(config.configKey())
+                    .append(" max ")
+                    .append(mMaxNumActiveChannelsForPartialScans);
+            if (scanDetailCache != null) {
+                dbg.append(" bssids " + scanDetailCache.size());
+            }
+            if (config.linkedConfigurations != null) {
+                dbg.append(" linked " + config.linkedConfigurations.size());
+            }
+            Log.v(TAG, dbg.toString());
+        }
+        Set<Integer> channelSet = new HashSet<>();
 
-        // Resolve the correct network id.
-        int netid;
-        if (config.networkId != INVALID_NETWORK_ID) {
-            netid = config.networkId;
-        } else {
-            WifiConfiguration test =
-                    mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
-            if (test == null) {
-                return false;
-            } else {
-                netid = test.networkId;
+        // First add the currently connected network channel.
+        if (homeChannelFreq > 0) {
+            channelSet.add(homeChannelFreq);
+            if (channelSet.size() >= mMaxNumActiveChannelsForPartialScans) {
+                return channelSet;
             }
         }
 
-        return canModifyNetwork(uid, netid, onlyAnnotate);
-    }
+        long nowInMillis = mClock.getWallClockMillis();
 
-    boolean checkConfigOverridePermission(int uid) {
-        try {
-            return (mFacade.checkUidPermission(
-                    android.Manifest.permission.OVERRIDE_WIFI_CONFIG, uid)
-                    == PackageManager.PERMISSION_GRANTED);
-        } catch (RemoteException e) {
-            return false;
+        // Then get channels for the network.
+        if (!addToChannelSetForNetworkFromScanDetailCache(
+                channelSet, scanDetailCache, nowInMillis, ageInMillis,
+                mMaxNumActiveChannelsForPartialScans)) {
+            return channelSet;
         }
-    }
 
-    int getMaxDhcpRetries() {
-        return mFacade.getIntegerSetting(mContext,
-                Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
-                DEFAULT_MAX_DHCP_RETRIES);
-    }
-
-    void clearBssidBlacklist() {
-        mWifiConfigStore.clearBssidBlacklist();
-    }
-
-    void blackListBssid(String bssid) {
-        mWifiConfigStore.blackListBssid(bssid);
-    }
-
-    public boolean isBssidBlacklisted(String bssid) {
-        return mWifiConfigStore.isBssidBlacklisted(bssid);
-    }
-
-    public boolean getEnableAutoJoinWhenAssociated() {
-        return mEnableAutoJoinWhenAssociated.get();
-    }
-
-    public void setEnableAutoJoinWhenAssociated(boolean enabled) {
-        mEnableAutoJoinWhenAssociated.set(enabled);
-    }
-
-    public void setActiveScanDetail(ScanDetail activeScanDetail) {
-        synchronized (mActiveScanDetailLock) {
-            mActiveScanDetail = activeScanDetail;
+        // Lastly get channels for linked networks.
+        if (config.linkedConfigurations != null) {
+            for (String configKey : config.linkedConfigurations.keySet()) {
+                WifiConfiguration linkedConfig = getInternalConfiguredNetwork(configKey);
+                if (linkedConfig == null) {
+                    continue;
+                }
+                ScanDetailCache linkedScanDetailCache =
+                        getScanDetailCacheForNetwork(linkedConfig.networkId);
+                if (!addToChannelSetForNetworkFromScanDetailCache(
+                        channelSet, linkedScanDetailCache, nowInMillis, ageInMillis,
+                        mMaxNumActiveChannelsForPartialScans)) {
+                    break;
+                }
+            }
         }
+        return channelSet;
+    }
+
+    /**
+     * Retrieves a list of all the saved networks before enabling disconnected/connected PNO.
+     *
+     * PNO network list sent to the firmware has limited size. If there are a lot of saved
+     * networks, this list will be truncated and we might end up not sending the networks
+     * with the highest chance of connecting to the firmware.
+     * So, re-sort the network list based on the frequency of connection to those networks
+     * and whether it was last seen in the scan results.
+     *
+     * TODO (b/30399964): Recalculate the list whenever network status changes.
+     * @return list of networks with updated priorities.
+     */
+    public List<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList() {
+        List<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
+        List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
+        // Remove any permanently disabled networks.
+        Iterator<WifiConfiguration> iter = networks.iterator();
+        while (iter.hasNext()) {
+            WifiConfiguration config = iter.next();
+            if (config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
+                iter.remove();
+            }
+        }
+        Collections.sort(networks, sScanListComparator);
+        // Let's use the network list size - 1 as the highest priority and then go down from there.
+        // So, the most frequently connected network has the highest priority now.
+        int priority = networks.size() - 1;
+        for (WifiConfiguration config : networks) {
+            pnoList.add(WifiConfigurationUtil.createPnoNetwork(config, priority));
+            priority--;
+        }
+        return pnoList;
+    }
+
+    /**
+     * Retrieves a list of all the saved hidden networks for scans.
+     *
+     * Hidden network list sent to the firmware has limited size. If there are a lot of saved
+     * networks, this list will be truncated and we might end up not sending the networks
+     * with the highest chance of connecting to the firmware.
+     * So, re-sort the network list based on the frequency of connection to those networks
+     * and whether it was last seen in the scan results.
+     *
+     * @return list of networks with updated priorities.
+     */
+    public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList() {
+        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenList = new ArrayList<>();
+        List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
+        // Remove any permanently disabled networks or non hidden networks.
+        Iterator<WifiConfiguration> iter = networks.iterator();
+        while (iter.hasNext()) {
+            WifiConfiguration config = iter.next();
+            if (!config.hiddenSSID ||
+                    config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
+                iter.remove();
+            }
+        }
+        Collections.sort(networks, sScanListComparator);
+        // Let's use the network list size - 1 as the highest priority and then go down from there.
+        // So, the most frequently connected network has the highest priority now.
+        int priority = networks.size() - 1;
+        for (WifiConfiguration config : networks) {
+            hiddenList.add(
+                    new WifiScanner.ScanSettings.HiddenNetwork(config.SSID));
+            priority--;
+        }
+        return hiddenList;
     }
 
     /**
      * Check if the provided ephemeral network was deleted by the user or not.
+     *
      * @param ssid caller must ensure that the SSID passed thru this API match
-     *        the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
+     *             the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
      * @return true if network was deleted, false otherwise.
      */
     public boolean wasEphemeralNetworkDeleted(String ssid) {
@@ -3319,11 +2332,508 @@
     }
 
     /**
-     * Check if the User has enabled connecting to carrier networks from Settings.
-     * @return true if enabled in Settings, false otherwise.
+     * Disable an ephemeral SSID for the purpose of network selection.
+     *
+     * The only way to "un-disable it" is if the user create a network for that SSID and then
+     * forget it.
+     *
+     * @param ssid caller must ensure that the SSID passed thru this API match
+     *             the WifiConfiguration.SSID rules, and thus be surrounded by quotes.
+     * @return the {@link WifiConfiguration} corresponding to this SSID, if any, so that we can
+     * disconnect if this is the current network.
      */
-    public boolean getIsCarrierNetworkEnabledByUser() {
-        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.WIFI_CONNECT_CARRIER_NETWORKS, 0) == 1;
+    public WifiConfiguration disableEphemeralNetwork(String ssid) {
+        if (ssid == null) {
+            return null;
+        }
+        WifiConfiguration foundConfig = null;
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (config.ephemeral && TextUtils.equals(config.SSID, ssid)) {
+                foundConfig = config;
+                break;
+            }
+        }
+        mDeletedEphemeralSSIDs.add(ssid);
+        Log.d(TAG, "Forget ephemeral SSID " + ssid + " num=" + mDeletedEphemeralSSIDs.size());
+        if (foundConfig != null) {
+            Log.d(TAG, "Found ephemeral config in disableEphemeralNetwork: "
+                    + foundConfig.networkId);
+        }
+        return foundConfig;
+    }
+
+    /**
+     * Resets all sim networks state.
+     */
+    public void resetSimNetworks() {
+        if (mVerboseLoggingEnabled) localLog("resetSimNetworks");
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (TelephonyUtil.isSimConfig(config)) {
+                String currentIdentity = TelephonyUtil.getSimIdentity(mTelephonyManager, config);
+                // Update the loaded config
+                config.enterpriseConfig.setIdentity(currentIdentity);
+                if (config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.PEAP) {
+                    config.enterpriseConfig.setAnonymousIdentity("");
+                }
+            }
+        }
+    }
+
+    /**
+     * Any network using certificates to authenticate access requires unlocked key store; unless
+     * the certificates can be stored with hardware encryption
+     *
+     * @return true if we need an unlocked keystore, false otherwise.
+     */
+    public boolean needsUnlockedKeyStore() {
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (WifiConfigurationUtil.isConfigForEapNetwork(config)) {
+                if (mWifiKeyStore.needsSoftwareBackedKeyStore(config.enterpriseConfig)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Helper method to perform the following operations during user switch/unlock:
+     * - Remove private networks of the old user.
+     * - Load from the new user store file.
+     * - Save the store files again to migrate any user specific networks from the shared store
+     *   to user store.
+     * This method assumes the user store is visible (i.e CE storage is unlocked). So, the caller
+     * should ensure that the stores are accessible before invocation.
+     *
+     * @param userId The identifier of the new foreground user, after the unlock or switch.
+     */
+    private void handleUserUnlockOrSwitch(int userId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Loading from store after user switch/unlock for " + userId);
+        }
+        // Switch out the user store file.
+        if (loadFromUserStoreAfterUnlockOrSwitch(userId)) {
+            saveToStore(true);
+            mPendingUnlockStoreRead = false;
+        }
+    }
+
+    /**
+     * Handles the switch to a different foreground user:
+     * - Flush the current state to the old user's store file.
+     * - Switch the user specific store file.
+     * - Reload the networks from the store files (shared & user).
+     * - Write the store files to move any user specific private networks from shared store to user
+     *   store.
+     *
+     * Need to be called when {@link com.android.server.SystemService#onSwitchUser(int)} is invoked.
+     *
+     * @param userId The identifier of the new foreground user, after the switch.
+     * @return List of network ID's of all the private networks of the old user which will be
+     * removed from memory.
+     */
+    public Set<Integer> handleUserSwitch(int userId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Handling user switch for " + userId);
+        }
+        if (userId == mCurrentUserId) {
+            Log.w(TAG, "User already in foreground " + userId);
+            return new HashSet<>();
+        }
+        if (mPendingStoreRead) {
+            Log.wtf(TAG, "Unexpected user switch before store is read!");
+            return new HashSet<>();
+        }
+        if (mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
+            saveToStore(true);
+        }
+        // Remove any private networks of the old user before switching the userId.
+        Set<Integer> removedNetworkIds = clearInternalUserData(mCurrentUserId);
+        mConfiguredNetworks.setNewUser(userId);
+        mCurrentUserId = userId;
+
+        if (mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
+            handleUserUnlockOrSwitch(mCurrentUserId);
+        } else {
+            // Cannot read data from new user's CE store file before they log-in.
+            mPendingUnlockStoreRead = true;
+            Log.i(TAG, "Waiting for user unlock to load from store");
+        }
+        return removedNetworkIds;
+    }
+
+    /**
+     * Handles the unlock of foreground user. This maybe needed to read the store file if the user's
+     * CE storage is not visible when {@link #handleUserSwitch(int)} is invoked.
+     *
+     * Need to be called when {@link com.android.server.SystemService#onUnlockUser(int)} is invoked.
+     *
+     * @param userId The identifier of the user that unlocked.
+     */
+    public void handleUserUnlock(int userId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Handling user unlock for " + userId);
+        }
+        if (mPendingStoreRead) {
+            Log.w(TAG, "Ignore user unlock until store is read!");
+            mDeferredUserUnlockRead = true;
+            return;
+        }
+        if (userId == mCurrentUserId && mPendingUnlockStoreRead) {
+            handleUserUnlockOrSwitch(mCurrentUserId);
+        }
+    }
+
+    /**
+     * Handles the stop of foreground user. This is needed to write the store file to flush
+     * out any pending data before the user's CE store storage is unavailable.
+     *
+     * Need to be called when {@link com.android.server.SystemService#onStopUser(int)} is invoked.
+     *
+     * @param userId The identifier of the user that stopped.
+     */
+    public void handleUserStop(int userId) {
+        if (userId == mCurrentUserId && mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
+            saveToStore(true);
+            clearInternalData();
+            mCurrentUserId = UserHandle.USER_SYSTEM;
+        }
+    }
+
+    /**
+     * Helper method to clear internal databases.
+     * This method clears the:
+     *  - List of configured networks.
+     *  - Map of scan detail caches.
+     *  - List of deleted ephemeral networks.
+     */
+    private void clearInternalData() {
+        mConfiguredNetworks.clear();
+        mDeletedEphemeralSSIDs.clear();
+        mScanDetailCaches.clear();
+        clearLastSelectedNetwork();
+    }
+
+    /**
+     * Helper method to clear internal databases of the specified user.
+     * This method clears the:
+     *  - Private configured configured networks of the specified user.
+     *  - Map of scan detail caches.
+     *  - List of deleted ephemeral networks.
+     *
+     * @param userId The identifier of the current foreground user, before the switch.
+     * @return List of network ID's of all the private networks of the old user which will be
+     * removed from memory.
+     */
+    private Set<Integer> clearInternalUserData(int userId) {
+        Set<Integer> removedNetworkIds = new HashSet<>();
+        // Remove any private networks of the old user before switching the userId.
+        for (WifiConfiguration config : getInternalConfiguredNetworks()) {
+            if (!config.shared && WifiConfigurationUtil.doesUidBelongToAnyProfile(
+                    config.creatorUid, mUserManager.getProfiles(userId))) {
+                removedNetworkIds.add(config.networkId);
+                mConfiguredNetworks.remove(config.networkId);
+            }
+        }
+        mDeletedEphemeralSSIDs.clear();
+        mScanDetailCaches.clear();
+        clearLastSelectedNetwork();
+        return removedNetworkIds;
+    }
+
+    /**
+     * Helper function to populate the internal (in-memory) data from the retrieved shared store
+     * (file) data.
+     *
+     * @param configurations list of configurations retrieved from store.
+     */
+    private void loadInternalDataFromSharedStore(
+            List<WifiConfiguration> configurations) {
+        for (WifiConfiguration configuration : configurations) {
+            configuration.networkId = mNextNetworkId++;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Adding network from shared store " + configuration.configKey());
+            }
+            mConfiguredNetworks.put(configuration);
+        }
+    }
+
+    /**
+     * Helper function to populate the internal (in-memory) data from the retrieved user store
+     * (file) data.
+     *
+     * @param configurations        list of configurations retrieved from store.
+     * @param deletedEphemeralSSIDs list of ssid's representing the ephemeral networks deleted by
+     *                              the user.
+     */
+    private void loadInternalDataFromUserStore(
+            List<WifiConfiguration> configurations, Set<String> deletedEphemeralSSIDs) {
+        for (WifiConfiguration configuration : configurations) {
+            configuration.networkId = mNextNetworkId++;
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Adding network from user store " + configuration.configKey());
+            }
+            mConfiguredNetworks.put(configuration);
+        }
+        for (String ssid : deletedEphemeralSSIDs) {
+            mDeletedEphemeralSSIDs.add(ssid);
+        }
+    }
+
+    /**
+     * Helper function to populate the internal (in-memory) data from the retrieved stores (file)
+     * data.
+     * This method:
+     * 1. Clears all existing internal data.
+     * 2. Sends out the networks changed broadcast after loading all the data.
+     *
+     * @param sharedConfigurations  list of  network configurations retrieved from shared store.
+     * @param userConfigurations    list of  network configurations retrieved from user store.
+     * @param deletedEphemeralSSIDs list of ssid's representing the ephemeral networks deleted by
+     *                              the user.
+     */
+    private void loadInternalData(
+            List<WifiConfiguration> sharedConfigurations,
+            List<WifiConfiguration> userConfigurations, Set<String> deletedEphemeralSSIDs) {
+        // Clear out all the existing in-memory lists and load the lists from what was retrieved
+        // from the config store.
+        clearInternalData();
+        loadInternalDataFromSharedStore(sharedConfigurations);
+        loadInternalDataFromUserStore(userConfigurations, deletedEphemeralSSIDs);
+        if (mConfiguredNetworks.sizeForAllUsers() == 0) {
+            Log.w(TAG, "No stored networks found.");
+        }
+        sendConfiguredNetworksChangedBroadcast();
+        mPendingStoreRead = false;
+    }
+
+    /**
+     * Migrate data from legacy store files. The function performs the following operations:
+     * 1. Check if the legacy store files are present.
+     * 2. If yes, read all the data from the store files.
+     * 3. Save it to the new store files.
+     * 4. Delete the legacy store file.
+     *
+     * @return true if migration was successful or not needed (fresh install), false if it failed.
+     */
+    public boolean migrateFromLegacyStore() {
+        if (!mWifiConfigStoreLegacy.areStoresPresent()) {
+            Log.d(TAG, "Legacy store files not found. No migration needed!");
+            return true;
+        }
+        WifiConfigStoreDataLegacy storeData = mWifiConfigStoreLegacy.read();
+        Log.d(TAG, "Reading from legacy store completed");
+        loadInternalData(storeData.getConfigurations(), new ArrayList<WifiConfiguration>(),
+                storeData.getDeletedEphemeralSSIDs());
+
+        // Setup user store for the current user in case it have not setup yet, so that data
+        // owned by the current user will be backed to the user store.
+        if (mDeferredUserUnlockRead) {
+            mWifiConfigStore.setUserStore(WifiConfigStore.createUserFile(mCurrentUserId));
+            mDeferredUserUnlockRead = false;
+        }
+
+        if (!saveToStore(true)) {
+            return false;
+        }
+        mWifiConfigStoreLegacy.removeStores();
+        Log.d(TAG, "Migration from legacy store completed");
+        return true;
+    }
+
+    /**
+     * Read the config store and load the in-memory lists from the store data retrieved and sends
+     * out the networks changed broadcast.
+     *
+     * This reads all the network configurations from:
+     * 1. Shared WifiConfigStore.xml
+     * 2. User WifiConfigStore.xml
+     *
+     * @return true on success or not needed (fresh install/pending legacy store migration),
+     * false otherwise.
+     */
+    public boolean loadFromStore() {
+        if (!mWifiConfigStore.areStoresPresent()) {
+            Log.d(TAG, "New store files not found. No saved networks loaded!");
+            if (!mWifiConfigStoreLegacy.areStoresPresent()) {
+                // No legacy store files either, so reset the pending store read flag.
+                mPendingStoreRead = false;
+            }
+            return true;
+        }
+        // If the user unlock comes in before we load from store, which means the user store have
+        // not been setup yet for the current user.  Setup the user store before the read so that
+        // configurations for the current user will also being loaded.
+        if (mDeferredUserUnlockRead) {
+            Log.i(TAG, "Handling user unlock before loading from store.");
+            mWifiConfigStore.setUserStore(WifiConfigStore.createUserFile(mCurrentUserId));
+            mDeferredUserUnlockRead = false;
+        }
+        try {
+            mWifiConfigStore.read();
+        } catch (IOException e) {
+            Log.wtf(TAG, "Reading from new store failed. All saved networks are lost!", e);
+            return false;
+        } catch (XmlPullParserException e) {
+            Log.wtf(TAG, "XML deserialization of store failed. All saved networks are lost!", e);
+            return false;
+        }
+        loadInternalData(mNetworkListStoreData.getSharedConfigurations(),
+                mNetworkListStoreData.getUserConfigurations(),
+                mDeletedEphemeralSsidsStoreData.getSsidList());
+        return true;
+    }
+
+    /**
+     * Read the user config store and load the in-memory lists from the store data retrieved and
+     * sends out the networks changed broadcast.
+     * This should be used for all user switches/unlocks to only load networks from the user
+     * specific store and avoid reloading the shared networks.
+     *
+     * This reads all the network configurations from:
+     * 1. User WifiConfigStore.xml
+     *
+     * @param userId The identifier of the foreground user.
+     * @return true on success, false otherwise.
+     */
+    public boolean loadFromUserStoreAfterUnlockOrSwitch(int userId) {
+        try {
+            mWifiConfigStore.switchUserStoreAndRead(WifiConfigStore.createUserFile(userId));
+        } catch (IOException e) {
+            Log.wtf(TAG, "Reading from new store failed. All saved private networks are lost!", e);
+            return false;
+        } catch (XmlPullParserException e) {
+            Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are" +
+                    "lost!", e);
+            return false;
+        }
+        loadInternalDataFromUserStore(mNetworkListStoreData.getUserConfigurations(),
+                mDeletedEphemeralSsidsStoreData.getSsidList());
+        return true;
+    }
+
+    /**
+     * 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.
+     */
+    public boolean saveToStore(boolean forceWrite) {
+        ArrayList<WifiConfiguration> sharedConfigurations = new ArrayList<>();
+        ArrayList<WifiConfiguration> userConfigurations = new ArrayList<>();
+        // List of network IDs for legacy Passpoint configuration to be removed.
+        List<Integer> legacyPasspointNetId = new ArrayList<>();
+        for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
+            // Ignore ephemeral networks and non-legacy Passpoint configurations.
+            if (config.ephemeral || (config.isPasspoint() && !config.isLegacyPasspointConfig)) {
+                continue;
+            }
+
+            // Migrate the legacy Passpoint configurations owned by the current user to
+            // {@link PasspointManager}.
+            if (config.isLegacyPasspointConfig && WifiConfigurationUtil.doesUidBelongToAnyProfile(
+                        config.creatorUid, mUserManager.getProfiles(mCurrentUserId))) {
+                legacyPasspointNetId.add(config.networkId);
+                // Migrate the legacy Passpoint configuration and add it to PasspointManager.
+                if (!PasspointManager.addLegacyPasspointConfig(config)) {
+                    Log.e(TAG, "Failed to migrate legacy Passpoint config: " + config.FQDN);
+                }
+                // This will prevent adding |config| to the |sharedConfigurations|.
+                continue;
+            }
+
+            // We push all shared networks & private networks not belonging to the current
+            // user to the shared store. Ideally, private networks for other users should
+            // not even be in memory,
+            // But, this logic is in place to deal with store migration from N to O
+            // because all networks were previously stored in a central file. We cannot
+            // write these private networks to the user specific store until the corresponding
+            // user logs in.
+            if (config.shared || !WifiConfigurationUtil.doesUidBelongToAnyProfile(
+                    config.creatorUid, mUserManager.getProfiles(mCurrentUserId))) {
+                sharedConfigurations.add(config);
+            } else {
+                userConfigurations.add(config);
+            }
+        }
+
+        // Remove the configurations for migrated Passpoint configurations.
+        for (int networkId : legacyPasspointNetId) {
+            mConfiguredNetworks.remove(networkId);
+        }
+
+        // Setup store data for write.
+        mNetworkListStoreData.setSharedConfigurations(sharedConfigurations);
+        mNetworkListStoreData.setUserConfigurations(userConfigurations);
+        mDeletedEphemeralSsidsStoreData.setSsidList(mDeletedEphemeralSSIDs);
+
+        try {
+            mWifiConfigStore.write(forceWrite);
+        } catch (IOException e) {
+            Log.wtf(TAG, "Writing to store failed. Saved networks maybe lost!", e);
+            return false;
+        } catch (XmlPullParserException e) {
+            Log.wtf(TAG, "XML serialization for store failed. Saved networks maybe lost!", e);
+            return false;
+        }
+        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 and other internal state of WifiConfigManager.
+     */
+    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 ----");
+        pw.println("WifiConfigManager - Configured networks Begin ----");
+        for (WifiConfiguration network : getInternalConfiguredNetworks()) {
+            pw.println(network);
+        }
+        pw.println("WifiConfigManager - Configured networks End ----");
+        pw.println("WifiConfigManager - Next network ID to be allocated " + mNextNetworkId);
+        pw.println("WifiConfigManager - Last selected network ID " + mLastSelectedNetworkId);
+    }
+
+    /**
+     * Returns true if the given uid has permission to add, update or remove proxy settings
+     */
+    private boolean canModifyProxySettings(int uid) {
+        final DevicePolicyManagerInternal dpmi =
+                mWifiPermissionsWrapper.getDevicePolicyManagerInternal();
+        final boolean isUidProfileOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
+                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+        final boolean isUidDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(uid,
+                DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        final boolean hasConfigOverridePermission =
+                mWifiPermissionsUtil.checkConfigOverridePermission(uid);
+        // If |uid| corresponds to the device owner, allow all modifications.
+        if (isUidDeviceOwner || isUidProfileOwner || hasConfigOverridePermission) {
+            return true;
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "UID: " + uid + " cannot modify WifiConfiguration proxy settings."
+                    + " ConfigOverride=" + hasConfigOverridePermission
+                    + " DeviceOwner=" + isUidDeviceOwner
+                    + " ProfileOwner=" + isUidProfileOwner);
+        }
+        return false;
+    }
+
+    /**
+     * Set the saved network update event listener
+     */
+    public void setOnSavedNetworkUpdateListener(OnSavedNetworkUpdateListener listener) {
+        mListener = listener;
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index e4aef24..659192b 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -16,1359 +16,573 @@
 
 package com.android.server.wifi;
 
+import android.app.AlarmManager;
 import android.content.Context;
-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.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
-import android.util.SparseArray;
+import android.util.Xml;
 
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.util.TelephonyUtil;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.XmlUtil;
 
-import org.json.JSONException;
-import org.json.JSONObject;
+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.File;
 import java.io.FileNotFoundException;
-import java.io.FileReader;
+import java.io.FileOutputStream;
 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!!!
+ * store. Uses keystore for certificate/key management operations.
+ * NOTE: This class should only be used from WifiConfigManager and is not thread-safe!
  */
 public class WifiConfigStore {
-
-    public static final String TAG = "WifiConfigStore";
-    // 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
-    // serialized JSON dictionary.
-    public static final String ID_STRING_VAR_NAME = "id_str";
-    public static final String ID_STRING_KEY_FQDN = "fqdn";
-    public static final String ID_STRING_KEY_CREATOR_UID = "creatorUid";
-    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 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;
-
-    WifiConfigStore(Context context, WifiNative wifiNative, KeyStore keyStore, LocalLog localLog,
-            boolean showNetworks, boolean verboseDebug) {
-        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;
-    }
-
-    private static String removeDoubleQuotes(String string) {
-        int length = string.length();
-        if ((length > 1) && (string.charAt(0) == '"')
-                && (string.charAt(length - 1) == '"')) {
-            return string.substring(1, length - 1);
-        }
-        return string;
-    }
+    private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
+    private static final String XML_TAG_VERSION = "Version";
+    /**
+     * 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;
 
     /**
-     * Generate a string to be used as a key value by wpa_supplicant from
-     * 'set', within the set of strings from 'strings' for the variable concatenated.
-     * 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 strings the set of string literals to concatenate strinfs from.
-     * @return A wpa_supplicant formatted value.
+     * Alarm tag to use for starting alarms for buffering file writes.
      */
-    private static String makeString(BitSet set, String[] strings) {
-        return makeStringWithException(set, strings, null);
-    }
-
+    @VisibleForTesting
+    public static final String BUFFERED_WRITE_ALARM_TAG = "WriteBufferAlarm";
     /**
-     * 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 exception literal string to be excluded from the _ to - transformation.
-     * @return A wpa_supplicant formatted value.
+     * Log tag.
      */
-    private static String makeStringWithException(BitSet set, String[] strings, String exception) {
-        StringBuilder result = new StringBuilder();
-
-        /* Make sure all set bits are in [0, strings.length) to avoid
-         * going out of bounds on strings.  (Shouldn't happen, but...) */
-        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)) {
-            String currentName = strings[bit];
-            if (exception != null && currentName.equals(exception)) {
-                valueSet.add(currentName);
-            } else {
-                // Most wpa_supplicant strings use a dash whereas (for some bizarre
-                // reason) the strings are defined with underscore in the code...
-                valueSet.add(currentName.replace('_', '-'));
-            }
-        }
-        return TextUtils.join(" ", valueSet);
-    }
-
-    /*
-     * Convert string to Hexadecimal before passing to wifi native layer
-     * In native function "doCommand()" have trouble in converting Unicode character string to UTF8
-     * conversion to hex is required because SSIDs can have space characters in them;
-     * and that can confuses the supplicant because it uses space charaters as delimiters
-     */
-    private static String encodeSSID(String str) {
-        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;
-
-        string = string.replace('-', '_');
-
-        for (int i = 0; i < size; i++) {
-            if (string.equals(strings[i])) {
-                return i;
-            }
-        }
-        loge("Failed to look-up a string: " + string);
-        return -1;
-    }
-
-    private void readNetworkBitsetVariable(int netId, BitSet variable, String varName,
-            String[] strings) {
-        String value = mWifiNative.getNetworkVariable(netId, varName);
-        if (!TextUtils.isEmpty(value)) {
-            variable.clear();
-            String[] vals = value.split(" ");
-            for (String val : vals) {
-                int index = lookupString(val, strings);
-                if (0 <= index) {
-                    variable.set(index);
-                }
-            }
-        }
-    }
-
+    private static final String TAG = "WifiConfigStore";
     /**
-     * Read the variables from the supplicant daemon that are needed to
-     * fill in the WifiConfiguration object.
-     *
-     * @param config the {@link WifiConfiguration} object to be filled in.
+     * Config store file name for both shared & user specific stores.
      */
-    public void readNetworkVariables(WifiConfiguration config) {
-        if (config == null) {
-            return;
-        }
-        if (VDBG) localLog("readNetworkVariables: " + config.networkId);
-        int netId = config.networkId;
-        if (netId < 0) {
-            return;
-        }
-        /*
-         * TODO: maybe should have a native method that takes an array of
-         * variable names and returns an array of values. But we'd still
-         * be doing a round trip to the supplicant daemon for each variable.
-         */
-        String value;
-
-        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
-        if (!TextUtils.isEmpty(value)) {
-            if (value.charAt(0) != '"') {
-                config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
-                //TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
-                //supplicant string
-            } else {
-                config.SSID = value;
-            }
-        } else {
-            config.SSID = null;
-        }
-
-        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.getNetworkSelectionStatus().setNetworkSelectionBSSID(value);
-        } else {
-            config.getNetworkSelectionStatus().setNetworkSelectionBSSID(null);
-        }
-
-        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
-        config.priority = -1;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.priority = Integer.parseInt(value);
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
-        config.hiddenSSID = false;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.hiddenSSID = Integer.parseInt(value) != 0;
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pmfVarName);
-        config.requirePMF = false;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.requirePMF = Integer.parseInt(value) == STORED_VALUE_FOR_REQUIRE_PMF;
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
-        config.wepTxKeyIndex = -1;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.wepTxKeyIndex = Integer.parseInt(value);
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        for (int i = 0; i < 4; i++) {
-            value = mWifiNative.getNetworkVariable(netId,
-                    WifiConfiguration.wepKeyVarNames[i]);
-            if (!TextUtils.isEmpty(value)) {
-                config.wepKeys[i] = value;
-            } else {
-                config.wepKeys[i] = null;
-            }
-        }
-
-        value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pskVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.preSharedKey = value;
-        } else {
-            config.preSharedKey = null;
-        }
-
-        readNetworkBitsetVariable(config.networkId, config.allowedProtocols,
-                WifiConfiguration.Protocol.varName, WifiConfiguration.Protocol.strings);
-
-        readNetworkBitsetVariable(config.networkId, config.allowedKeyManagement,
-                WifiConfiguration.KeyMgmt.varName, WifiConfiguration.KeyMgmt.strings);
-        // The FT flags should not be exposed to external apps.
-        config.allowedKeyManagement = removeFastTransitionFlags(config.allowedKeyManagement);
-
-        readNetworkBitsetVariable(config.networkId, config.allowedAuthAlgorithms,
-                WifiConfiguration.AuthAlgorithm.varName, WifiConfiguration.AuthAlgorithm.strings);
-
-        readNetworkBitsetVariable(config.networkId, config.allowedPairwiseCiphers,
-                WifiConfiguration.PairwiseCipher.varName, WifiConfiguration.PairwiseCipher.strings);
-
-        readNetworkBitsetVariable(config.networkId, config.allowedGroupCiphers,
-                WifiConfiguration.GroupCipher.varName, WifiConfiguration.GroupCipher.strings);
-
-        if (config.enterpriseConfig == null) {
-            config.enterpriseConfig = new WifiEnterpriseConfig();
-        }
-        config.enterpriseConfig.loadFromSupplicant(new SupplicantLoader(netId));
-    }
-
+    private static final String STORE_FILE_NAME = "WifiConfigStore.xml";
     /**
-     * Load all the configured networks from wpa_supplicant.
-     *
-     * @param configs       Map of configuration key to configuration objects corresponding to all
-     *                      the networks.
-     * @param networkExtras Map of extra configuration parameters stored in wpa_supplicant.conf
-     * @return Max priority of all the configs.
+     * Directory to store the config store files in.
      */
-    public int loadNetworks(Map<String, WifiConfiguration> configs,
-            SparseArray<Map<String, String>> networkExtras) {
-        int lastPriority = 0;
-        int last_id = -1;
-        boolean done = false;
-        while (!done) {
-            String listStr = mWifiNative.listNetworks(last_id);
-            if (listStr == null) {
-                return lastPriority;
-            }
-            String[] lines = listStr.split("\n");
-            if (mShowNetworks) {
-                localLog("loadNetworks:  ");
-                for (String net : lines) {
-                    localLog(net);
-                }
-            }
-            // Skip the first line, which is a header
-            for (int i = 1; i < lines.length; i++) {
-                String[] result = lines[i].split("\t");
-                // network-id | ssid | bssid | flags
-                WifiConfiguration config = new WifiConfiguration();
-                try {
-                    config.networkId = Integer.parseInt(result[0]);
-                    last_id = config.networkId;
-                } catch (NumberFormatException e) {
-                    loge("Failed to read network-id '" + result[0] + "'");
-                    continue;
-                }
-                // Ignore the supplicant status, start all networks disabled.
-                config.status = WifiConfiguration.Status.DISABLED;
-                readNetworkVariables(config);
-                // Parse the serialized JSON dictionary in ID_STRING_VAR_NAME once and cache the
-                // result for efficiency.
-                Map<String, String> extras = mWifiNative.getNetworkExtra(config.networkId,
-                        ID_STRING_VAR_NAME);
-                if (extras == null) {
-                    extras = new HashMap<String, String>();
-                    // If ID_STRING_VAR_NAME did not contain a dictionary, assume that it contains
-                    // just a quoted FQDN. This is the legacy format that was used in Marshmallow.
-                    final String fqdn = Utils.unquote(mWifiNative.getNetworkVariable(
-                            config.networkId, ID_STRING_VAR_NAME));
-                    if (fqdn != null) {
-                        extras.put(ID_STRING_KEY_FQDN, fqdn);
-                        config.FQDN = fqdn;
-                        // Mark the configuration as a Hotspot 2.0 network.
-                        config.providerFriendlyName = "";
-                    }
-                }
-                networkExtras.put(config.networkId, extras);
-
-                if (config.priority > lastPriority) {
-                    lastPriority = config.priority;
-                }
-                config.setIpAssignment(IpAssignment.DHCP);
-                config.setProxySettings(ProxySettings.NONE);
-                // The configKey is explicitly stored in wpa_supplicant.conf, because config does
-                // not contain sufficient information to compute it at this point.
-                String configKey = extras.get(ID_STRING_KEY_CONFIG_KEY);
-                if (configKey == null) {
-                    // Handle the legacy case where the configKey is not stored in
-                    // wpa_supplicant.conf but can be computed straight away.
-                    // Force an update of this legacy network configuration by writing
-                    // the configKey for this network into wpa_supplicant.conf.
-                    configKey = config.configKey();
-                    saveNetworkMetadata(config);
-                }
-                final WifiConfiguration duplicateConfig = configs.put(configKey, config);
-                if (duplicateConfig != null) {
-                    // The network is already known. Overwrite the duplicate entry.
-                    if (mShowNetworks) {
-                        localLog("Replacing duplicate network " + duplicateConfig.networkId
-                                + " with " + config.networkId + ".");
-                    }
-                    // This can happen after the user manually connected to an AP and tried to use
-                    // WPS to connect the AP later. In this case, the supplicant will create a new
-                    // network for the AP although there is an existing network already.
-                    mWifiNative.removeNetwork(duplicateConfig.networkId);
-                }
-            }
-            done = (lines.length == 1);
-        }
-        return lastPriority;
-    }
-
+    private static final String STORE_DIRECTORY_NAME = "wifi";
     /**
-     * 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.
+     * Time interval for buffering file writes for non-forced writes
      */
-    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;
-        }
-    }
-
+    private static final int BUFFERED_WRITE_ALARM_INTERVAL_MS = 10 * 1000;
     /**
-     * Remove enterprise keys from the network config.
-     *
-     * @param config Config corresponding to the network.
+     * Handler instance to post alarm timeouts to
      */
-    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);
-                }
-            }
-        }
-    }
-
+    private final Handler mEventHandler;
     /**
-     * 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.
+     * Alarm manager instance to start buffer timeout alarms.
      */
-    public boolean saveNetworkMetadata(WifiConfiguration config) {
-        final Map<String, String> metadata = new HashMap<String, String>();
-        if (config.isPasspoint()) {
-            metadata.put(ID_STRING_KEY_FQDN, config.FQDN);
-        }
-        metadata.put(ID_STRING_KEY_CONFIG_KEY, config.configKey());
-        metadata.put(ID_STRING_KEY_CREATOR_UID, Integer.toString(config.creatorUid));
-        if (!mWifiNative.setNetworkExtra(config.networkId, ID_STRING_VAR_NAME, metadata)) {
-            loge("failed to set id_str: " + metadata.toString());
-            return false;
-        }
-        return true;
-    }
-
-    private BitSet addFastTransitionFlags(BitSet keyManagementFlags) {
-        BitSet modifiedFlags = keyManagementFlags;
-        if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-            modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_PSK);
-        }
-        if (keyManagementFlags.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
-            modifiedFlags.set(WifiConfiguration.KeyMgmt.FT_EAP);
-        }
-        return modifiedFlags;
-    }
-
-    private BitSet removeFastTransitionFlags(BitSet keyManagementFlags) {
-        BitSet modifiedFlags = keyManagementFlags;
-        modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_PSK);
-        modifiedFlags.clear(WifiConfiguration.KeyMgmt.FT_EAP);
-        return modifiedFlags;
-    }
-
+    private final AlarmManager mAlarmManager;
     /**
-     * Save an entire network configuration to wpa_supplicant.
-     *
-     * @param config Config corresponding to the network.
-     * @param netId Net Id of the network.
-     * @param addFastTransitionFlags Add the BSS fast transition(80211r) flags to the network.
-     * @return true if successful, false otherwise.
+     * Clock instance to retrieve timestamps for alarms.
      */
-    private boolean saveNetwork(WifiConfiguration config, int netId,
-            boolean addFastTransitionFlags) {
-        if (config == null) {
-            return false;
-        }
-        if (VDBG) localLog("saveNetwork: " + netId);
-        if (config.SSID != null && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.ssidVarName,
-                encodeSSID(config.SSID))) {
-            loge("failed to set SSID: " + config.SSID);
-            return false;
-        }
-        if (!saveNetworkMetadata(config)) {
-            return false;
-        }
-        //set selected BSSID to supplicant
-        if (config.getNetworkSelectionStatus().getNetworkSelectionBSSID() != null) {
-            String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
-            if (!mWifiNative.setNetworkVariable(netId, WifiConfiguration.bssidVarName, bssid)) {
-                loge("failed to set BSSID: " + bssid);
-                return false;
-            }
-        }
-        BitSet allowedKeyManagement = config.allowedKeyManagement;
-        if (addFastTransitionFlags) {
-            allowedKeyManagement = addFastTransitionFlags(config.allowedKeyManagement);
-        }
-        String allowedKeyManagementString =
-                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
-        if (config.allowedKeyManagement.cardinality() != 0 && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.KeyMgmt.varName,
-                allowedKeyManagementString)) {
-            loge("failed to set key_mgmt: " + allowedKeyManagementString);
-            return false;
-        }
-        String allowedProtocolsString =
-                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
-        if (config.allowedProtocols.cardinality() != 0 && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.Protocol.varName,
-                allowedProtocolsString)) {
-            loge("failed to set proto: " + allowedProtocolsString);
-            return false;
-        }
-        String allowedAuthAlgorithmsString =
-                makeString(config.allowedAuthAlgorithms,
-                        WifiConfiguration.AuthAlgorithm.strings);
-        if (config.allowedAuthAlgorithms.cardinality() != 0 && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.AuthAlgorithm.varName,
-                allowedAuthAlgorithmsString)) {
-            loge("failed to set auth_alg: " + allowedAuthAlgorithmsString);
-            return false;
-        }
-        String allowedPairwiseCiphersString = makeString(config.allowedPairwiseCiphers,
-                WifiConfiguration.PairwiseCipher.strings);
-        if (config.allowedPairwiseCiphers.cardinality() != 0 && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.PairwiseCipher.varName,
-                allowedPairwiseCiphersString)) {
-            loge("failed to set pairwise: " + allowedPairwiseCiphersString);
-            return false;
-        }
-        // Make sure that the string "GTK_NOT_USED" is /not/ transformed - wpa_supplicant
-        // uses this literal value and not the 'dashed' version.
-        String allowedGroupCiphersString =
-                makeStringWithException(config.allowedGroupCiphers,
-                        WifiConfiguration.GroupCipher.strings,
-                        WifiConfiguration.GroupCipher
-                                .strings[WifiConfiguration.GroupCipher.GTK_NOT_USED]);
-        if (config.allowedGroupCiphers.cardinality() != 0 && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.GroupCipher.varName,
-                allowedGroupCiphersString)) {
-            loge("failed to set group: " + allowedGroupCiphersString);
-            return false;
-        }
-        // Prevent client screw-up by passing in a WifiConfiguration we gave it
-        // by preventing "*" as a key.
-        if (config.preSharedKey != null && !config.preSharedKey.equals("*")
-                && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.pskVarName,
-                config.preSharedKey)) {
-            loge("failed to set psk");
-            return false;
-        }
-        boolean hasSetKey = false;
-        if (config.wepKeys != null) {
-            for (int i = 0; i < config.wepKeys.length; i++) {
-                // Prevent client screw-up by passing in a WifiConfiguration we gave it
-                // by preventing "*" as a key.
-                if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
-                    if (!mWifiNative.setNetworkVariable(
-                            netId,
-                            WifiConfiguration.wepKeyVarNames[i],
-                            config.wepKeys[i])) {
-                        loge("failed to set wep_key" + i + ": " + config.wepKeys[i]);
-                        return false;
-                    }
-                    hasSetKey = true;
-                }
-            }
-        }
-        if (hasSetKey) {
-            if (!mWifiNative.setNetworkVariable(
-                    netId,
-                    WifiConfiguration.wepTxKeyIdxVarName,
-                    Integer.toString(config.wepTxKeyIndex))) {
-                loge("failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
-                return false;
-            }
-        }
-        if (!mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.priorityVarName,
-                Integer.toString(config.priority))) {
-            loge(config.SSID + ": failed to set priority: " + config.priority);
-            return false;
-        }
-        if (config.hiddenSSID && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.hiddenSSIDVarName,
-                Integer.toString(config.hiddenSSID ? 1 : 0))) {
-            loge(config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID);
-            return false;
-        }
-        if (config.requirePMF && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.pmfVarName,
-                Integer.toString(STORED_VALUE_FOR_REQUIRE_PMF))) {
-            loge(config.SSID + ": failed to set requirePMF: " + config.requirePMF);
-            return false;
-        }
-        if (config.updateIdentifier != null && !mWifiNative.setNetworkVariable(
-                netId,
-                WifiConfiguration.updateIdentiferVarName,
-                config.updateIdentifier)) {
-            loge(config.SSID + ": failed to set updateIdentifier: " + config.updateIdentifier);
-            return false;
-        }
-        return true;
-    }
-
+    private final Clock mClock;
     /**
-     * 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.
+     * Shared config store file instance.
      */
-    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);
-            return false;
-        }
-        return true;
-    }
-
+    private StoreFile mSharedStore;
     /**
-     * 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.
-     * @param addFastTransitionFlags Add the BSS fast transition(80211r) flags to the network.
-     * @return true if successful, false otherwise.
+     * User specific store file instance.
      */
-    public boolean addOrUpdateNetwork(WifiConfiguration config, WifiConfiguration existingConfig,
-            boolean addFastTransitionFlags) {
-        if (config == null) {
-            return false;
-        }
-        if (VDBG) localLog("addOrUpdateNetwork: " + config.networkId);
-        int netId = config.networkId;
-        boolean newNetwork = false;
-        /*
-         * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
-         * network configuration. Otherwise, the networkId should
-         * refer to an existing configuration.
-         */
-        if (netId == WifiConfiguration.INVALID_NETWORK_ID) {
-            newNetwork = true;
-            netId = mWifiNative.addNetwork();
-            if (netId < 0) {
-                loge("Failed to add a network!");
-                return false;
-            } else {
-                logi("addOrUpdateNetwork created netId=" + netId);
-            }
-            // Save the new network ID to the config
-            config.networkId = netId;
-        }
-        if (!saveNetwork(config, netId, addFastTransitionFlags)) {
-            if (newNetwork) {
-                mWifiNative.removeNetwork(netId);
-                loge("Failed to set a network variable, removed network: " + netId);
-            }
-            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;
-    }
-
+    private StoreFile mUserStore;
     /**
-     * Remove the specified network and save config
-     *
-     * @param config Config corresponding to the network.
-     * @return {@code true} if it succeeds, {@code false} otherwise
+     * Verbose logging flag.
      */
-    public boolean removeNetwork(WifiConfiguration config) {
-        if (config == null) {
-            return false;
-        }
-        if (VDBG) 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;
-    }
-
+    private boolean mVerboseLoggingEnabled = false;
     /**
-     * Select a network in wpa_supplicant.
-     *
-     * @param config Config corresponding to the network.
-     * @return true if successful, false otherwise.
+     * Flag to indicate if there is a buffered write pending.
      */
-    public boolean selectNetwork(WifiConfiguration config, Collection<WifiConfiguration> configs) {
-        if (config == null) {
-            return false;
-        }
-        if (VDBG) 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;
-    }
-
+    private boolean mBufferedWritePending = false;
     /**
-     * Disable a network in wpa_supplicant.
-     *
-     * @param config Config corresponding to the network.
-     * @return true if successful, false otherwise.
+     * Alarm listener for flushing out any buffered writes.
      */
-    boolean disableNetwork(WifiConfiguration config) {
-        if (config == null) {
-            return false;
-        }
-        if (VDBG) 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;
-    }
-
-    /**
-     * Set priority for a network in wpa_supplicant.
-     *
-     * @param config Config corresponding to the network.
-     * @return true if successful, false otherwise.
-     */
-    public boolean setNetworkPriority(WifiConfiguration config, int priority) {
-        if (config == null) {
-            return false;
-        }
-        if (VDBG) 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);
-            return false;
-        }
-        config.priority = priority;
-        return true;
-    }
-
-    /**
-     * Set SSID for a network in wpa_supplicant.
-     *
-     * @param config Config corresponding to the network.
-     * @return true if successful, false otherwise.
-     */
-    public boolean setNetworkSSID(WifiConfiguration config, String ssid) {
-        if (config == null) {
-            return false;
-        }
-        if (VDBG) 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);
-            return false;
-        }
-        config.SSID = ssid;
-        return true;
-    }
-
-    /**
-     * Set BSSID for a network in wpa_supplicant from network selection.
-     *
-     * @param config Config corresponding to the network.
-     * @param bssid  BSSID to be set.
-     * @return true if successful, false otherwise.
-     */
-    public boolean setNetworkBSSID(WifiConfiguration config, String bssid) {
-        // Sanity check the config is valid
-        if (config == null
-                || (config.networkId == WifiConfiguration.INVALID_NETWORK_ID
-                && config.SSID == null)) {
-            return false;
-        }
-        if (VDBG) localLog("setNetworkBSSID: " + config.networkId);
-        if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.bssidVarName,
-                bssid)) {
-            loge("Set BSSID of network in wpa_supplicant failed on " + config.networkId);
-            return false;
-        }
-        config.getNetworkSelectionStatus().setNetworkSelectionBSSID(bssid);
-        return true;
-    }
-
-    /**
-     * Enable/Disable HS20 parameter in wpa_supplicant.
-     *
-     * @param enable Enable/Disable the parameter.
-     */
-    public void enableHS20(boolean enable) {
-        mWifiNative.setHs20(enable);
-    }
-
-    /**
-     * Disables all the networks in the provided list in wpa_supplicant.
-     *
-     * @param configs Collection of configs which needs to be enabled.
-     * @return true if successful, false otherwise.
-     */
-    public boolean disableAllNetworks(Collection<WifiConfiguration> configs) {
-        if (VDBG) localLog("disableAllNetworks");
-        boolean networkDisabled = false;
-        for (WifiConfiguration enabled : configs) {
-            if (disableNetwork(enabled)) {
-                networkDisabled = true;
-            }
-        }
-        saveConfig();
-        return networkDisabled;
-    }
-
-    /**
-     * Save the current configuration to wpa_supplicant.conf.
-     */
-    public boolean saveConfig() {
-        return mWifiNative.saveConfig();
-    }
-
-    /**
-     * Read network variables from wpa_supplicant.conf.
-     *
-     * @param key The parameter to be parsed.
-     * @return Map of corresponding configKey to the value of the param requested.
-     */
-    public Map<String, String> readNetworkVariablesFromSupplicantFile(String key) {
-        Map<String, String> result = new HashMap<>();
-        BufferedReader reader = null;
-        try {
-            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);
-        } catch (IOException e) {
-            if (VDBG) loge("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
-        } finally {
-            try {
-                if (reader != null) {
-                    reader.close();
-                }
-            } catch (IOException e) {
-                if (VDBG) {
-                    loge("Could not close reader for " + SUPPLICANT_CONFIG_FILE + ", " + e);
-                }
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Read network variables from a given reader. This method is separate from
-     * readNetworkVariablesFromSupplicantFile() for testing.
-     *
-     * @param reader The reader to read the network variables from.
-     * @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);
-        boolean found = false;
-        String configKey = null;
-        String value = null;
-        for (String line = reader.readLine(); line != null; line = reader.readLine()) {
-            if (line.matches("[ \\t]*network=\\{")) {
-                found = true;
-                configKey = null;
-                value = null;
-            } else if (line.matches("[ \\t]*\\}")) {
-                found = false;
-                configKey = null;
-                value = null;
-            }
-            if (found) {
-                String trimmedLine = line.trim();
-                if (trimmedLine.startsWith(ID_STRING_VAR_NAME + "=")) {
+    private final AlarmManager.OnAlarmListener mBufferedWriteListener =
+            new AlarmManager.OnAlarmListener() {
+                public void onAlarm() {
                     try {
-                        // Trim the quotes wrapping the id_str value.
-                        final String encodedExtras = trimmedLine.substring(
-                                8, trimmedLine.length() -1);
-                        final JSONObject json =
-                                new JSONObject(URLDecoder.decode(encodedExtras, "UTF-8"));
-                        if (json.has(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY)) {
-                            final Object configKeyFromJson =
-                                    json.get(WifiConfigStore.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);
-                        }
+                        writeBufferedData();
+                    } catch (IOException e) {
+                        Log.wtf(TAG, "Buffered write failed", e);
                     }
+
                 }
-                if (trimmedLine.startsWith(key + "=")) {
-                    value = trimmedLine.substring(key.length() + 1);
-                }
-                if (configKey != null && value != null) {
-                    result.put(configKey, value);
-                }
-            }
-        }
-        return result;
+            };
+
+    /**
+     * List of data container.
+     */
+    private final Map<String, StoreData> mStoreDataList;
+
+    /**
+     * 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.
+     */
+    public WifiConfigStore(Context context, Looper looper, Clock clock,
+            StoreFile sharedStore) {
+
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mEventHandler = new Handler(looper);
+        mClock = clock;
+        mStoreDataList = new HashMap<>();
+
+        // Initialize the store files.
+        mSharedStore = sharedStore;
+        // The user store is initialized to null, this will be set when the user unlocks and
+        // CE storage is accessible via |switchUserStoreAndRead|.
+        mUserStore = null;
+    }
+
+    public void setUserStore(StoreFile userStore) {
+        mUserStore = userStore;
     }
 
     /**
-     * Resets all sim networks from the provided network list.
+     * Register a {@link StoreData} to store.  A {@link StoreData} is responsible
+     * for a block of data in the store file, and provides serialization/deserialization functions
+     * for those data.
      *
-     * @param configs List of all the networks.
+     * @param storeData The store data to be registered to the config store
+     * @return true if succeeded
      */
-    public void resetSimNetworks(Collection<WifiConfiguration> configs) {
-        if (VDBG) localLog("resetSimNetworks");
-        for (WifiConfiguration config : configs) {
-            if (TelephonyUtil.isSimConfig(config)) {
-                String currentIdentity = TelephonyUtil.getSimIdentity(mContext,
-                        config.enterpriseConfig.getEapMethod());
-                String supplicantIdentity =
-                        mWifiNative.getNetworkVariable(config.networkId, "identity");
-                if(supplicantIdentity != null) {
-                    supplicantIdentity = removeDoubleQuotes(supplicantIdentity);
-                }
-                if (currentIdentity == null || !currentIdentity.equals(supplicantIdentity)) {
-                    // Identity differs so update the identity
-                    mWifiNative.setNetworkVariable(config.networkId,
-                            WifiEnterpriseConfig.IDENTITY_KEY, WifiEnterpriseConfig.EMPTY_VALUE);
-                    // This configuration may have cached Pseudonym IDs; lets remove them
-                    mWifiNative.setNetworkVariable(config.networkId,
-                            WifiEnterpriseConfig.ANON_IDENTITY_KEY,
-                            WifiEnterpriseConfig.EMPTY_VALUE);
-                }
-                // Update the loaded config
-                config.enterpriseConfig.setIdentity(currentIdentity);
-                config.enterpriseConfig.setAnonymousIdentity("");
+    public boolean registerStoreData(StoreData storeData) {
+        if (storeData == null) {
+            Log.e(TAG, "Unable to register null store data");
+            return false;
+        }
+        mStoreDataList.put(storeData.getName(), storeData);
+        return true;
+    }
+
+    /**
+     * Helper method to create a store file instance for either the shared store or user store.
+     * Note: The method creates the store directory if not already present. This may be needed for
+     * user store files.
+     *
+     * @param storeBaseDir Base directory under which the store file is to be stored. The store file
+     *                     will be at <storeBaseDir>/wifi/WifiConfigStore.xml.
+     * @return new instance of the store file.
+     */
+    private static StoreFile createFile(File storeBaseDir) {
+        File storeDir = new File(storeBaseDir, STORE_DIRECTORY_NAME);
+        if (!storeDir.exists()) {
+            if (!storeDir.mkdir()) {
+                Log.w(TAG, "Could not create store directory " + storeDir);
             }
         }
+        return new StoreFile(new File(storeDir, STORE_FILE_NAME));
+    }
+
+    /**
+     * Create a new instance of the shared store file.
+     *
+     * @return new instance of the store file or null if the directory cannot be created.
+     */
+    public static StoreFile createSharedFile() {
+        return createFile(Environment.getDataMiscDirectory());
+    }
+
+    /**
+     * 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 or null if the directory cannot be created.
+     */
+    public static StoreFile createUserFile(int userId) {
+        return createFile(Environment.getDataMiscCeDirectory(userId));
+    }
+
+    /**
+     * Enable verbose logging.
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
+    /**
+     * API to check if any of the store files are present on the device. This can be used
+     * to detect if the device needs to perform data migration from legacy stores.
+     *
+     * @return true if any of the store file is present, false otherwise.
+     */
+    public boolean areStoresPresent() {
+        return (mSharedStore.exists() || (mUserStore != null && mUserStore.exists()));
+    }
+
+    /**
+     * API to write the data provided by registered 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.
+     */
+    public void write(boolean forceSync)
+            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 = serializeData(true);
+        mSharedStore.storeRawDataToWrite(sharedDataBytes);
+        if (mUserStore != null) {
+            byte[] userDataBytes = serializeData(false);
+            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();
+        }
     }
 
     /**
-     * Clear BSSID blacklist in wpa_supplicant.
+     * Serialize share data or user data from all store data.
+     *
+     * @param shareData Flag indicating share data
+     * @return byte[] of serialized bytes
+     * @throws XmlPullParserException
+     * @throws IOException
      */
-    public void clearBssidBlacklist() {
-        if (VDBG) localLog("clearBlacklist");
-        mBssidBlacklist.clear();
-        mWifiNative.clearBlacklist();
-        mWifiNative.setBssidBlacklist(null);
+    private byte[] serializeData(boolean shareData) throws XmlPullParserException, IOException {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+
+        XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
+        XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION);
+
+        for (Map.Entry<String, StoreData> entry : mStoreDataList.entrySet()) {
+            String tag = entry.getKey();
+            StoreData storeData = entry.getValue();
+            // Ignore this store data if this is for share file and the store data doesn't support
+            // share store.
+            if (shareData && !storeData.supportShareData()) {
+                continue;
+            }
+            XmlUtil.writeNextSectionStart(out, tag);
+            storeData.serializeData(out, shareData);
+            XmlUtil.writeNextSectionEnd(out, tag);
+        }
+        XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
+
+        return outputStream.toByteArray();
     }
 
     /**
-     * Add a BSSID to the blacklist.
-     *
-     * @param bssid bssid to be added.
+     * Helper method to start a buffered write alarm if one doesn't already exist.
      */
-    public void blackListBssid(String bssid) {
-        if (bssid == null) {
+    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();
+
+        long writeStartTime = mClock.getElapsedSinceBootMillis();
+        mSharedStore.writeBufferedRawData();
+        if (mUserStore != null) {
+            mUserStore.writeBufferedRawData();
+        }
+        long writeTime = mClock.getElapsedSinceBootMillis() - writeStartTime;
+
+        Log.d(TAG, "Writing to stores completed in " + writeTime + " ms.");
+    }
+
+    /**
+     * 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.
+     */
+    public void read() throws XmlPullParserException, IOException {
+        // Reset both share and user store data.
+        resetStoreData(true);
+        resetStoreData(false);
+
+        long readStartTime = mClock.getElapsedSinceBootMillis();
+        byte[] sharedDataBytes = mSharedStore.readRawData();
+        byte[] userDataBytes = null;
+        if (mUserStore != null) {
+            userDataBytes = mUserStore.readRawData();
+        }
+        long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
+        Log.d(TAG, "Reading from stores completed in " + readTime + " ms.");
+        deserializeData(sharedDataBytes, true);
+        deserializeData(userDataBytes, false);
+    }
+
+    /**
+     * Handles a user switch. This method changes the user specific store file and reads from the
+     * new user's store file.
+     *
+     * @param userStore StoreFile instance pointing to the user specific store file. This should
+     *                  be retrieved using {@link #createUserFile(int)} method.
+     */
+    public void switchUserStoreAndRead(StoreFile userStore)
+            throws XmlPullParserException, IOException {
+        // Reset user store data.
+        resetStoreData(false);
+
+        // Stop any pending buffered writes, if any.
+        stopBufferedWriteAlarm();
+        mUserStore = userStore;
+
+        // Now read from the user store file.
+        long readStartTime = mClock.getElapsedSinceBootMillis();
+        byte[] userDataBytes = mUserStore.readRawData();
+        long readTime = mClock.getElapsedSinceBootMillis() - readStartTime;
+        Log.d(TAG, "Reading from user store completed in " + readTime + " ms.");
+        deserializeData(userDataBytes, false);
+    }
+
+    /**
+     * Reset share data or user data in all store data.
+     *
+     * @param shareData Flag indicating share data
+     */
+    private void resetStoreData(boolean shareData) {
+        for (Map.Entry<String, StoreData> entry : mStoreDataList.entrySet()) {
+            entry.getValue().resetData(shareData);
+        }
+    }
+
+    /**
+     * Deserialize share data or user data into store data.
+     *
+     * @param dataBytes The data to parse
+     * @param shareData The flag indicating share data
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void deserializeData(byte[] dataBytes, boolean shareData)
+            throws XmlPullParserException, IOException {
+        if (dataBytes == null) {
             return;
         }
-        if (VDBG) localLog("blackListBssid: " + bssid);
-        mBssidBlacklist.add(bssid);
-        // Blacklist at wpa_supplicant
-        mWifiNative.addToBlacklist(bssid);
-        // Blacklist at firmware
-        String[] list = mBssidBlacklist.toArray(new String[mBssidBlacklist.size()]);
-        mWifiNative.setBssidBlacklist(list);
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(dataBytes);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+        // Start parsing the XML stream.
+        int rootTagDepth = in.getDepth() + 1;
+        parseDocumentStartAndVersionFromXml(in);
+
+        String[] headerName = new String[1];
+        while (XmlUtil.gotoNextSectionOrEnd(in, headerName, rootTagDepth)) {
+            StoreData storeData = mStoreDataList.get(headerName[0]);
+            if (storeData == null) {
+                throw new XmlPullParserException("Unknown store data: " + headerName[0]);
+            }
+            storeData.deserializeData(in, rootTagDepth + 1, shareData);
+        }
     }
 
     /**
-     * Checks if the provided bssid is blacklisted or not.
+     * Parse the document start and version from the XML stream.
+     * This is used for both the shared and user config store data.
      *
-     * @param bssid bssid to be checked.
-     * @return true if present, false otherwise.
+     * @param in XmlPullParser instance pointing to the XML stream.
+     * @return version number retrieved from the Xml stream.
      */
-    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 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);
         }
-    }
-
-    private void markAllNetworksDisabled(Collection<WifiConfiguration> configs) {
-        markAllNetworksDisabledExcept(WifiConfiguration.INVALID_NETWORK_ID, configs);
+        return version;
     }
 
     /**
-     * Start WPS pin method configuration with pin obtained
-     * from the access point
-     *
-     * @param config WPS configuration
-     * @return Wps result containing status and pin
+     * 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 WpsResult startWpsWithPinFromAccessPoint(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
-        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");
-            result.status = WpsResult.Status.FAILURE;
-        }
-        return result;
-    }
+    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;
 
-    /**
-     * Start WPS pin method configuration with obtained
-     * from the device
-     *
-     * @return WpsResult indicating status and pin
-     */
-    public WpsResult startWpsWithPinFromDevice(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
-        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");
-            result.status = WpsResult.Status.FAILURE;
-        }
-        return result;
-    }
-
-    /**
-     * Start WPS push button configuration
-     *
-     * @param config WPS configuration
-     * @return WpsResult indicating status and pin
-     */
-    public WpsResult startWpsPbc(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
-        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");
-            result.status = WpsResult.Status.FAILURE;
-        }
-        return result;
-    }
-
-    protected void logd(String s) {
-        Log.d(TAG, s);
-    }
-
-    protected void logi(String s) {
-        Log.i(TAG, s);
-    }
-
-    protected void loge(String s) {
-        loge(s, false);
-    }
-
-    protected void loge(String s, boolean stack) {
-        if (stack) {
-            Log.e(TAG, s + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
-                    + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
-        } else {
-            Log.e(TAG, s);
-        }
-    }
-
-    protected void log(String s) {
-        Log.d(TAG, s);
-    }
-
-    private void localLog(String s) {
-        if (mLocalLog != null) {
-            mLocalLog.log(TAG + ": " + s);
-        }
-    }
-
-    private void localLogAndLogcat(String s) {
-        localLog(s);
-        Log.d(TAG, s);
-    }
-
-    private class SupplicantSaver implements WifiEnterpriseConfig.SupplicantSaver {
-        private final int mNetId;
-        private final String mSetterSSID;
-
-        SupplicantSaver(int netId, String setterSSID) {
-            mNetId = netId;
-            mSetterSSID = setterSSID;
+        public StoreFile(File file) {
+            mAtomicFile = new AtomicFile(file);
+            mFileName = mAtomicFile.getBaseFile().getAbsolutePath();
         }
 
-        @Override
-        public boolean saveValue(String key, String value) {
-            if (key.equals(WifiEnterpriseConfig.PASSWORD_KEY)
-                    && value != null && value.equals("*")) {
-                // No need to try to set an obfuscated password, which will fail
-                return true;
-            }
-            if (key.equals(WifiEnterpriseConfig.REALM_KEY)
-                    || key.equals(WifiEnterpriseConfig.PLMN_KEY)) {
-                // No need to save realm or PLMN in supplicant
-                return true;
-            }
-            // TODO: We need a way to clear values in wpa_supplicant as opposed to
-            // mapping unset values to empty strings.
-            if (value == null) {
-                value = "\"\"";
-            }
-            if (!mWifiNative.setNetworkVariable(mNetId, key, value)) {
-                loge(mSetterSSID + ": failed to set " + key + ": " + value);
-                return false;
-            }
-            return true;
-        }
-    }
-
-    private class SupplicantLoader implements WifiEnterpriseConfig.SupplicantLoader {
-        private final int mNetId;
-
-        SupplicantLoader(int netId) {
-            mNetId = netId;
+        /**
+         * Returns whether the store file already exists on disk or not.
+         *
+         * @return true if it exists, false otherwise.
+         */
+        public boolean exists() {
+            return mAtomicFile.exists();
         }
 
-        @Override
-        public String loadValue(String key) {
-            String value = mWifiNative.getNetworkVariable(mNetId, key);
-            if (!TextUtils.isEmpty(value)) {
-                if (!enterpriseConfigKeyShouldBeQuoted(key)) {
-                    value = removeDoubleQuotes(value);
-                }
-                return value;
-            } else {
+        /**
+         * 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;
             }
         }
 
         /**
-         * Returns true if a particular config key needs to be quoted when passed to the supplicant.
+         * 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.
          */
-        private boolean enterpriseConfigKeyShouldBeQuoted(String key) {
-            switch (key) {
-                case WifiEnterpriseConfig.EAP_KEY:
-                case WifiEnterpriseConfig.ENGINE_KEY:
-                    return false;
-                default:
-                    return true;
+        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;
         }
     }
 
-    // TODO(rpius): Remove this.
-    private class WpaConfigFileObserver extends FileObserver {
+    /**
+     * Interface to be implemented by a module that contained data in the config store file.
+     *
+     * The module will be responsible for serializing/deserializing their own data.
+     */
+    public interface StoreData {
+        /**
+         * Serialize a XML data block to the output stream. The |shared| flag indicates if the
+         * output stream is backed by a share store or an user store.
+         *
+         * @param out The output stream to serialize the data to
+         * @param shared Flag indicating if the output stream is backed by a share store or an
+         *               user store
+         */
+        void serializeData(XmlSerializer out, boolean shared)
+                throws XmlPullParserException, IOException;
 
-        WpaConfigFileObserver() {
-            super(SUPPLICANT_CONFIG_FILE, CLOSE_WRITE);
-        }
+        /**
+         * Deserialize a XML data block from the input stream.  The |shared| flag indicates if the
+         * input stream is backed by a share store or an user store.  When |shared| is set to true,
+         * the shared configuration data will be overwritten by the parsed data. Otherwise,
+         * the user configuration will be overwritten by the parsed data.
+         *
+         * @param in The input stream to read the data from
+         * @param outerTagDepth The depth of the outer tag in the XML document
+         * @Param shared Flag indicating if the input stream is backed by a share store or an
+         *               user store
+         */
+        void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+                throws XmlPullParserException, IOException;
 
-        @Override
-        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());
-            }
-        }
+        /**
+         * Reset configuration data.  The |shared| flag indicates which configuration data to
+         * reset.  When |shared| is set to true, the shared configuration data will be reset.
+         * Otherwise, the user configuration data will be reset.
+         */
+        void resetData(boolean shared);
+
+        /**
+         * Return the name of this store data.  The data will be enclosed under this tag in
+         * the XML block.
+         *
+         * @return The name of the store data
+         */
+        String getName();
+
+        /**
+         * Flag indicating if shared configuration data is supported.
+         *
+         * @return true if shared configuration data is supported
+         */
+        boolean supportShareData();
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiConfigStoreLegacy.java b/service/java/com/android/server/wifi/WifiConfigStoreLegacy.java
new file mode 100644
index 0000000..8677755
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiConfigStoreLegacy.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.IpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.os.Environment;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.server.net.IpConfigStore;
+import com.android.server.wifi.hotspot2.LegacyPasspointConfig;
+import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class provides the API's to load network configurations from legacy store
+ * mechanism (Pre O release).
+ * This class loads network configurations from:
+ * 1. /data/misc/wifi/networkHistory.txt
+ * 2. /data/misc/wifi/wpa_supplicant.conf
+ * 3. /data/misc/wifi/ipconfig.txt
+ * 4. /data/misc/wifi/PerProviderSubscription.conf
+ *
+ * The order of invocation of the public methods during migration is the following:
+ * 1. Check if legacy stores are present using {@link #areStoresPresent()}.
+ * 2. Load all the store data using {@link #read()}
+ * 3. Write the store data to the new store.
+ * 4. Remove all the legacy stores using {@link #removeStores()}
+ *
+ * NOTE: This class should only be used from WifiConfigManager and is not thread-safe!
+ *
+ * TODO(b/31065385): Passpoint config store data migration & deletion.
+ */
+public class WifiConfigStoreLegacy {
+    /**
+     * Log tag.
+     */
+    private static final String TAG = "WifiConfigStoreLegacy";
+    /**
+     * NetworkHistory config store file path.
+     */
+    private static final File NETWORK_HISTORY_FILE =
+            new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
+    /**
+     * Passpoint config store file path.
+     */
+    private static final File PPS_FILE =
+            new File(Environment.getDataMiscDirectory(), "wifi/PerProviderSubscription.conf");
+    /**
+     * IpConfig config store file path.
+     */
+    private static final File IP_CONFIG_FILE =
+            new File(Environment.getDataMiscDirectory(), "wifi/ipconfig.txt");
+    /**
+     * List of external dependencies for WifiConfigManager.
+     */
+    private final WifiNetworkHistory mWifiNetworkHistory;
+    private final WifiNative mWifiNative;
+    private final IpConfigStore mIpconfigStore;
+
+    private final LegacyPasspointConfigParser mPasspointConfigParser;
+
+    WifiConfigStoreLegacy(WifiNetworkHistory wifiNetworkHistory,
+            WifiNative wifiNative, IpConfigStore ipConfigStore,
+            LegacyPasspointConfigParser passpointConfigParser) {
+        mWifiNetworkHistory = wifiNetworkHistory;
+        mWifiNative = wifiNative;
+        mIpconfigStore = ipConfigStore;
+        mPasspointConfigParser = passpointConfigParser;
+    }
+
+    /**
+     * Helper function to lookup the WifiConfiguration object from configKey to WifiConfiguration
+     * object map using the hashcode of the configKey.
+     *
+     * @param configurationMap Map of configKey to WifiConfiguration object.
+     * @param hashCode         hash code of the configKey to match.
+     * @return
+     */
+    private static WifiConfiguration lookupWifiConfigurationUsingConfigKeyHash(
+            Map<String, WifiConfiguration> configurationMap, int hashCode) {
+        for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
+            if (entry.getKey().hashCode() == hashCode) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Helper function to load {@link IpConfiguration} data from the ip config store file and
+     * populate the provided configuration map.
+     *
+     * @param configurationMap Map of configKey to WifiConfiguration object.
+     */
+    private void loadFromIpConfigStore(Map<String, WifiConfiguration> configurationMap) {
+        // This is a map of the hash code of the network's configKey to the corresponding
+        // IpConfiguration.
+        SparseArray<IpConfiguration> ipConfigurations =
+                mIpconfigStore.readIpAndProxyConfigurations(IP_CONFIG_FILE.getAbsolutePath());
+        if (ipConfigurations == null || ipConfigurations.size() == 0) {
+            Log.w(TAG, "No ip configurations found in ipconfig store");
+            return;
+        }
+        for (int i = 0; i < ipConfigurations.size(); i++) {
+            int id = ipConfigurations.keyAt(i);
+            WifiConfiguration config =
+                    lookupWifiConfigurationUsingConfigKeyHash(configurationMap, id);
+            // This is the only place the map is looked up through a (dangerous) hash-value!
+            if (config == null || config.ephemeral) {
+                Log.w(TAG, "configuration found for missing network, nid=" + id
+                        + ", ignored, networks.size=" + Integer.toString(ipConfigurations.size()));
+            } else {
+                config.setIpConfiguration(ipConfigurations.valueAt(i));
+            }
+        }
+    }
+
+    /**
+     * Helper function to load {@link WifiConfiguration} data from networkHistory file and populate
+     * the provided configuration map and deleted ephemeral ssid list.
+     *
+     * @param configurationMap      Map of configKey to WifiConfiguration object.
+     * @param deletedEphemeralSSIDs Map of configKey to WifiConfiguration object.
+     */
+    private void loadFromNetworkHistory(
+            Map<String, WifiConfiguration> configurationMap, Set<String> deletedEphemeralSSIDs) {
+        // TODO: Need  to revisit the scan detail cache persistance. We're not doing it in the new
+        // config store, so ignore it here as well.
+        Map<Integer, ScanDetailCache> scanDetailCaches = new HashMap<>();
+        mWifiNetworkHistory.readNetworkHistory(
+                configurationMap, scanDetailCaches, deletedEphemeralSSIDs);
+    }
+
+    /**
+     * Helper function to load {@link WifiConfiguration} data from wpa_supplicant and populate
+     * the provided configuration map and network extras.
+     *
+     * This method needs to manually parse the wpa_supplicant.conf file to retrieve some of the
+     * password fields like psk, wep_keys. password, etc.
+     *
+     * @param configurationMap Map of configKey to WifiConfiguration object.
+     * @param networkExtras    Map of network extras parsed from wpa_supplicant.
+     */
+    private void loadFromWpaSupplicant(
+            Map<String, WifiConfiguration> configurationMap,
+            SparseArray<Map<String, String>> networkExtras) {
+        if (!mWifiNative.migrateNetworksFromSupplicant(configurationMap, networkExtras)) {
+            Log.wtf(TAG, "Failed to load wifi configurations from wpa_supplicant");
+            return;
+        }
+        if (configurationMap.isEmpty()) {
+            Log.w(TAG, "No wifi configurations found in wpa_supplicant");
+            return;
+        }
+    }
+
+    /**
+     * Helper function to update {@link WifiConfiguration} that represents a Passpoint
+     * configuration.
+     *
+     * This method will manually parse PerProviderSubscription.conf file to retrieve missing
+     * fields: provider friendly name, roaming consortium OIs, realm, IMSI.
+     *
+     * @param configurationMap Map of configKey to WifiConfiguration object.
+     * @param networkExtras    Map of network extras parsed from wpa_supplicant.
+     */
+    private void loadFromPasspointConfigStore(
+            Map<String, WifiConfiguration> configurationMap,
+            SparseArray<Map<String, String>> networkExtras) {
+        Map<String, LegacyPasspointConfig> passpointConfigMap = null;
+        try {
+            passpointConfigMap = mPasspointConfigParser.parseConfig(PPS_FILE.getAbsolutePath());
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to read/parse Passpoint config file: " + e.getMessage());
+        }
+
+        List<String> entriesToBeRemoved = new ArrayList<>();
+        for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
+            WifiConfiguration wifiConfig = entry.getValue();
+            // Ignore non-Enterprise network since enterprise configuration is required for
+            // Passpoint.
+            if (wifiConfig.enterpriseConfig == null || wifiConfig.enterpriseConfig.getEapMethod()
+                    == WifiEnterpriseConfig.Eap.NONE) {
+                continue;
+            }
+            // Ignore configuration without FQDN.
+            Map<String, String> extras = networkExtras.get(wifiConfig.networkId);
+            if (extras == null || !extras.containsKey(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN)) {
+                continue;
+            }
+            String fqdn = networkExtras.get(wifiConfig.networkId).get(
+                    SupplicantStaNetworkHal.ID_STRING_KEY_FQDN);
+
+            // Remove the configuration if failed to find the matching configuration in the
+            // Passpoint configuration file.
+            if (passpointConfigMap == null || !passpointConfigMap.containsKey(fqdn)) {
+                entriesToBeRemoved.add(entry.getKey());
+                continue;
+            }
+
+            // Update the missing Passpoint configuration fields to this WifiConfiguration.
+            LegacyPasspointConfig passpointConfig = passpointConfigMap.get(fqdn);
+            wifiConfig.isLegacyPasspointConfig = true;
+            wifiConfig.FQDN = fqdn;
+            wifiConfig.providerFriendlyName = passpointConfig.mFriendlyName;
+            if (passpointConfig.mRoamingConsortiumOis != null) {
+                wifiConfig.roamingConsortiumIds = Arrays.copyOf(
+                        passpointConfig.mRoamingConsortiumOis,
+                        passpointConfig.mRoamingConsortiumOis.length);
+            }
+            if (passpointConfig.mImsi != null) {
+                wifiConfig.enterpriseConfig.setPlmn(passpointConfig.mImsi);
+            }
+            if (passpointConfig.mRealm != null) {
+                wifiConfig.enterpriseConfig.setRealm(passpointConfig.mRealm);
+            }
+        }
+
+        // Remove any incomplete Passpoint configurations. Should never happen, in case it does
+        // remove them to avoid maintaining any invalid Passpoint configurations.
+        for (String key : entriesToBeRemoved) {
+            Log.w(TAG, "Remove incomplete Passpoint configuration: " + key);
+            configurationMap.remove(key);
+        }
+    }
+
+    /**
+     * Helper function to load from the different legacy stores:
+     * 1. Read the network configurations from wpa_supplicant using {@link WifiNative}.
+     * 2. Read the network configurations from networkHistory.txt using {@link WifiNetworkHistory}.
+     * 3. Read the Ip configurations from ipconfig.txt using {@link IpConfigStore}.
+     * 4. Read all the passpoint info from PerProviderSubscription.conf using
+     * {@link LegacyPasspointConfigParser}.
+     */
+    public WifiConfigStoreDataLegacy read() {
+        final Map<String, WifiConfiguration> configurationMap = new HashMap<>();
+        final SparseArray<Map<String, String>> networkExtras = new SparseArray<>();
+        final Set<String> deletedEphemeralSSIDs = new HashSet<>();
+
+        loadFromWpaSupplicant(configurationMap, networkExtras);
+        loadFromNetworkHistory(configurationMap, deletedEphemeralSSIDs);
+        loadFromIpConfigStore(configurationMap);
+        loadFromPasspointConfigStore(configurationMap, networkExtras);
+
+        // Now create config store data instance to be returned.
+        return new WifiConfigStoreDataLegacy(
+                new ArrayList<>(configurationMap.values()), deletedEphemeralSSIDs);
+    }
+
+    /**
+     * Function to check if the legacy store files are present and hence load from those stores and
+     * then delete them.
+     *
+     * @return true if legacy store files are present, false otherwise.
+     */
+    public boolean areStoresPresent() {
+        // We may have to keep the wpa_supplicant.conf file around. So, just use networkhistory.txt
+        // as a check to see if we have not yet migrated or not. This should be the last file
+        // that is deleted after migration.
+        File file = new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
+        return file.exists();
+    }
+
+    /**
+     * Method to remove all the legacy store files. This should only be invoked once all
+     * the data has been migrated to the new store file.
+     * 1. Removes all networks from wpa_supplicant and saves it to wpa_supplicant.conf
+     * 2. Deletes ipconfig.txt
+     * 3. Deletes networkHistory.txt
+     *
+     * @return true if all the store files were deleted successfully, false otherwise.
+     */
+    public boolean removeStores() {
+        // TODO(b/29352330): Delete wpa_supplicant.conf file instead.
+        // First remove all networks from wpa_supplicant and save configuration.
+        if (!mWifiNative.removeAllNetworks()) {
+            Log.e(TAG, "Removing networks from wpa_supplicant failed");
+            return false;
+        }
+
+        // Now remove the ipconfig.txt file.
+        if (!IP_CONFIG_FILE.delete()) {
+            Log.e(TAG, "Removing ipconfig.txt failed");
+            return false;
+        }
+
+        // Now finally remove network history.txt
+        if (!NETWORK_HISTORY_FILE.delete()) {
+            Log.e(TAG, "Removing networkHistory.txt failed");
+            return false;
+        }
+
+        if (!PPS_FILE.delete()) {
+            Log.e(TAG, "Removing PerProviderSubscription.conf failed");
+            return false;
+        }
+
+        Log.i(TAG, "All legacy stores removed!");
+        return true;
+    }
+
+    /**
+     * Interface used to set a masked value in the provided configuration. The masked value is
+     * retrieved by parsing the wpa_supplicant.conf file.
+     */
+    private interface MaskedWpaSupplicantFieldSetter {
+        void setValue(WifiConfiguration config, String value);
+    }
+
+    /**
+     * Class used to encapsulate all the store data retrieved from the legacy (Pre O) store files.
+     */
+    public static class WifiConfigStoreDataLegacy {
+        private List<WifiConfiguration> mConfigurations;
+        private Set<String> mDeletedEphemeralSSIDs;
+        // private List<HomeSP> mHomeSps;
+
+        WifiConfigStoreDataLegacy(List<WifiConfiguration> configurations,
+                Set<String> deletedEphemeralSSIDs) {
+            mConfigurations = configurations;
+            mDeletedEphemeralSSIDs = deletedEphemeralSSIDs;
+        }
+
+        public List<WifiConfiguration> getConfigurations() {
+            return mConfigurations;
+        }
+
+        public Set<String> getDeletedEphemeralSSIDs() {
+            return mDeletedEphemeralSSIDs;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
index 7d0fb3c..c726f49 100644
--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
@@ -17,33 +17,330 @@
 package com.android.server.wifi;
 
 import android.content.pm.UserInfo;
+import android.net.IpConfiguration;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiScanner;
 import android.os.UserHandle;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
 
 /**
- * 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 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
+     * profiles
      */
     public static boolean isVisibleToAnyProfile(WifiConfiguration config, List<UserInfo> profiles) {
-        if (config.shared) {
-            return true;
-        }
-        final int creatorUserId = UserHandle.getUserId(config.creatorUid);
+        return (config.shared || doesUidBelongToAnyProfile(config.creatorUid, profiles));
+    }
+
+    /**
+     * Check whether a uid belong to a user or any of its managed profiles.
+     *
+     * @param uid      uid of the app.
+     * @param profiles the user IDs of the user itself and all its managed profiles (can be obtained
+     *                 via {@link android.os.UserManager#getProfiles})
+     * @return whether the uid belongs to the user or any of its managed profiles.
+     */
+    public static boolean doesUidBelongToAnyProfile(int uid, List<UserInfo> profiles) {
+        final int userId = UserHandle.getUserId(uid);
         for (UserInfo profile : profiles) {
-            if (profile.id == creatorUserId) {
+            if (profile.id == userId) {
                 return true;
             }
         }
         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));
+    }
+
+    /**
+     * Compare existing and new WifiConfiguration objects after a network update and return if
+     * IP parameters have changed or not.
+     *
+     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
+     * @param newConfig      New WifiConfiguration object corresponding to the network.
+     * @return true if IP parameters have changed, false otherwise.
+     */
+    public static boolean hasIpChanged(WifiConfiguration existingConfig,
+            WifiConfiguration newConfig) {
+        if (existingConfig.getIpAssignment() != newConfig.getIpAssignment()) {
+            return true;
+        }
+        if (newConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
+            return !Objects.equals(existingConfig.getStaticIpConfiguration(),
+                    newConfig.getStaticIpConfiguration());
+        }
+        return false;
+    }
+
+    /**
+     * Compare existing and new WifiConfiguration objects after a network update and return if
+     * proxy parameters have changed or not.
+     *
+     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
+     * @param newConfig      New WifiConfiguration object corresponding to the network.
+     * @return true if proxy parameters have changed, false if no existing config and proxy settings
+     * are NONE, false otherwise.
+     */
+    public static boolean hasProxyChanged(WifiConfiguration existingConfig,
+            WifiConfiguration newConfig) {
+        if (existingConfig == null) {
+            return newConfig.getProxySettings() != IpConfiguration.ProxySettings.NONE;
+        }
+        if (newConfig.getProxySettings() != existingConfig.getProxySettings()) {
+            return true;
+        }
+        return !Objects.equals(existingConfig.getHttpProxy(), newConfig.getHttpProxy());
+    }
+
+    /**
+     * Compare existing and new WifiEnterpriseConfig objects after a network update and return if
+     * credential parameters have changed or not.
+     *
+     * @param existingEnterpriseConfig Existing WifiConfiguration object corresponding to the
+     *                                 network.
+     * @param newEnterpriseConfig      New WifiConfiguration object corresponding to the network.
+     * @return true if credentials have changed, false otherwise.
+     */
+    @VisibleForTesting
+    public static boolean hasEnterpriseConfigChanged(WifiEnterpriseConfig existingEnterpriseConfig,
+            WifiEnterpriseConfig newEnterpriseConfig) {
+        if (existingEnterpriseConfig != null && newEnterpriseConfig != null) {
+            if (existingEnterpriseConfig.getEapMethod() != newEnterpriseConfig.getEapMethod()) {
+                return true;
+            }
+            if (existingEnterpriseConfig.getPhase2Method()
+                    != newEnterpriseConfig.getPhase2Method()) {
+                return true;
+            }
+            X509Certificate[] existingCaCerts = existingEnterpriseConfig.getCaCertificates();
+            X509Certificate[] newCaCerts = newEnterpriseConfig.getCaCertificates();
+            if (!Arrays.equals(existingCaCerts, newCaCerts)) {
+                return true;
+            }
+        } else {
+            // One of the configs may have an enterpriseConfig
+            if (existingEnterpriseConfig != null || newEnterpriseConfig != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Compare existing and new WifiConfiguration objects after a network update and return if
+     * credential parameters have changed or not.
+     *
+     * @param existingConfig Existing WifiConfiguration object corresponding to the network.
+     * @param newConfig      New WifiConfiguration object corresponding to the network.
+     * @return true if credentials have changed, false otherwise.
+     */
+    public static boolean hasCredentialChanged(WifiConfiguration existingConfig,
+            WifiConfiguration newConfig) {
+        if (!Objects.equals(existingConfig.allowedKeyManagement,
+                newConfig.allowedKeyManagement)) {
+            return true;
+        }
+        if (!Objects.equals(existingConfig.allowedProtocols, newConfig.allowedProtocols)) {
+            return true;
+        }
+        if (!Objects.equals(existingConfig.allowedAuthAlgorithms,
+                newConfig.allowedAuthAlgorithms)) {
+            return true;
+        }
+        if (!Objects.equals(existingConfig.allowedPairwiseCiphers,
+                newConfig.allowedPairwiseCiphers)) {
+            return true;
+        }
+        if (!Objects.equals(existingConfig.allowedGroupCiphers,
+                newConfig.allowedGroupCiphers)) {
+            return true;
+        }
+        if (!Objects.equals(existingConfig.preSharedKey, newConfig.preSharedKey)) {
+            return true;
+        }
+        if (!Arrays.equals(existingConfig.wepKeys, newConfig.wepKeys)) {
+            return true;
+        }
+        if (existingConfig.wepTxKeyIndex != newConfig.wepTxKeyIndex) {
+            return true;
+        }
+        if (existingConfig.hiddenSSID != newConfig.hiddenSSID) {
+            return true;
+        }
+        if (hasEnterpriseConfigChanged(existingConfig.enterpriseConfig,
+                newConfig.enterpriseConfig)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if the provided two networks are the same.
+     *
+     * @param config      Configuration corresponding to a network.
+     * @param config1      Configuration corresponding to another network.
+     *
+     * @return true if |config| and |config1| are the same network.
+     *         false otherwise.
+     */
+    public static boolean isSameNetwork(WifiConfiguration config, WifiConfiguration config1) {
+        if (config == null && config1 == null) {
+            return true;
+        }
+        if (config == null || config1 == null) {
+            return false;
+        }
+        if (config.networkId != config1.networkId) {
+            return false;
+        }
+        if (!Objects.equals(config.SSID, config1.SSID)) {
+            return false;
+        }
+        String networkSelectionBSSID = config.getNetworkSelectionStatus()
+                .getNetworkSelectionBSSID();
+        String networkSelectionBSSID1 = config1.getNetworkSelectionStatus()
+                .getNetworkSelectionBSSID();
+        if (!Objects.equals(networkSelectionBSSID, networkSelectionBSSID1)) {
+            return false;
+        }
+        if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Create a PnoNetwork object from the provided WifiConfiguration.
+     *
+     * @param config      Configuration corresponding to the network.
+     * @param newPriority New priority to be assigned to the network.
+     * @return PnoNetwork object corresponding to the network.
+     */
+    public static WifiScanner.PnoSettings.PnoNetwork createPnoNetwork(
+            WifiConfiguration config, int newPriority) {
+        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
+                new WifiScanner.PnoSettings.PnoNetwork(config.SSID);
+        if (config.hiddenSSID) {
+            pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
+        }
+        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND;
+        pnoNetwork.flags |= WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND;
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK;
+        } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
+                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
+            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL;
+        } else {
+            pnoNetwork.authBitField |= WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN;
+        }
+        return pnoNetwork;
+    }
+
+
+    /**
+     * General WifiConfiguration list sorting algorithm:
+     * 1, Place the fully enabled networks first.
+     * 2. Next place all the temporarily disabled networks.
+     * 3. Place the permanently disabled networks last (Permanently disabled networks are removed
+     * before WifiConfigManager uses this comparator today!).
+     *
+     * Among the networks with the same status, sort them in the order determined by the return of
+     * {@link #compareNetworksWithSameStatus(WifiConfiguration, WifiConfiguration)} method
+     * implementation.
+     */
+    public abstract static class WifiConfigurationComparator implements
+            Comparator<WifiConfiguration> {
+        private static final int ENABLED_NETWORK_SCORE = 3;
+        private static final int TEMPORARY_DISABLED_NETWORK_SCORE = 2;
+        private static final int PERMANENTLY_DISABLED_NETWORK_SCORE = 1;
+
+        @Override
+        public int compare(WifiConfiguration a, WifiConfiguration b) {
+            int configAScore = getNetworkStatusScore(a);
+            int configBScore = getNetworkStatusScore(b);
+            if (configAScore == configBScore) {
+                return compareNetworksWithSameStatus(a, b);
+            } else {
+                return Integer.compare(configBScore, configAScore);
+            }
+        }
+
+        // This needs to be implemented by the connected/disconnected PNO list comparator.
+        abstract int compareNetworksWithSameStatus(WifiConfiguration a, WifiConfiguration b);
+
+        /**
+         * Returns an integer representing a score for each configuration. The scores are assigned
+         * based on the status of the configuration. The scores are assigned according to the order:
+         * Fully enabled network > Temporarily disabled network > Permanently disabled network.
+         */
+        private int getNetworkStatusScore(WifiConfiguration config) {
+            if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
+                return ENABLED_NETWORK_SCORE;
+            } else if (config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
+                return TEMPORARY_DISABLED_NETWORK_SCORE;
+            } else {
+                return PERMANENTLY_DISABLED_NETWORK_SCORE;
+            }
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityHelper.java b/service/java/com/android/server/wifi/WifiConnectivityHelper.java
new file mode 100644
index 0000000..4aac311
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiConnectivityHelper.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static android.net.wifi.WifiManager.WIFI_FEATURE_CONTROL_ROAMING;
+
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * This class provides helper functions for Wifi connectivity related modules to
+ * access WifiNative. It starts with firmware roaming. TODO(b/34819513): Move operations
+ * such as connection to network and legacy framework roaming here.
+ *
+ * NOTE: This class is not thread safe and should only be used from the WifiStateMachine thread.
+ */
+public class WifiConnectivityHelper {
+    private static final String TAG = "WifiConnectivityHelper";
+    @VisibleForTesting
+    public static int INVALID_LIST_SIZE = -1;
+    private final WifiNative mWifiNative;
+    private boolean mFirmwareRoamingSupported = false;
+    private int mMaxNumBlacklistBssid = INVALID_LIST_SIZE;
+    private int mMaxNumWhitelistSsid = INVALID_LIST_SIZE;
+
+    WifiConnectivityHelper(WifiNative wifiNative) {
+        mWifiNative = wifiNative;
+    }
+
+    /**
+     * Query firmware if it supports
+     * {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING}. If yes, get the firmware
+     * roaming capabilities. If firmware roaming is supported but we fail to get the roaming
+     * capabilities or the returned capability values are invalid, we fall back to framework
+     * roaming.
+     *
+     * @return true if succeed, false if firmware roaming is supported but fail to get valid
+     * roaming capabilities.
+     */
+    public boolean getFirmwareRoamingInfo() {
+        mFirmwareRoamingSupported = false;
+        mMaxNumBlacklistBssid = INVALID_LIST_SIZE;
+        mMaxNumWhitelistSsid = INVALID_LIST_SIZE;
+
+        int fwFeatureSet = mWifiNative.getSupportedFeatureSet();
+        Log.d(TAG, "Firmware supported feature set: " + Integer.toHexString(fwFeatureSet));
+
+        if ((fwFeatureSet & WIFI_FEATURE_CONTROL_ROAMING) == 0) {
+            Log.d(TAG, "Firmware roaming is not supported");
+            return true;
+        }
+
+        WifiNative.RoamingCapabilities roamingCap = new WifiNative.RoamingCapabilities();
+        if (mWifiNative.getRoamingCapabilities(roamingCap)) {
+            if (roamingCap.maxBlacklistSize < 0 || roamingCap.maxWhitelistSize < 0) {
+                Log.e(TAG, "Invalid firmware roaming capabilities: max num blacklist bssid="
+                        + roamingCap.maxBlacklistSize + " max num whitelist ssid="
+                        + roamingCap.maxWhitelistSize);
+            } else {
+                mFirmwareRoamingSupported = true;
+                mMaxNumBlacklistBssid = roamingCap.maxBlacklistSize;
+                mMaxNumWhitelistSsid = roamingCap.maxWhitelistSize;
+                Log.d(TAG, "Firmware roaming supported with capabilities: max num blacklist bssid="
+                        + mMaxNumBlacklistBssid + " max num whitelist ssid="
+                        + mMaxNumWhitelistSsid);
+                return true;
+            }
+        } else {
+            Log.e(TAG, "Failed to get firmware roaming capabilities");
+        }
+
+        return false;
+    }
+
+    /**
+     * Return if firmware roaming is supported.
+     */
+    public boolean isFirmwareRoamingSupported() {
+        return mFirmwareRoamingSupported;
+    }
+
+    /**
+     * Get the maximum size of BSSID blacklist firmware supports.
+     *
+     * @return INVALID_LIST_SIZE if firmware roaming is not supported, or
+     * maximum size of the BSSID blacklist firmware supports.
+     */
+    public int getMaxNumBlacklistBssid() {
+        if (mFirmwareRoamingSupported) {
+            return mMaxNumBlacklistBssid;
+        } else {
+            Log.e(TAG, "getMaxNumBlacklistBssid: Firmware roaming is not supported");
+            return INVALID_LIST_SIZE;
+        }
+    }
+
+    /**
+     * Get the maximum size of SSID whitelist firmware supports.
+     *
+     * @return INVALID_LIST_SIZE if firmware roaming is not supported, or
+     * maximum size of the SSID whitelist firmware supports.
+     */
+    public int getMaxNumWhitelistSsid() {
+        if (mFirmwareRoamingSupported) {
+            return mMaxNumWhitelistSsid;
+        } else {
+            Log.e(TAG, "getMaxNumWhitelistSsid: Firmware roaming is not supported");
+            return INVALID_LIST_SIZE;
+        }
+    }
+
+    /**
+     * Write firmware roaming configuration to firmware.
+     *
+     * @param blacklistBssids BSSIDs to be blacklisted
+     * @param whitelistSsids  SSIDs to be whitelisted
+     * @return true if succeeded, false otherwise.
+     */
+    public boolean setFirmwareRoamingConfiguration(ArrayList<String> blacklistBssids,
+            ArrayList<String> whitelistSsids) {
+        if (!mFirmwareRoamingSupported) {
+            Log.e(TAG, "Firmware roaming is not supported");
+            return false;
+        }
+
+        if (blacklistBssids == null || whitelistSsids == null) {
+            Log.e(TAG, "Invalid firmware roaming configuration settings");
+            return false;
+        }
+
+        int blacklistSize = blacklistBssids.size();
+        int whitelistSize = whitelistSsids.size();
+
+        if (blacklistSize > mMaxNumBlacklistBssid || whitelistSize > mMaxNumWhitelistSsid) {
+            Log.e(TAG, "Invalid BSSID blacklist size " + blacklistSize + " SSID whitelist size "
+                    + whitelistSize + ". Max blacklist size: " + mMaxNumBlacklistBssid
+                    + ", max whitelist size: " + mMaxNumWhitelistSsid);
+            return false;
+        }
+
+        WifiNative.RoamingConfig roamConfig = new WifiNative.RoamingConfig();
+        roamConfig.blacklistBssids = blacklistBssids;
+        roamConfig.whitelistSsids = whitelistSsids;
+
+        return mWifiNative.configureRoaming(roamConfig);
+    }
+
+    /**
+     * Remove the request |networkId| from supplicant if it's the current network,
+     * if the current configured network matches |networkId|.
+     *
+     * @param networkId network id of the network to be removed from supplicant.
+     */
+    public void removeNetworkIfCurrent(int networkId) {
+        mWifiNative.removeNetworkIfCurrent(networkId);
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 49a2842..f45d17b 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -18,14 +18,12 @@
 
 import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE;
 
-import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiScanner.PnoSettings;
 import android.net.wifi.WifiScanner.ScanSettings;
@@ -36,15 +34,18 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wifi.util.ScanDetailUtil;
+import com.android.server.wifi.hotspot2.PasspointNetworkEvaluator;
+import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -52,7 +53,8 @@
  *
  * When the screen is turned on or off, WiFi is connected or disconnected,
  * or on-demand, a scan is initiatiated and the scan results are passed
- * to QNS for it to make a recommendation on which network to connect to.
+ * to WifiNetworkSelector for it to make a recommendation on which network
+ * to connect to.
  */
 public class WifiConnectivityManager {
     public static final String WATCHDOG_TIMER_TAG =
@@ -64,7 +66,6 @@
     public static final String RESTART_CONNECTIVITY_SCAN_TIMER_TAG =
             "WifiConnectivityManager Restart Scan";
 
-    private static final String TAG = "WifiConnectivityManager";
     private static final long RESET_TIME_STAMP = Long.MIN_VALUE;
     // Constants to indicate whether a scan should start immediately or
     // it should comply to the minimum scan interval rule.
@@ -85,8 +86,8 @@
     // PNO scan interval in milli-seconds. This is the scan
     // performed when screen is off and connected.
     private static final int CONNECTED_PNO_SCAN_INTERVAL_MS = 160 * 1000; // 160 seconds
-    // When a network is found by PNO scan but gets rejected by QNS due to its
-    // low RSSI value, scan will be reschduled in an exponential back off manner.
+    // When a network is found by PNO scan but gets rejected by Wifi Network Selector due
+    // to its low RSSI value, scan will be reschduled in an exponential back off manner.
     private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds
     private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds
     // Maximum number of retries when starting a scan failed
@@ -106,6 +107,10 @@
     public static final int MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS = 4 * 60 * 1000; // 4 mins
     // Max number of connection attempts in the above time interval.
     public static final int MAX_CONNECTION_ATTEMPTS_RATE = 6;
+    // Packet tx/rx rates to determine if we want to do partial vs full scans.
+    // TODO(b/31180330): Make these device configs.
+    public static final int MAX_TX_PACKET_FOR_FULL_SCANS = 8;
+    public static final int MAX_RX_PACKET_FOR_FULL_SCANS = 16;
 
     // WifiStateMachine has a bunch of states. From the
     // WifiConnectivityManager's perspective it only cares
@@ -116,24 +121,26 @@
     public static final int WIFI_STATE_DISCONNECTED = 2;
     public static final int WIFI_STATE_TRANSITIONING = 3;
 
-    // Due to b/28020168, timer based single scan will be scheduled
-    // to provide periodic scan in an exponential backoff fashion.
-    private static final boolean ENABLE_BACKGROUND_SCAN = false;
-    // Flag to turn on connected PNO, when needed
-    private static final boolean ENABLE_CONNECTED_PNO_SCAN = false;
+    // Saved network evaluator priority
+    private static final int SAVED_NETWORK_EVALUATOR_PRIORITY = 1;
+    private static final int PASSPOINT_NETWORK_EVALUATOR_PRIORITY = 2;
+    private static final int SCORED_NETWORK_EVALUATOR_PRIORITY = 3;
+
+    // Log tag for this class
+    private static final String TAG = "WifiConnectivityManager";
 
     private final WifiStateMachine mStateMachine;
     private final WifiScanner mScanner;
     private final WifiConfigManager mConfigManager;
     private final WifiInfo mWifiInfo;
-    private final WifiQualifiedNetworkSelector mQualifiedNetworkSelector;
+    private final WifiConnectivityHelper mConnectivityHelper;
+    private final WifiNetworkSelector mNetworkSelector;
     private final WifiLastResortWatchdog mWifiLastResortWatchdog;
     private final WifiMetrics mWifiMetrics;
     private final AlarmManager mAlarmManager;
     private final Handler mEventHandler;
     private final Clock mClock;
-    private final LocalLog mLocalLog =
-            new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 128 : 256);
+    private final LocalLog mLocalLog;
     private final LinkedList<Long> mConnectionAttemptTimeStamps;
 
     private boolean mDbg = false;
@@ -150,6 +157,8 @@
     private long mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP;
     private boolean mPnoScanStarted = false;
     private boolean mPeriodicScanTimerSet = false;
+    // Device configs
+    private boolean mEnableAutoJoinWhenAssociated;
     private boolean mWaitForFullBandScanResults = false;
 
     // PNO settings
@@ -161,6 +170,24 @@
     private int mSecureBonus;
     private int mBand5GHzBonus;
 
+    // BSSID blacklist
+    @VisibleForTesting
+    public static final int BSSID_BLACKLIST_THRESHOLD = 3;
+    @VisibleForTesting
+    public static final int BSSID_BLACKLIST_EXPIRE_TIME_MS = 5 * 60 * 1000;
+    private static class BssidBlacklistStatus {
+        // Number of times this BSSID has been rejected for association.
+        public int counter;
+        public boolean isBlacklisted;
+        public long blacklistedTimeStamp = RESET_TIME_STAMP;
+    }
+    private Map<String, BssidBlacklistStatus> mBssidBlacklist =
+            new HashMap<>();
+
+    // Association failure reason codes
+    @VisibleForTesting
+    public static final int REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA = 17;
+
     // A helper to log debugging information in the local log buffer, which can
     // be retrieved in bugreport.
     private void localLog(String log) {
@@ -214,22 +241,31 @@
      * Executes selection of potential network candidates, initiation of connection attempt to that
      * network.
      *
-     * @return true - if a candidate is selected by QNS
-     *         false - if no candidate is selected by QNS
+     * @return true - if a candidate is selected by WifiNetworkSelector
+     *         false - if no candidate is selected by WifiNetworkSelector
      */
     private boolean handleScanResults(List<ScanDetail> scanDetails, String listenerName) {
-        localLog(listenerName + " onResults: start QNS");
+        // Check if any blacklisted BSSIDs can be freed.
+        refreshBssidBlacklist();
+
+        if (mStateMachine.isLinkDebouncing() || mStateMachine.isSupplicantTransientState()) {
+            localLog(listenerName + " onResults: No network selection because linkDebouncing is "
+                    + mStateMachine.isLinkDebouncing() + " and supplicantTransient is "
+                    + mStateMachine.isSupplicantTransientState());
+            return false;
+        }
+
+        localLog(listenerName + " onResults: start network selection");
+
         WifiConfiguration candidate =
-                mQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                mUntrustedConnectionAllowed, scanDetails,
-                mStateMachine.isLinkDebouncing(), mStateMachine.isConnected(),
-                mStateMachine.isDisconnected(),
-                mStateMachine.isSupplicantTransientState());
+                mNetworkSelector.selectNetwork(scanDetails, buildBssidBlacklist(), mWifiInfo,
+                mStateMachine.isConnected(), mStateMachine.isDisconnected(),
+                mUntrustedConnectionAllowed);
         mWifiLastResortWatchdog.updateAvailableNetworks(
-                mQualifiedNetworkSelector.getFilteredScanDetails());
+                mNetworkSelector.getFilteredScanDetails());
         mWifiMetrics.countScanResults(scanDetails);
         if (candidate != null) {
-            localLog(listenerName + ": QNS candidate-" + candidate.SSID);
+            localLog(listenerName + ":  WNS candidate-" + candidate.SSID);
             connectToNetwork(candidate);
             return true;
         } else {
@@ -237,63 +273,6 @@
         }
     }
 
-    // Periodic scan results listener. A periodic scan is initiated when
-    // screen is on.
-    private class PeriodicScanListener implements WifiScanner.ScanListener {
-        private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
-
-        public void clearScanDetails() {
-            mScanDetails.clear();
-        }
-
-        @Override
-        public void onSuccess() {
-            localLog("PeriodicScanListener onSuccess");
-        }
-
-        @Override
-        public void onFailure(int reason, String description) {
-            Log.e(TAG, "PeriodicScanListener onFailure:"
-                          + " reason: " + reason
-                          + " description: " + description);
-
-            // reschedule the scan
-            if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
-                scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
-            } else {
-                mScanRestartCount = 0;
-                Log.e(TAG, "Failed to successfully start periodic scan for "
-                          + MAX_SCAN_RESTART_ALLOWED + " times");
-            }
-        }
-
-        @Override
-        public void onPeriodChanged(int periodInMs) {
-            localLog("PeriodicScanListener onPeriodChanged: "
-                          + "actual scan period " + periodInMs + "ms");
-        }
-
-        @Override
-        public void onResults(WifiScanner.ScanData[] results) {
-            handleScanResults(mScanDetails, "PeriodicScanListener");
-            clearScanDetails();
-            mScanRestartCount = 0;
-        }
-
-        @Override
-        public void onFullResult(ScanResult fullScanResult) {
-            if (mDbg) {
-                localLog("PeriodicScanListener onFullResult: "
-                            + fullScanResult.SSID + " capabilities "
-                            + fullScanResult.capabilities);
-            }
-
-            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
-        }
-    }
-
-    private final PeriodicScanListener mPeriodicScanListener = new PeriodicScanListener();
-
     // All single scan results listener.
     //
     // Note: This is the listener for all the available single scan results,
@@ -308,14 +287,12 @@
 
         @Override
         public void onSuccess() {
-            localLog("registerScanListener onSuccess");
         }
 
         @Override
         public void onFailure(int reason, String description) {
-            Log.e(TAG, "registerScanListener onFailure:"
-                          + " reason: " + reason
-                          + " description: " + description);
+            localLog("registerScanListener onFailure:"
+                      + " reason: " + reason + " description: " + description);
         }
 
         @Override
@@ -364,19 +341,18 @@
             }
 
             if (mDbg) {
-                localLog("AllSingleScanListener onFullResult: "
-                            + fullScanResult.SSID + " capabilities "
-                            + fullScanResult.capabilities);
+                localLog("AllSingleScanListener onFullResult: " + fullScanResult.SSID
+                        + " capabilities " + fullScanResult.capabilities);
             }
 
-            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
+            mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult));
         }
     }
 
     private final AllSingleScanListener mAllSingleScanListener = new AllSingleScanListener();
 
     // Single scan results listener. A single scan is initiated when
-    // Disconnected/ConnectedPNO scan found a valid network and woke up
+    // DisconnectedPNO scan found a valid network and woke up
     // the system, or by the watchdog timer, or to form the timer based
     // periodic scan.
     //
@@ -391,29 +367,27 @@
 
         @Override
         public void onSuccess() {
-            localLog("SingleScanListener onSuccess");
         }
 
         @Override
         public void onFailure(int reason, String description) {
-            Log.e(TAG, "SingleScanListener onFailure:"
-                          + " reason: " + reason
-                          + " description: " + description);
+            localLog("SingleScanListener onFailure:"
+                    + " reason: " + reason + " description: " + description);
 
             // reschedule the scan
             if (mSingleScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
                 scheduleDelayedSingleScan(mIsFullBandScan);
             } else {
                 mSingleScanRestartCount = 0;
-                Log.e(TAG, "Failed to successfully start single scan for "
-                          + MAX_SCAN_RESTART_ALLOWED + " times");
+                localLog("Failed to successfully start single scan for "
+                        + MAX_SCAN_RESTART_ALLOWED + " times");
             }
         }
 
         @Override
         public void onPeriodChanged(int periodInMs) {
             localLog("SingleScanListener onPeriodChanged: "
-                          + "actual scan period " + periodInMs + "ms");
+                    + "actual scan period " + periodInMs + "ms");
         }
 
         @Override
@@ -425,9 +399,6 @@
         }
     }
 
-    // re-enable this when b/27695292 is fixed
-    // private final SingleScanListener mSingleScanListener = new SingleScanListener();
-
     // PNO scan results listener for both disconected and connected PNO scanning.
     // A PNO scan is initiated when screen is off.
     private class PnoScanListener implements WifiScanner.PnoScanListener {
@@ -440,7 +411,7 @@
         }
 
         // Reset to the start value when either a non-PNO scan is started or
-        // QNS selects a candidate from the PNO scan results.
+        // WifiNetworkSelector selects a candidate from the PNO scan results.
         public void resetLowRssiNetworkRetryDelay() {
             mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_START_DELAY_MS;
         }
@@ -452,33 +423,31 @@
 
         @Override
         public void onSuccess() {
-            localLog("PnoScanListener onSuccess");
         }
 
         @Override
         public void onFailure(int reason, String description) {
-            Log.e(TAG, "PnoScanListener onFailure:"
-                          + " reason: " + reason
-                          + " description: " + description);
+            localLog("PnoScanListener onFailure:"
+                    + " reason: " + reason + " description: " + description);
 
             // reschedule the scan
             if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
                 scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
             } else {
                 mScanRestartCount = 0;
-                Log.e(TAG, "Failed to successfully start PNO scan for "
-                          + MAX_SCAN_RESTART_ALLOWED + " times");
+                localLog("Failed to successfully start PNO scan for "
+                        + MAX_SCAN_RESTART_ALLOWED + " times");
             }
         }
 
         @Override
         public void onPeriodChanged(int periodInMs) {
             localLog("PnoScanListener onPeriodChanged: "
-                          + "actual scan period " + periodInMs + "ms");
+                    + "actual scan period " + periodInMs + "ms");
         }
 
         // Currently the PNO scan results doesn't include IE,
-        // which contains information required by QNS. Ignore them
+        // which contains information required by WifiNetworkSelector. Ignore them
         // for now.
         @Override
         public void onResults(WifiScanner.ScanData[] results) {
@@ -490,10 +459,8 @@
 
         @Override
         public void onPnoNetworkFound(ScanResult[] results) {
-            localLog("PnoScanListener: onPnoNetworkFound: results len = " + results.length);
-
             for (ScanResult result: results) {
-                mScanDetails.add(ScanDetailUtil.toScanDetail(result));
+                mScanDetails.add(ScanResultUtil.toScanDetail(result));
             }
 
             boolean wasConnectAttempted;
@@ -502,7 +469,7 @@
             mScanRestartCount = 0;
 
             if (!wasConnectAttempted) {
-                // The scan results were rejected by QNS due to low RSSI values
+                // The scan results were rejected by WifiNetworkSelector due to low RSSI values
                 if (mLowRssiNetworkRetryDelay > LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS) {
                     mLowRssiNetworkRetryDelay = LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS;
                 }
@@ -518,50 +485,122 @@
 
     private final PnoScanListener mPnoScanListener = new PnoScanListener();
 
+    private class OnSavedNetworkUpdateListener implements
+            WifiConfigManager.OnSavedNetworkUpdateListener {
+        @Override
+        public void onSavedNetworkAdded(int networkId) {
+            updatePnoScan();
+        }
+        @Override
+        public void onSavedNetworkEnabled(int networkId) {
+            updatePnoScan();
+        }
+        @Override
+        public void onSavedNetworkRemoved(int networkId) {
+            updatePnoScan();
+        }
+        @Override
+        public void onSavedNetworkUpdated(int networkId) {
+            updatePnoScan();
+        }
+        @Override
+        public void onSavedNetworkTemporarilyDisabled(int networkId) {
+            mConnectivityHelper.removeNetworkIfCurrent(networkId);
+        }
+        @Override
+        public void onSavedNetworkPermanentlyDisabled(int networkId) {
+            mConnectivityHelper.removeNetworkIfCurrent(networkId);
+            updatePnoScan();
+        }
+        private void updatePnoScan() {
+            // Update the PNO scan network list when screen is off. Here we
+            // rely on startConnectivityScan() to perform all the checks and clean up.
+            if (!mScreenOn) {
+                localLog("Saved networks updated");
+                startConnectivityScan(false);
+            }
+        }
+    }
+
     /**
      * WifiConnectivityManager constructor
      */
-    public WifiConnectivityManager(Context context, WifiStateMachine stateMachine,
-                WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo,
-                WifiQualifiedNetworkSelector qualifiedNetworkSelector,
-                WifiInjector wifiInjector, Looper looper, boolean enable) {
+    WifiConnectivityManager(Context context, WifiStateMachine stateMachine,
+            WifiScanner scanner, WifiConfigManager configManager, WifiInfo wifiInfo,
+            WifiNetworkSelector networkSelector, WifiConnectivityHelper connectivityHelper,
+            WifiLastResortWatchdog wifiLastResortWatchdog, WifiMetrics wifiMetrics,
+            Looper looper, Clock clock, LocalLog localLog, boolean enable,
+            FrameworkFacade frameworkFacade,
+            SavedNetworkEvaluator savedNetworkEvaluator,
+            ScoredNetworkEvaluator scoredNetworkEvaluator,
+            PasspointNetworkEvaluator passpointNetworkEvaluator) {
         mStateMachine = stateMachine;
         mScanner = scanner;
         mConfigManager = configManager;
         mWifiInfo = wifiInfo;
-        mQualifiedNetworkSelector = qualifiedNetworkSelector;
-        mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog();
-        mWifiMetrics = wifiInjector.getWifiMetrics();
+        mNetworkSelector = networkSelector;
+        mConnectivityHelper = connectivityHelper;
+        mLocalLog = localLog;
+        mWifiLastResortWatchdog = wifiLastResortWatchdog;
+        mWifiMetrics = wifiMetrics;
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mEventHandler = new Handler(looper);
-        mClock = wifiInjector.getClock();
+        mClock = clock;
         mConnectionAttemptTimeStamps = new LinkedList<>();
 
-        mMin5GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_5G_ACCEPT_RSSI;
-        mMin24GHzRssi = WifiQualifiedNetworkSelector.MINIMUM_2G_ACCEPT_RSSI;
-        mBand5GHzBonus = WifiQualifiedNetworkSelector.BAND_AWARD_5GHz;
-        mCurrentConnectionBonus = mConfigManager.mCurrentNetworkBoost.get();
+        mMin5GHzRssi = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mMin24GHzRssi = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mBand5GHzBonus = context.getResources().getInteger(
+                R.integer.config_wifi_framework_5GHz_preference_boost_factor);
+        mCurrentConnectionBonus = context.getResources().getInteger(
+                R.integer.config_wifi_framework_current_network_boost);
         mSameNetworkBonus = context.getResources().getInteger(
-                                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
+                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
         mSecureBonus = context.getResources().getInteger(
-                            R.integer.config_wifi_framework_SECURITY_AWARD);
-        mInitialScoreMax = (mConfigManager.mThresholdSaturatedRssi24.get()
-                            + WifiQualifiedNetworkSelector.RSSI_SCORE_OFFSET)
-                            * WifiQualifiedNetworkSelector.RSSI_SCORE_SLOPE;
+                R.integer.config_wifi_framework_SECURITY_AWARD);
+        int thresholdSaturatedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
+        mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
+                R.bool.config_wifi_framework_enable_associated_network_selection);
+        mInitialScoreMax = (context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz)
+                    + context.getResources().getInteger(
+                            R.integer.config_wifi_framework_RSSI_SCORE_OFFSET))
+                * context.getResources().getInteger(
+                        R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
 
-        Log.i(TAG, "PNO settings:" + " min5GHzRssi " + mMin5GHzRssi
-                    + " min24GHzRssi " + mMin24GHzRssi
-                    + " currentConnectionBonus " + mCurrentConnectionBonus
-                    + " sameNetworkBonus " + mSameNetworkBonus
-                    + " secureNetworkBonus " + mSecureBonus
-                    + " initialScoreMax " + mInitialScoreMax);
+        localLog("PNO settings:" + " min5GHzRssi " + mMin5GHzRssi
+                + " min24GHzRssi " + mMin24GHzRssi
+                + " currentConnectionBonus " + mCurrentConnectionBonus
+                + " sameNetworkBonus " + mSameNetworkBonus
+                + " secureNetworkBonus " + mSecureBonus
+                + " initialScoreMax " + mInitialScoreMax);
+
+        boolean hs2Enabled = context.getResources().getBoolean(
+                R.bool.config_wifi_hotspot2_enabled);
+        localLog("Passpoint is: " + (hs2Enabled ? "enabled" : "disabled"));
+
+        // Register the network evaluators
+        mNetworkSelector.registerNetworkEvaluator(savedNetworkEvaluator,
+                SAVED_NETWORK_EVALUATOR_PRIORITY);
+        if (hs2Enabled) {
+            mNetworkSelector.registerNetworkEvaluator(passpointNetworkEvaluator,
+                    PASSPOINT_NETWORK_EVALUATOR_PRIORITY);
+        }
+        mNetworkSelector.registerNetworkEvaluator(scoredNetworkEvaluator,
+                SCORED_NETWORK_EVALUATOR_PRIORITY);
 
         // Register for all single scan results
         mScanner.registerScanListener(mAllSingleScanListener);
 
+        // Listen to WifiConfigManager network update events
+        mConfigManager.setOnSavedNetworkUpdateListener(new OnSavedNetworkUpdateListener());
+
         mWifiConnectivityManagerEnabled = enable;
 
-        Log.i(TAG, "ConnectivityScanManager initialized and "
+        localLog("ConnectivityScanManager initialized and "
                 + (enable ? "enabled" : "disabled"));
     }
 
@@ -606,12 +645,12 @@
      * Attempt to connect to a network candidate.
      *
      * Based on the currently connected network, this menthod determines whether we should
-     * connect or roam to the network candidate recommended by QNS.
+     * connect or roam to the network candidate recommended by WifiNetworkSelector.
      */
     private void connectToNetwork(WifiConfiguration candidate) {
         ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
         if (scanResultCandidate == null) {
-            Log.e(TAG, "connectToNetwork: bad candidate - "  + candidate
+            localLog("connectToNetwork: bad candidate - "  + candidate
                     + " scanResult: " + scanResultCandidate);
             return;
         }
@@ -621,8 +660,8 @@
 
         // Check if we are already connected or in the process of connecting to the target
         // BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just
-        // in case the firmware automatically roamed to a BSSID different from what QNS
-        // selected.
+        // in case the firmware automatically roamed to a BSSID different from what
+        // WifiNetworkSelector selected.
         if (targetBssid != null
                 && (targetBssid.equals(mLastConnectionAttemptBssid)
                     || targetBssid.equals(mWifiInfo.getBSSID()))
@@ -632,7 +671,15 @@
             return;
         }
 
-        Long elapsedTimeMillis = mClock.elapsedRealtime();
+        if (candidate.BSSID != null
+                && !candidate.BSSID.equals(WifiStateMachine.SUPPLICANT_BSSID_ANY)
+                && !candidate.BSSID.equals(targetBssid)) {
+            localLog("connecToNetwork: target BSSID " + targetBssid + " does not match the "
+                    + "config specified BSSID " + candidate.BSSID + ". Drop it!");
+            return;
+        }
+
+        Long elapsedTimeMillis = mClock.getElapsedSinceBootMillis();
         if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
             localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
             mTotalConnectivityAttemptsRateLimited++;
@@ -643,20 +690,39 @@
         mLastConnectionAttemptBssid = targetBssid;
 
         WifiConfiguration currentConnectedNetwork = mConfigManager
-                .getWifiConfiguration(mWifiInfo.getNetworkId());
+                .getConfiguredNetwork(mWifiInfo.getNetworkId());
         String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" :
                 (mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID());
 
         if (currentConnectedNetwork != null
                 && (currentConnectedNetwork.networkId == candidate.networkId
-                || currentConnectedNetwork.isLinked(candidate))) {
-            localLog("connectToNetwork: Roaming from " + currentAssociationId + " to "
-                        + targetAssociationId);
-            mStateMachine.autoRoamToNetwork(candidate.networkId, scanResultCandidate);
+                //TODO(b/36788683): re-enable linked configuration check
+                /* || currentConnectedNetwork.isLinked(candidate) */)) {
+            // Framework initiates roaming only if firmware doesn't support
+            // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING}.
+            if (mConnectivityHelper.isFirmwareRoamingSupported()) {
+                // Keep this logging here for now to validate the firmware roaming behavior.
+                localLog("connectToNetwork: Roaming candidate - " + targetAssociationId + "."
+                        + " The actual roaming target is up to the firmware.");
+            } else {
+                localLog("connectToNetwork: Roaming to " + targetAssociationId + " from "
+                        + currentAssociationId);
+                mStateMachine.startRoamToNetwork(candidate.networkId, scanResultCandidate);
+            }
         } else {
-            localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to "
-                        + targetAssociationId);
-            mStateMachine.autoConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID);
+            // Framework specifies the connection target BSSID if firmware doesn't support
+            // {@link android.net.wifi.WifiManager#WIFI_FEATURE_CONTROL_ROAMING} or the
+            // candidate configuration contains a specified BSSID.
+            if (mConnectivityHelper.isFirmwareRoamingSupported() && (candidate.BSSID == null
+                      || candidate.BSSID.equals(WifiStateMachine.SUPPLICANT_BSSID_ANY))) {
+                targetBssid = WifiStateMachine.SUPPLICANT_BSSID_ANY;
+                localLog("connectToNetwork: Connect to " + candidate.SSID + ":" + targetBssid
+                        + " from " + currentAssociationId);
+            } else {
+                localLog("connectToNetwork: Connect to " + targetAssociationId + " from "
+                        + currentAssociationId);
+            }
+            mStateMachine.startConnectToNetwork(candidate.networkId, targetBssid);
         }
     }
 
@@ -667,14 +733,7 @@
 
     private int getScanBand(boolean isFullBandScan) {
         if (isFullBandScan) {
-            int freqBand = mStateMachine.getFrequencyBand();
-            if (freqBand == WifiManager.WIFI_FREQUENCY_BAND_5GHZ) {
-                return WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS;
-            } else if (freqBand == WifiManager.WIFI_FREQUENCY_BAND_2GHZ) {
-                return WifiScanner.WIFI_BAND_24_GHZ;
-            } else {
-                return WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
-            }
+            return WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
         } else {
             // Use channel list instead.
             return WifiScanner.WIFI_BAND_UNSPECIFIED;
@@ -690,7 +749,9 @@
             return false;
         }
 
-        HashSet<Integer> freqs = mConfigManager.makeChannelList(config, CHANNEL_LIST_AGE_MS);
+        Set<Integer> freqs =
+                mConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        config.networkId, CHANNEL_LIST_AGE_MS, mWifiInfo.getFrequency());
 
         if (freqs != null && freqs.size() != 0) {
             int index = 0;
@@ -707,13 +768,11 @@
 
     // Watchdog timer handler
     private void watchdogHandler() {
-        localLog("watchdogHandler");
-
         // Schedule the next timer and start a single scan if we are in disconnected state.
         // Otherwise, the watchdog timer will be scheduled when entering disconnected
         // state.
         if (mWifiState == WIFI_STATE_DISCONNECTED) {
-            Log.i(TAG, "start a single scan from watchdogHandler");
+            localLog("start a single scan from watchdogHandler");
 
             scheduleWatchdogTimer();
             startSingleScan(true);
@@ -722,7 +781,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;
@@ -738,13 +797,9 @@
 
         // If the WiFi traffic is heavy, only partial scan is initiated.
         if (mWifiState == WIFI_STATE_CONNECTED
-                && (mWifiInfo.txSuccessRate
-                            > mConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS
-                    || mWifiInfo.rxSuccessRate
-                            > mConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS)) {
-            localLog("No full band scan due to heavy traffic, txSuccessRate="
-                        + mWifiInfo.txSuccessRate + " rxSuccessRate="
-                        + mWifiInfo.rxSuccessRate);
+                && (mWifiInfo.txSuccessRate > MAX_TX_PACKET_FOR_FULL_SCANS
+                    || mWifiInfo.rxSuccessRate > MAX_RX_PACKET_FOR_FULL_SCANS)) {
+            localLog("No full band scan due to ongoing traffic");
             isFullBandScan = false;
         }
 
@@ -794,19 +849,11 @@
                             | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
         settings.numBssidsPerScan = 0;
 
-        //Retrieve the list of hidden networkId's to scan for.
-        Set<Integer> hiddenNetworkIds = mConfigManager.getHiddenConfiguredNetworkIds();
-        if (hiddenNetworkIds != null && hiddenNetworkIds.size() > 0) {
-            int i = 0;
-            settings.hiddenNetworkIds = new int[hiddenNetworkIds.size()];
-            for (Integer netId : hiddenNetworkIds) {
-                settings.hiddenNetworkIds[i++] = netId;
-            }
-        }
+        List<ScanSettings.HiddenNetwork> hiddenNetworkList =
+                mConfigManager.retrieveHiddenNetworkList();
+        settings.hiddenNetworks =
+                hiddenNetworkList.toArray(new ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
 
-        // re-enable this when b/27695292 is fixed
-        // mSingleScanListener.clearScanDetails();
-        // mScanner.startScan(settings, mSingleScanListener, WIFI_WORK_SOURCE);
         SingleScanListener singleScanListener =
                 new SingleScanListener(isFullBandScan);
         mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
@@ -817,38 +864,26 @@
         mPnoScanListener.resetLowRssiNetworkRetryDelay();
 
         // No connectivity scan if auto roaming is disabled.
-        if (mWifiState == WIFI_STATE_CONNECTED
-                && !mConfigManager.getEnableAutoJoinWhenAssociated()) {
+        if (mWifiState == WIFI_STATE_CONNECTED && !mEnableAutoJoinWhenAssociated) {
             return;
         }
 
         // Due to b/28020168, timer based single scan will be scheduled
         // to provide periodic scan in an exponential backoff fashion.
-        if (!ENABLE_BACKGROUND_SCAN) {
-            if (scanImmediately) {
-                resetLastPeriodicSingleScanTimeStamp();
-            }
-            mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS;
-            startPeriodicSingleScan();
-        } else {
-            ScanSettings settings = new ScanSettings();
-            settings.band = getScanBand();
-            settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
-                                | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
-            settings.numBssidsPerScan = 0;
-            settings.periodInMs = PERIODIC_SCAN_INTERVAL_MS;
-
-            mPeriodicScanListener.clearScanDetails();
-            mScanner.startBackgroundScan(settings, mPeriodicScanListener, WIFI_WORK_SOURCE);
+        if (scanImmediately) {
+            resetLastPeriodicSingleScanTimeStamp();
         }
+        mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS;
+        startPeriodicSingleScan();
     }
 
     // Start a DisconnectedPNO scan when screen is off and Wifi is disconnected
     private void startDisconnectedPnoScan() {
+        // TODO(b/29503772): Need to change this interface.
+
         // Initialize PNO settings
         PnoSettings pnoSettings = new PnoSettings();
-        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList =
-                mConfigManager.retrieveDisconnectedPnoNetworkList();
+        List<PnoSettings.PnoNetwork> pnoNetworkList = mConfigManager.retrievePnoNetworkList();
         int listSize = pnoNetworkList.size();
 
         if (listSize == 0) {
@@ -873,8 +908,6 @@
         scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH;
         scanSettings.numBssidsPerScan = 0;
         scanSettings.periodInMs = DISCONNECTED_PNO_SCAN_INTERVAL_MS;
-        // TODO: enable exponential back off scan later to further save energy
-        // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs;
 
         mPnoScanListener.clearScanDetails();
 
@@ -882,51 +915,7 @@
         mPnoScanStarted = true;
     }
 
-    // Start a ConnectedPNO scan when screen is off and Wifi is connected
-    private void startConnectedPnoScan() {
-        // Disable ConnectedPNO for now due to b/28020168
-        if (!ENABLE_CONNECTED_PNO_SCAN) {
-            return;
-        }
-
-        // Initialize PNO settings
-        PnoSettings pnoSettings = new PnoSettings();
-        ArrayList<PnoSettings.PnoNetwork> pnoNetworkList =
-                mConfigManager.retrieveConnectedPnoNetworkList();
-        int listSize = pnoNetworkList.size();
-
-        if (listSize == 0) {
-            // No saved network
-            localLog("No saved network for starting connected PNO.");
-            return;
-        }
-
-        pnoSettings.networkList = new PnoSettings.PnoNetwork[listSize];
-        pnoSettings.networkList = pnoNetworkList.toArray(pnoSettings.networkList);
-        pnoSettings.min5GHzRssi = mMin5GHzRssi;
-        pnoSettings.min24GHzRssi = mMin24GHzRssi;
-        pnoSettings.initialScoreMax = mInitialScoreMax;
-        pnoSettings.currentConnectionBonus = mCurrentConnectionBonus;
-        pnoSettings.sameNetworkBonus = mSameNetworkBonus;
-        pnoSettings.secureBonus = mSecureBonus;
-        pnoSettings.band5GHzBonus = mBand5GHzBonus;
-
-        // Initialize scan settings
-        ScanSettings scanSettings = new ScanSettings();
-        scanSettings.band = getScanBand();
-        scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH;
-        scanSettings.numBssidsPerScan = 0;
-        scanSettings.periodInMs = CONNECTED_PNO_SCAN_INTERVAL_MS;
-        // TODO: enable exponential back off scan later to further save energy
-        // scanSettings.maxPeriodInMs = 8 * scanSettings.periodInMs;
-
-        mPnoScanListener.clearScanDetails();
-
-        mScanner.startConnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener);
-        mPnoScanStarted = true;
-    }
-
-    // Stop a PNO scan. This includes both DisconnectedPNO and ConnectedPNO scans.
+    // Stop PNO scan.
     private void stopPnoScan() {
         if (mPnoScanStarted) {
             mScanner.stopPnoScan(mPnoScanListener);
@@ -937,10 +926,10 @@
 
     // Set up watchdog timer
     private void scheduleWatchdogTimer() {
-        Log.i(TAG, "scheduleWatchdogTimer");
+        localLog("scheduleWatchdogTimer");
 
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + WATCHDOG_INTERVAL_MS,
+                            mClock.getElapsedSinceBootMillis() + WATCHDOG_INTERVAL_MS,
                             WATCHDOG_TIMER_TAG,
                             mWatchdogListener, mEventHandler);
     }
@@ -948,7 +937,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;
@@ -969,7 +958,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);
     }
@@ -979,7 +968,7 @@
         localLog("scheduleDelayedConnectivityScan");
 
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.elapsedRealtime() + msFromNow,
+                            mClock.getElapsedSinceBootMillis() + msFromNow,
                             RESTART_CONNECTIVITY_SCAN_TIMER_TAG,
                             mRestartScanListener, mEventHandler);
 
@@ -989,11 +978,11 @@
     // the current screen state and WiFi state.
     private void startConnectivityScan(boolean scanImmediately) {
         localLog("startConnectivityScan: screenOn=" + mScreenOn
-                        + " wifiState=" + mWifiState
-                        + " scanImmediately=" + scanImmediately
-                        + " wifiEnabled=" + mWifiEnabled
-                        + " wifiConnectivityManagerEnabled="
-                        + mWifiConnectivityManagerEnabled);
+                + " wifiState=" + stateToString(mWifiState)
+                + " scanImmediately=" + scanImmediately
+                + " wifiEnabled=" + mWifiEnabled
+                + " wifiConnectivityManagerEnabled="
+                + mWifiConnectivityManagerEnabled);
 
         if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
             return;
@@ -1010,24 +999,19 @@
 
         if (mScreenOn) {
             startPeriodicScan(scanImmediately);
-        } else { // screenOff
-            if (mWifiState == WIFI_STATE_CONNECTED) {
-                startConnectedPnoScan();
-            } else {
+        } else {
+            if (mWifiState == WIFI_STATE_DISCONNECTED && !mPnoScanStarted) {
                 startDisconnectedPnoScan();
             }
         }
+
     }
 
     // Stop connectivity scan if there is any.
     private void stopConnectivityScan() {
         // Due to b/28020168, timer based single scan will be scheduled
         // to provide periodic scan in an exponential backoff fashion.
-        if (!ENABLE_BACKGROUND_SCAN) {
-            cancelPeriodicScanTimer();
-        } else {
-            mScanner.stopBackgroundScan(mPeriodicScanListener);
-        }
+        cancelPeriodicScanTimer();
         stopPnoScan();
         mScanRestartCount = 0;
     }
@@ -1044,10 +1028,26 @@
     }
 
     /**
+     * Helper function that converts the WIFI_STATE_XXX constants to string
+     */
+    private static String stateToString(int state) {
+        switch (state) {
+            case WIFI_STATE_CONNECTED:
+                return "connected";
+            case WIFI_STATE_DISCONNECTED:
+                return "disconnected";
+            case WIFI_STATE_TRANSITIONING:
+                return "transitioning";
+            default:
+                return "unknown";
+        }
+    }
+
+    /**
      * Handler for WiFi state (connected/disconnected) changes
      */
     public void handleConnectionStateChanged(int state) {
-        localLog("handleConnectionStateChanged: state=" + state);
+        localLog("handleConnectionStateChanged: state=" + stateToString(state));
 
         mWifiState = state;
 
@@ -1056,16 +1056,17 @@
         if (mWifiState == WIFI_STATE_DISCONNECTED) {
             mLastConnectionAttemptBssid = null;
             scheduleWatchdogTimer();
+            startConnectivityScan(SCAN_IMMEDIATELY);
+        } else {
+            startConnectivityScan(SCAN_ON_SCHEDULE);
         }
-
-        startConnectivityScan(SCAN_ON_SCHEDULE);
     }
 
     /**
      * Handler when user toggles whether untrusted connection is allowed
      */
     public void setUntrustedConnectionAllowed(boolean allowed) {
-        Log.i(TAG, "setUntrustedConnectionAllowed: allowed=" + allowed);
+        localLog("setUntrustedConnectionAllowed: allowed=" + allowed);
 
         if (mUntrustedConnectionAllowed != allowed) {
             mUntrustedConnectionAllowed = allowed;
@@ -1076,107 +1077,252 @@
     /**
      * Handler when user specifies a particular network to connect to
      */
-    public void connectToUserSelectNetwork(int netId, boolean persistent) {
-        Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId
-                   + " persist=" + persistent);
+    public void setUserConnectChoice(int netId) {
+        localLog("setUserConnectChoice: netId=" + netId);
 
-        mQualifiedNetworkSelector.userSelectNetwork(netId, persistent);
+        mNetworkSelector.setUserConnectChoice(netId);
+    }
+
+    /**
+     * Handler to prepare for connection to a user or app specified network
+     */
+    public void prepareForForcedConnection(int netId) {
+        localLog("prepareForForcedConnection: netId=" + netId);
 
         clearConnectionAttemptTimeStamps();
+        clearBssidBlacklist();
     }
 
     /**
      * Handler for on-demand connectivity scan
      */
     public void forceConnectivityScan() {
-        Log.i(TAG, "forceConnectivityScan");
+        localLog("forceConnectivityScan");
 
         mWaitForFullBandScanResults = true;
         startSingleScan(true);
     }
 
     /**
-     * Track whether a BSSID should be enabled or disabled for QNS
+     * Update the BSSID blacklist when a BSSID is enabled or disabled
+     *
+     * @param bssid the bssid to be enabled/disabled
+     * @param enable -- true enable the bssid
+     *               -- false disable the bssid
+     * @param reasonCode enable/disable reason code
+     * @return true if blacklist is updated; false otherwise
      */
-    public boolean trackBssid(String bssid, boolean enable) {
-        Log.i(TAG, "trackBssid: " + (enable ? "enable " : "disable ") + bssid);
-
-        boolean ret = mQualifiedNetworkSelector
-                            .enableBssidForQualityNetworkSelection(bssid, enable);
-
-        if (ret && !enable) {
-            // Disabling a BSSID can happen when the AP candidate to connect to has
-            // no capacity for new stations. We start another scan immediately so that QNS
-            // can give us another candidate to connect to.
-            startConnectivityScan(SCAN_IMMEDIATELY);
+    private boolean updateBssidBlacklist(String bssid, boolean enable, int reasonCode) {
+        // Remove the bssid from blacklist when it is enabled.
+        if (enable) {
+            return mBssidBlacklist.remove(bssid) != null;
         }
 
-        return ret;
+        // Update the bssid's blacklist status when it is disabled because of
+        // association rejection.
+        BssidBlacklistStatus status = mBssidBlacklist.get(bssid);
+        if (status == null) {
+            // First time for this BSSID
+            status = new BssidBlacklistStatus();
+            mBssidBlacklist.put(bssid, status);
+        }
+
+        status.blacklistedTimeStamp = mClock.getElapsedSinceBootMillis();
+        status.counter++;
+        if (!status.isBlacklisted) {
+            if (status.counter >= BSSID_BLACKLIST_THRESHOLD
+                    || reasonCode == REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA) {
+                status.isBlacklisted = true;
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
-     * Set band preference when doing scan and making connection
+     * Track whether a BSSID should be enabled or disabled for WifiNetworkSelector
+     *
+     * @param bssid the bssid to be enabled/disabled
+     * @param enable -- true enable the bssid
+     *               -- false disable the bssid
+     * @param reasonCode enable/disable reason code
+     * @return true if blacklist is updated; false otherwise
      */
-    public void setUserPreferredBand(int band) {
-        Log.i(TAG, "User band preference: " + band);
+    public boolean trackBssid(String bssid, boolean enable, int reasonCode) {
+        localLog("trackBssid: " + (enable ? "enable " : "disable ") + bssid + " reason code "
+                + reasonCode);
 
-        mQualifiedNetworkSelector.setUserPreferredBand(band);
+        if (bssid == null) {
+            return false;
+        }
+
+        if (!updateBssidBlacklist(bssid, enable, reasonCode)) {
+            return false;
+        }
+
+        // Blacklist was updated, so update firmware roaming configuration.
+        updateFirmwareRoamingConfiguration();
+
+        if (!enable) {
+            // Disabling a BSSID can happen when connection to the AP was rejected.
+            // We start another scan immediately so that WifiNetworkSelector can
+            // give us another candidate to connect to.
+            startConnectivityScan(SCAN_IMMEDIATELY);
+        }
+
+        return true;
+    }
+
+    /**
+     * Check whether a bssid is disabled
+     */
+    @VisibleForTesting
+    public boolean isBssidDisabled(String bssid) {
+        BssidBlacklistStatus status = mBssidBlacklist.get(bssid);
+        return status == null ? false : status.isBlacklisted;
+    }
+
+    /**
+     * Compile and return a hashset of the blacklisted BSSIDs
+     */
+    private HashSet<String> buildBssidBlacklist() {
+        HashSet<String> blacklistedBssids = new HashSet<String>();
+        for (String bssid : mBssidBlacklist.keySet()) {
+            if (isBssidDisabled(bssid)) {
+                blacklistedBssids.add(bssid);
+            }
+        }
+
+        return blacklistedBssids;
+    }
+
+    /**
+     * Update firmware roaming configuration if the firmware roaming feature is supported.
+     * Compile and write the BSSID blacklist only. TODO(b/36488259): SSID whitelist is always
+     * empty for now.
+     */
+    private void updateFirmwareRoamingConfiguration() {
+        if (!mConnectivityHelper.isFirmwareRoamingSupported()) {
+            return;
+        }
+
+        int maxBlacklistSize = mConnectivityHelper.getMaxNumBlacklistBssid();
+        if (maxBlacklistSize <= 0) {
+            Log.wtf(TAG, "Invalid max BSSID blacklist size:  " + maxBlacklistSize);
+            return;
+        }
+
+        ArrayList<String> blacklistedBssids = new ArrayList<String>(buildBssidBlacklist());
+        int blacklistSize = blacklistedBssids.size();
+
+        if (blacklistSize > maxBlacklistSize) {
+            Log.wtf(TAG, "Attempt to write " + blacklistSize + " blacklisted BSSIDs, max size is "
+                    + maxBlacklistSize);
+
+            blacklistedBssids = new ArrayList<String>(blacklistedBssids.subList(0,
+                    maxBlacklistSize));
+            localLog("Trim down BSSID blacklist size from " + blacklistSize + " to "
+                    + blacklistedBssids.size());
+        }
+
+        if (!mConnectivityHelper.setFirmwareRoamingConfiguration(blacklistedBssids,
+                new ArrayList<String>())) {  // TODO(b/36488259): SSID whitelist management.
+            localLog("Failed to set firmware roaming configuration.");
+        }
+    }
+
+    /**
+     * Refresh the BSSID blacklist
+     *
+     * Go through the BSSID blacklist and check if a BSSID has been blacklisted for
+     * BSSID_BLACKLIST_EXPIRE_TIME_MS. If yes, re-enable it.
+     */
+    private void refreshBssidBlacklist() {
+        if (mBssidBlacklist.isEmpty()) {
+            return;
+        }
+
+        boolean updated = false;
+        Iterator<BssidBlacklistStatus> iter = mBssidBlacklist.values().iterator();
+        Long currentTimeStamp = mClock.getElapsedSinceBootMillis();
+
+        while (iter.hasNext()) {
+            BssidBlacklistStatus status = iter.next();
+            if (status.isBlacklisted && ((currentTimeStamp - status.blacklistedTimeStamp)
+                    >= BSSID_BLACKLIST_EXPIRE_TIME_MS)) {
+                iter.remove();
+                updated = true;
+            }
+        }
+
+        if (updated) {
+            updateFirmwareRoamingConfiguration();
+        }
+    }
+
+    /**
+     * Clear the BSSID blacklist
+     */
+    private void clearBssidBlacklist() {
+        mBssidBlacklist.clear();
+        updateFirmwareRoamingConfiguration();
+    }
+
+    /**
+     * Start WifiConnectivityManager
+     */
+    private void start() {
+        mConnectivityHelper.getFirmwareRoamingInfo();
+        clearBssidBlacklist();
         startConnectivityScan(SCAN_IMMEDIATELY);
     }
 
     /**
+     * Stop and reset WifiConnectivityManager
+     */
+    private void stop() {
+        stopConnectivityScan();
+        clearBssidBlacklist();
+        resetLastPeriodicSingleScanTimeStamp();
+        mLastConnectionAttemptBssid = null;
+        mWaitForFullBandScanResults = false;
+    }
+
+    /**
+     * Update WifiConnectivityManager running state
+     *
+     * Start WifiConnectivityManager only if both Wifi and WifiConnectivityManager
+     * are enabled, otherwise stop it.
+     */
+    private void updateRunningState() {
+        if (mWifiEnabled && mWifiConnectivityManagerEnabled) {
+            localLog("Starting up WifiConnectivityManager");
+            start();
+        } else {
+            localLog("Stopping WifiConnectivityManager");
+            stop();
+        }
+    }
+
+    /**
      * Inform WiFi is enabled for connection or not
      */
     public void setWifiEnabled(boolean enable) {
-        Log.i(TAG, "Set WiFi " + (enable ? "enabled" : "disabled"));
+        localLog("Set WiFi " + (enable ? "enabled" : "disabled"));
 
         mWifiEnabled = enable;
+        updateRunningState();
 
-        if (!mWifiEnabled) {
-            stopConnectivityScan();
-            resetLastPeriodicSingleScanTimeStamp();
-            mLastConnectionAttemptBssid = null;
-            mWaitForFullBandScanResults = false;
-        } else if (mWifiConnectivityManagerEnabled) {
-           startConnectivityScan(SCAN_IMMEDIATELY);
-        }
     }
 
     /**
-     * Turn on/off the WifiConnectivityMangager at runtime
+     * Turn on/off the WifiConnectivityManager at runtime
      */
     public void enable(boolean enable) {
-        Log.i(TAG, "Set WiFiConnectivityManager " + (enable ? "enabled" : "disabled"));
+        localLog("Set WiFiConnectivityManager " + (enable ? "enabled" : "disabled"));
 
         mWifiConnectivityManagerEnabled = enable;
-
-        if (!mWifiConnectivityManagerEnabled) {
-            stopConnectivityScan();
-            resetLastPeriodicSingleScanTimeStamp();
-            mLastConnectionAttemptBssid = null;
-            mWaitForFullBandScanResults = false;
-        } else if (mWifiEnabled) {
-           startConnectivityScan(SCAN_IMMEDIATELY);
-        }
-    }
-
-    /**
-     * Enable/disable verbose logging
-     */
-    public void enableVerboseLogging(int verbose) {
-        mDbg = verbose > 0;
-    }
-
-    /**
-     * Dump the local log buffer
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("Dump of WifiConnectivityManager");
-        pw.println("WifiConnectivityManager - Log Begin ----");
-        pw.println("WifiConnectivityManager - Number of connectivity attempts rate limited: "
-                + mTotalConnectivityAttemptsRateLimited);
-        mLocalLog.dump(fd, pw, args);
-        pw.println("WifiConnectivityManager - Log End ----");
+        updateRunningState();
     }
 
     @VisibleForTesting
@@ -1188,4 +1334,14 @@
     long getLastPeriodicSingleScanTimeStamp() {
         return mLastPeriodicSingleScanTimeStamp;
     }
+
+    /**
+     * Dump the local logs.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WifiConnectivityManager");
+        pw.println("WifiConnectivityManager - Log Begin ----");
+        mLocalLog.dump(fd, pw, args);
+        pw.println("WifiConnectivityManager - Log End ----");
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
index 67a7a42..c1b1861 100644
--- a/service/java/com/android/server/wifi/WifiController.java
+++ b/service/java/com/android/server/wifi/WifiController.java
@@ -30,7 +30,6 @@
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
-import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -270,13 +269,13 @@
             }
         };
 
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
-                false, contentObserver);
+        mFacade.registerContentObserver(mContext,
+                Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN), false,
+                contentObserver);
     }
 
     /**
-     * Observes settings changes to scan always mode.
+     * Observes settings changes to wifi idle time.
      */
     private void registerForWifiIdleTimeChange(Handler handler) {
         ContentObserver contentObserver = new ContentObserver(handler) {
@@ -286,9 +285,8 @@
             }
         };
 
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
-                false, contentObserver);
+        mFacade.registerContentObserver(mContext,
+                Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS), false, contentObserver);
     }
 
     /**
@@ -301,9 +299,9 @@
                 readWifiSleepPolicy();
             }
         };
-        mContext.getContentResolver().registerContentObserver(
-                Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
-                false, contentObserver);
+        mFacade.registerContentObserver(mContext,
+                Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY), false,
+                contentObserver);
     }
 
     /**
@@ -458,6 +456,11 @@
                             break;
                         }
                         if (mDeviceIdle == false) {
+                            // wifi is toggled, we need to explicitly tell WifiStateMachine that we
+                            // are headed to connect mode before going to the DeviceActiveState
+                            // since that will start supplicant and WifiStateMachine may not know
+                            // what state to head to (it might go to scan mode).
+                            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
                             transitionTo(mDeviceActiveState);
                         } else {
                             checkLocksAndTransitionWhenDeviceIdle();
@@ -476,7 +479,7 @@
                         if (msg.arg2 == 0) { // previous wifi state has not been saved yet
                             mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
                         }
-                        mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
+                        mWifiStateMachine.setHostApRunning((SoftApModeConfiguration) msg.obj,
                                 true);
                         transitionTo(mApEnabledState);
                     }
@@ -585,7 +588,6 @@
             // in to client mode
             mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
             mWifiStateMachine.setSupplicantRunning(true);
-            mWifiStateMachine.setDriverStart(true);
             // Supplicant can't restart right away, so not the time we switched off
             mDisabledTimestamp = SystemClock.elapsedRealtime();
             mDeferredEnableSerialNumber++;
@@ -817,7 +819,6 @@
         @Override
         public void enter() {
             mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-            mWifiStateMachine.setDriverStart(true);
             mWifiStateMachine.setHighPerfModeEnabled(false);
         }
 
@@ -868,7 +869,6 @@
         @Override
         public void enter() {
             mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
-            mWifiStateMachine.setDriverStart(true);
         }
     }
 
@@ -877,7 +877,6 @@
         @Override
         public void enter() {
             mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-            mWifiStateMachine.setDriverStart(true);
             mWifiStateMachine.setHighPerfModeEnabled(false);
         }
     }
@@ -887,7 +886,6 @@
         @Override
         public void enter() {
             mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-            mWifiStateMachine.setDriverStart(true);
             mWifiStateMachine.setHighPerfModeEnabled(true);
         }
     }
@@ -896,7 +894,7 @@
     class NoLockHeldState extends State {
         @Override
         public void enter() {
-            mWifiStateMachine.setDriverStart(false);
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.DISABLED_MODE);
         }
     }
 
diff --git a/service/java/com/android/server/wifi/WifiCountryCode.java b/service/java/com/android/server/wifi/WifiCountryCode.java
index 1339656..e69fb8e 100644
--- a/service/java/com/android/server/wifi/WifiCountryCode.java
+++ b/service/java/com/android/server/wifi/WifiCountryCode.java
@@ -42,15 +42,12 @@
     public WifiCountryCode(
             WifiNative wifiNative,
             String oemDefaultCountryCode,
-            String persistentCountryCode,
             boolean revertCountryCodeOnCellularLoss) {
 
         mWifiNative = wifiNative;
         mRevertCountryCodeOnCellularLoss = revertCountryCodeOnCellularLoss;
 
-        if (!TextUtils.isEmpty(persistentCountryCode)) {
-            mDefaultCountryCode = persistentCountryCode.toUpperCase();
-        } else if (!TextUtils.isEmpty(oemDefaultCountryCode)) {
+        if (!TextUtils.isEmpty(oemDefaultCountryCode)) {
             mDefaultCountryCode = oemDefaultCountryCode.toUpperCase();
         } else {
             if (mRevertCountryCodeOnCellularLoss) {
@@ -132,17 +129,15 @@
      * otherwise we think it is from other applications.
      * @return Returns true if the country code passed in is acceptable.
      */
-    public synchronized boolean setCountryCode(String countryCode, boolean persist) {
+    public synchronized boolean setCountryCode(String countryCode) {
         if (DBG) Log.d(TAG, "Receive set country code request: " + countryCode);
-        // Ignore empty country code.
+        // Empty country code.
         if (TextUtils.isEmpty(countryCode)) {
-            if (DBG) Log.d(TAG, "Ignore empty country code");
-            return false;
+            if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
+            mTelephonyCountryCode = null;
+        } else {
+            mTelephonyCountryCode = countryCode.toUpperCase();
         }
-        if (persist) {
-            mDefaultCountryCode = countryCode;
-        }
-        mTelephonyCountryCode = countryCode.toUpperCase();
         // If wpa_supplicant is ready we set the country code now, otherwise it will be
         // set once wpa_supplicant is ready.
         if (mReady) {
diff --git a/service/java/com/android/server/wifi/WifiLogger.java b/service/java/com/android/server/wifi/WifiDiagnostics.java
similarity index 89%
rename from service/java/com/android/server/wifi/WifiLogger.java
rename to service/java/com/android/server/wifi/WifiDiagnostics.java
index c15e2a8..b2acb2a 100644
--- a/service/java/com/android/server/wifi/WifiLogger.java
+++ b/service/java/com/android/server/wifi/WifiDiagnostics.java
@@ -18,10 +18,9 @@
 
 import android.content.Context;
 import android.util.Base64;
-import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.util.ByteArrayRingBuffer;
 import com.android.server.wifi.util.StringUtil;
 
@@ -31,7 +30,6 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
-import java.lang.StringBuilder;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -43,15 +41,15 @@
 /**
  * Tracks various logs for framework.
  */
-class WifiLogger extends BaseWifiLogger {
+class WifiDiagnostics extends BaseWifiDiagnostics {
     /**
      * Thread-safety:
      * 1) All non-private methods are |synchronized|.
-     * 2) Callbacks into WifiLogger use non-private (and hence, synchronized) methods. See, e.g,
+     * 2) Callbacks into WifiDiagnostics use non-private (and hence, synchronized) methods. See, e.g,
      *    onRingBufferData(), onWifiAlert().
      */
 
-    private static final String TAG = "WifiLogger";
+    private static final String TAG = "WifiDiags";
     private static final boolean DBG = false;
 
     /** log level flags; keep these consistent with wifi_logger.h */
@@ -103,22 +101,28 @@
     private WifiNative.RingBufferStatus[] mRingBuffers;
     private WifiNative.RingBufferStatus mPerPacketRingBuffer;
     private WifiStateMachine mWifiStateMachine;
-    private final WifiNative mWifiNative;
     private final BuildProperties mBuildProperties;
+    private final WifiLog mLog;
+    private final LastMileLogger mLastMileLogger;
+    private final Runtime mJavaRuntime;
     private int mMaxRingBufferSizeBytes;
 
-    public WifiLogger(Context context, WifiStateMachine wifiStateMachine, WifiNative wifiNative,
-                      BuildProperties buildProperties) {
+    public WifiDiagnostics(Context context, WifiInjector wifiInjector,
+                           WifiStateMachine wifiStateMachine, WifiNative wifiNative,
+                           BuildProperties buildProperties, LastMileLogger lastMileLogger) {
+        super(wifiNative);
         RING_BUFFER_BYTE_LIMIT_SMALL = context.getResources().getInteger(
                 R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb) * 1024;
         RING_BUFFER_BYTE_LIMIT_LARGE = context.getResources().getInteger(
                 R.integer.config_wifi_logger_ring_buffer_verbose_size_limit_kb) * 1024;
 
         mWifiStateMachine = wifiStateMachine;
-        mWifiNative = wifiNative;
         mBuildProperties = buildProperties;
         mIsLoggingEventHandlerRegistered = false;
         mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_SMALL;
+        mLog = wifiInjector.makeLog(TAG);
+        mLastMileLogger = lastMileLogger;
+        mJavaRuntime = wifiInjector.getJavaRuntime();
     }
 
     @Override
@@ -153,7 +157,7 @@
         }
 
         if (!mWifiNative.startPktFateMonitoring()) {
-            Log.e(TAG, "Failed to start packet fate monitoring");
+            mLog.wC("Failed to start packet fate monitoring");
         }
     }
 
@@ -162,7 +166,7 @@
         if (mPerPacketRingBuffer != null) {
             startLoggingRingBuffer(mPerPacketRingBuffer);
         } else {
-            if (DBG) Log.d(TAG, "There is no per packet ring buffer");
+            if (DBG) mLog.tC("There is no per packet ring buffer");
         }
     }
 
@@ -171,7 +175,7 @@
         if (mPerPacketRingBuffer != null) {
             stopLoggingRingBuffer(mPerPacketRingBuffer);
         } else {
-            if (DBG) Log.d(TAG, "There is no per packet ring buffer");
+            if (DBG) mLog.tC("There is no per packet ring buffer");
         }
     }
 
@@ -179,9 +183,9 @@
     public synchronized void stopLogging() {
         if (mIsLoggingEventHandlerRegistered) {
             if (!mWifiNative.resetLogHandler()) {
-                Log.e(TAG, "Fail to reset log handler");
+                mLog.wC("Fail to reset log handler");
             } else {
-                if (DBG) Log.d(TAG, "Reset log handler");
+                if (DBG) mLog.tC("Reset log handler");
             }
             // Clear mIsLoggingEventHandlerRegistered even if resetLogHandler() failed, because
             // the log handler is in an indeterminate state.
@@ -195,8 +199,11 @@
     }
 
     @Override
-    synchronized void reportConnectionFailure() {
-        mPacketFatesForLastFailure = fetchPacketFates();
+    synchronized void reportConnectionEvent(long connectionId, byte event) {
+        mLastMileLogger.reportConnectionEvent(connectionId, event);
+        if (event == CONNECTION_EVENT_FAILED) {
+            mPacketFatesForLastFailure = fetchPacketFates();
+        }
     }
 
     @Override
@@ -231,11 +238,9 @@
         }
 
         dumpPacketFates(pw);
-        pw.println("--------------------------------------------------------------------");
+        mLastMileLogger.dump(pw);
 
-        pw.println("WifiNative - Log Begin ----");
-        mWifiNative.getLocalLog().dump(fd, pw, args);
-        pw.println("WifiNative - Log End ----");
+        pw.println("--------------------------------------------------------------------");
     }
 
     /* private methods and data */
@@ -368,12 +373,12 @@
             new WifiNative.WifiLoggerEventHandler() {
         @Override
         public void onRingBufferData(WifiNative.RingBufferStatus status, byte[] buffer) {
-            WifiLogger.this.onRingBufferData(status, buffer);
+            WifiDiagnostics.this.onRingBufferData(status, buffer);
         }
 
         @Override
         public void onWifiAlert(int errorCode, byte[] buffer) {
-            WifiLogger.this.onWifiAlert(errorCode, buffer);
+            WifiDiagnostics.this.onWifiAlert(errorCode, buffer);
         }
     };
 
@@ -408,12 +413,17 @@
     }
 
     private boolean fetchRingBuffers() {
+        if (mBuildProperties.isUserBuild()) {
+            mRingBuffers = null;
+            return false;
+        }
+
         if (mRingBuffers != null) return true;
 
         mRingBuffers = mWifiNative.getRingBufferStatus();
         if (mRingBuffers != null) {
             for (WifiNative.RingBufferStatus buffer : mRingBuffers) {
-                if (DBG) Log.d(TAG, "RingBufferStatus is: \n" + buffer.name);
+                if (DBG) mLog.trace("RingBufferStatus is: %").c(buffer.name).flush();
                 if (mRingBufferData.containsKey(buffer.name) == false) {
                     mRingBufferData.put(buffer.name,
                             new ByteArrayRingBuffer(mMaxRingBufferSizeBytes));
@@ -423,7 +433,7 @@
                 }
             }
         } else {
-            Log.e(TAG, "no ring buffers found");
+            mLog.wC("no ring buffers found");
         }
 
         return mRingBuffers != null;
@@ -438,7 +448,7 @@
     private boolean startLoggingAllExceptPerPacketBuffers() {
 
         if (mRingBuffers == null) {
-            if (DBG) Log.d(TAG, "No ring buffers to log anything!");
+            if (DBG) mLog.tC("No ring buffers to log anything!");
             return false;
         }
 
@@ -446,7 +456,7 @@
 
             if ((buffer.flag & RING_BUFFER_FLAG_HAS_PER_PACKET_ENTRIES) != 0) {
                 /* skip per-packet-buffer */
-                if (DBG) Log.d(TAG, "skipped per packet logging ring " + buffer.name);
+                if (DBG) mLog.trace("skipped per packet logging ring %").c(buffer.name).flush();
                 continue;
             }
 
@@ -463,7 +473,7 @@
 
         if (mWifiNative.startLoggingRingBuffer(
                 mLogLevel, 0, minInterval, minDataSize, buffer.name) == false) {
-            if (DBG) Log.e(TAG, "Could not start logging ring " + buffer.name);
+            if (DBG) mLog.warn("Could not start logging ring %").c(buffer.name).flush();
             return false;
         }
 
@@ -472,7 +482,7 @@
 
     private boolean stopLoggingRingBuffer(WifiNative.RingBufferStatus buffer) {
         if (mWifiNative.startLoggingRingBuffer(0, 0, 0, 0, buffer.name) == false) {
-            if (DBG) Log.e(TAG, "Could not stop logging ring " + buffer.name);
+            if (DBG) mLog.warn("Could not stop logging ring %").c(buffer.name).flush();
         }
         return true;
     }
@@ -486,24 +496,6 @@
         return true;
     }
 
-    private boolean getAllRingBufferData() {
-        if (mRingBuffers == null) {
-            Log.e(TAG, "Not ring buffers available to collect data!");
-            return false;
-        }
-
-        for (WifiNative.RingBufferStatus element : mRingBuffers){
-            boolean result = mWifiNative.getRingBufferData(element.name);
-            if (!result) {
-                Log.e(TAG, "Fail to get ring buffer data of: " + element.name);
-                return false;
-            }
-        }
-
-        Log.d(TAG, "getAllRingBufferData Successfully!");
-        return true;
-    }
-
     private boolean enableVerboseLoggingForDogfood() {
         return false;
     }
@@ -542,7 +534,7 @@
         return mLastBugReports;
     }
 
-    private static String compressToBase64(byte[] input) {
+    private String compressToBase64(byte[] input) {
         String result;
         //compress
         Deflater compressor = new Deflater();
@@ -561,14 +553,14 @@
             compressor.end();
             bos.close();
         } catch (IOException e) {
-            Log.e(TAG, "ByteArrayOutputStream close error");
+            mLog.wC("ByteArrayOutputStream close error");
             result =  android.util.Base64.encodeToString(input, Base64.DEFAULT);
             return result;
         }
 
         byte[] compressed = bos.toByteArray();
         if (DBG) {
-            Log.d(TAG," length is:" + (compressed == null? "0" : compressed.length));
+            mLog.dump("length is: %").c(compressed == null ? 0 : compressed.length).flush();
         }
 
         //encode
@@ -576,7 +568,7 @@
                 compressed.length < input.length ? compressed : input , Base64.DEFAULT);
 
         if (DBG) {
-            Log.d(TAG, "FwMemoryDump length is :" + result.length());
+            mLog.dump("FwMemoryDump length is: %").c(result.length()).flush();
         }
 
         return result;
@@ -585,7 +577,7 @@
     private ArrayList<String> getLogcat(int maxLines) {
         ArrayList<String> lines = new ArrayList<String>(maxLines);
         try {
-            Process process = Runtime.getRuntime().exec(String.format("logcat -t %d", maxLines));
+            Process process = mJavaRuntime.exec(String.format("logcat -t %d", maxLines));
             BufferedReader reader = new BufferedReader(
                     new InputStreamReader(process.getInputStream()));
             String line;
@@ -599,20 +591,20 @@
             }
             process.waitFor();
         } catch (InterruptedException|IOException e) {
-            Log.e(TAG, "Exception while capturing logcat" + e);
+            mLog.dump("Exception while capturing logcat: %").c(e.toString()).flush();
         }
         return lines;
     }
 
     private LimitedCircularArray<String> getKernelLog(int maxLines) {
-        if (DBG) Log.d(TAG, "Reading kernel log ...");
+        if (DBG) mLog.tC("Reading kernel log ...");
         LimitedCircularArray<String> lines = new LimitedCircularArray<String>(maxLines);
         String log = mWifiNative.readKernelLog();
         String logLines[] = log.split("\n");
         for (int i = 0; i < logLines.length; i++) {
             lines.addLast(logLines[i]);
         }
-        if (DBG) Log.d(TAG, "Added " + logLines.length + " lines");
+        if (DBG) mLog.dump("Added % lines").c(logLines.length).flush();
         return lines;
     }
 
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 6939d45..c517785 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -16,34 +16,302 @@
 
 package com.android.server.wifi;
 
+import android.app.ActivityManager;
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IWifiScanner;
+import android.net.wifi.IWificond;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.WifiScanner;
+import android.os.BatteryStats;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserManager;
 import android.security.KeyStore;
+import android.telephony.TelephonyManager;
+import android.util.LocalLog;
+
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.net.DelayedDiskWrite;
+import com.android.server.net.IpConfigStore;
+import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;
+import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.hotspot2.PasspointNetworkEvaluator;
+import com.android.server.wifi.hotspot2.PasspointObjectFactory;
+import com.android.server.wifi.p2p.SupplicantP2pIfaceHal;
+import com.android.server.wifi.p2p.WifiP2pMonitor;
+import com.android.server.wifi.p2p.WifiP2pNative;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 /**
- *  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 WifiApConfigStore mWifiApConfigStore;
+    private final WifiNative mWifiNative;
+    private final WifiMonitor mWifiMonitor;
+    private final WifiP2pNative mWifiP2pNative;
+    private final WifiP2pMonitor mWifiP2pMonitor;
+    private final SupplicantStaIfaceHal mSupplicantStaIfaceHal;
+    private final SupplicantP2pIfaceHal mSupplicantP2pIfaceHal;
+    private final WifiVendorHal mWifiVendorHal;
+    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 WificondControl mWificondControl;
     private final Clock mClock = new Clock();
-    private final WifiMetrics mWifiMetrics = new WifiMetrics(mClock);
-    private final WifiLastResortWatchdog mWifiLastResortWatchdog =
-            new WifiLastResortWatchdog(mWifiMetrics);
+    private final WifiMetrics 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;
+    private final WifiMulticastLockManager mWifiMulticastLockManager;
+    private final WifiConfigStore mWifiConfigStore;
+    private final WifiKeyStore mWifiKeyStore;
+    private final WifiNetworkHistory mWifiNetworkHistory;
+    private final IpConfigStore mIpConfigStore;
+    private final WifiConfigStoreLegacy mWifiConfigStoreLegacy;
+    private final WifiConfigManager mWifiConfigManager;
+    private final WifiConnectivityHelper mWifiConnectivityHelper;
+    private final LocalLog mConnectivityLocalLog;
+    private final WifiNetworkSelector mWifiNetworkSelector;
+    private final SavedNetworkEvaluator mSavedNetworkEvaluator;
+    private final PasspointNetworkEvaluator mPasspointNetworkEvaluator;
+    private final ScoredNetworkEvaluator mScoredNetworkEvaluator;
+    private final WifiNetworkScoreCache mWifiNetworkScoreCache;
+    private final NetworkScoreManager mNetworkScoreManager;
+    private WifiScanner mWifiScanner;
+    private final WifiPermissionsWrapper mWifiPermissionsWrapper;
+    private final WifiPermissionsUtil mWifiPermissionsUtil;
+    private final PasspointManager mPasspointManager;
+    private final SIMAccessor mSimAccessor;
+    private HandlerThread mWifiAwareHandlerThread;
+    private HalDeviceManager mHalDeviceManager;
+    private final IBatteryStats mBatteryStats;
+    private final WifiStateTracker mWifiStateTracker;
+    private final Runtime mJavaRuntime;
+    private final SelfRecovery mSelfRecovery;
+
+    private final boolean mUseRealLogger;
+
+    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;
+        mUseRealLogger = mContext.getResources().getBoolean(
+                R.bool.config_wifi_enable_wifi_firmware_debugging);
+        mSettingsStore = new WifiSettingsStore(mContext);
+        mWifiPermissionsWrapper = new WifiPermissionsWrapper(mContext);
+        mNetworkScoreManager = mContext.getSystemService(NetworkScoreManager.class);
+        mWifiNetworkScoreCache = new WifiNetworkScoreCache(mContext);
+        mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
+                mWifiNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+        mWifiPermissionsUtil = new WifiPermissionsUtil(mWifiPermissionsWrapper, mContext,
+                mSettingsStore, UserManager.get(mContext), mNetworkScoreManager, this);
+        mWifiBackupRestore = new WifiBackupRestore(mWifiPermissionsUtil);
+        mBatteryStats = IBatteryStats.Stub.asInterface(mFrameworkFacade.getService(
+                BatteryStats.SERVICE_NAME));
+        mWifiStateTracker = new WifiStateTracker(mBatteryStats);
+        // Now create and start handler threads
+        mWifiServiceHandlerThread = new HandlerThread("WifiService");
+        mWifiServiceHandlerThread.start();
+        mWifiStateMachineHandlerThread = new HandlerThread("WifiStateMachine");
+        mWifiStateMachineHandlerThread.start();
+        Looper wifiStateMachineLooper = mWifiStateMachineHandlerThread.getLooper();
+        mWifiMetrics = new WifiMetrics(mClock, wifiStateMachineLooper);
+        // Modules interacting with Native.
+        mWifiMonitor = new WifiMonitor(this);
+        mHalDeviceManager = new HalDeviceManager();
+        mWifiVendorHal =
+                new WifiVendorHal(mHalDeviceManager, mWifiStateMachineHandlerThread.getLooper());
+        mSupplicantStaIfaceHal = new SupplicantStaIfaceHal(mContext, mWifiMonitor);
+        mWificondControl = new WificondControl(this, mWifiMonitor);
+        mWifiNative = new WifiNative(SystemProperties.get("wifi.interface", "wlan0"),
+                mWifiVendorHal, mSupplicantStaIfaceHal, mWificondControl);
+        mWifiP2pMonitor = new WifiP2pMonitor(this);
+        mSupplicantP2pIfaceHal = new SupplicantP2pIfaceHal(mWifiP2pMonitor);
+        mWifiP2pNative = new WifiP2pNative(SystemProperties.get("wifi.direct.interface", "p2p0"),
+                mSupplicantP2pIfaceHal);
+
+        // Now get instances of all the objects that depend on the HandlerThreads
+        mTrafficPoller =  new WifiTrafficPoller(mContext, mWifiServiceHandlerThread.getLooper(),
+                mWifiNative.getInterfaceName());
+        mCountryCode = new WifiCountryCode(mWifiNative,
+                SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE),
+                mContext.getResources()
+                        .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss));
+        mWifiApConfigStore = new WifiApConfigStore(mContext, mBackupManagerProxy);
+
+        // WifiConfigManager/Store objects and their dependencies.
+        // New config store
+        mWifiKeyStore = new WifiKeyStore(mKeyStore);
+        mWifiConfigStore = new WifiConfigStore(
+                mContext, wifiStateMachineLooper, mClock,
+                WifiConfigStore.createSharedFile());
+        // Legacy config store
+        DelayedDiskWrite writer = new DelayedDiskWrite();
+        mWifiNetworkHistory = new WifiNetworkHistory(mContext, writer);
+        mIpConfigStore = new IpConfigStore(writer);
+        mWifiConfigStoreLegacy = new WifiConfigStoreLegacy(
+                mWifiNetworkHistory, mWifiNative, mIpConfigStore,
+                new LegacyPasspointConfigParser());
+        // Config Manager
+        mWifiConfigManager = new WifiConfigManager(mContext, mClock,
+                UserManager.get(mContext), TelephonyManager.from(mContext),
+                mWifiKeyStore, mWifiConfigStore, mWifiConfigStoreLegacy, mWifiPermissionsUtil,
+                mWifiPermissionsWrapper, new NetworkListStoreData(),
+                new DeletedEphemeralSsidsStoreData());
+        mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
+        mConnectivityLocalLog = new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 256 : 512);
+        mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiConfigManager, mClock,
+                mConnectivityLocalLog);
+        mSavedNetworkEvaluator = new SavedNetworkEvaluator(mContext,
+                mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiConnectivityHelper);
+        mScoredNetworkEvaluator = new ScoredNetworkEvaluator(context, wifiStateMachineLooper,
+                mFrameworkFacade, mNetworkScoreManager, mWifiConfigManager, mConnectivityLocalLog,
+                mWifiNetworkScoreCache);
+        mSimAccessor = new SIMAccessor(mContext);
+        mPasspointManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
+                mSimAccessor, new PasspointObjectFactory(), mWifiConfigManager, mWifiConfigStore);
+        mPasspointNetworkEvaluator = new PasspointNetworkEvaluator(
+                mPasspointManager, mWifiConfigManager, mConnectivityLocalLog);
+        // mWifiStateMachine has an implicit dependency on mJavaRuntime due to WifiDiagnostics.
+        mJavaRuntime = Runtime.getRuntime();
+        mWifiStateMachine = new WifiStateMachine(mContext, mFrameworkFacade,
+                wifiStateMachineLooper, UserManager.get(mContext),
+                this, mBackupManagerProxy, mCountryCode, mWifiNative);
+        mCertManager = new WifiCertManager(mContext);
+        mNotificationController = new WifiNotificationController(mContext,
+                mWifiServiceHandlerThread.getLooper(), mFrameworkFacade, null, this);
+        mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
+        mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
+                mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
+        mSelfRecovery = new SelfRecovery(mWifiController);
+        mWifiLastResortWatchdog = new WifiLastResortWatchdog(mSelfRecovery, 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 UserManager getUserManager() {
+        return UserManager.get(mContext);
+    }
 
     public WifiMetrics getWifiMetrics() {
         return mWifiMetrics;
     }
 
+    public SupplicantStaIfaceHal getSupplicantStaIfaceHal() {
+        return mSupplicantStaIfaceHal;
+    }
+
+    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 WifiApConfigStore getWifiApConfigStore() {
+        return mWifiApConfigStore;
+    }
+
+    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 +324,163 @@
         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 WifiConfigManager getWifiConfigManager() {
+        return mWifiConfigManager;
+    }
+
+    public PasspointManager getPasspointManager() {
+        return mPasspointManager;
+    }
+
+    public TelephonyManager makeTelephonyManager() {
+        // may not be available when WiFi starts
+        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    public WifiStateTracker getWifiStateTracker() {
+        return mWifiStateTracker;
+    }
+
+    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 nmService NetworkManagementService allowing SoftApManager to listen for interface
+     * changes
+     * @param listener listener for SoftApManager
+     * @param apInterface network interface to start hostapd against
+     * @param config softAp WifiConfiguration
+     * @return an instance of SoftApManager
+     */
+    public SoftApManager makeSoftApManager(INetworkManagementService nmService,
+                                           SoftApManager.Listener listener,
+                                           IApInterface apInterface,
+                                           WifiConfiguration config) {
+        return new SoftApManager(mWifiServiceHandlerThread.getLooper(),
+                                 mWifiNative, mCountryCode.getCountryCode(),
+                                 listener, apInterface, nmService,
+                                 mWifiApConfigStore, config, mWifiMetrics);
+    }
+
+    /**
+     * Create a WifiLog instance.
+     * @param tag module name to include in all log messages
+     */
+    public WifiLog makeLog(String tag) {
+        return new LogcatLog(tag);
+    }
+
+    public BaseWifiDiagnostics makeWifiDiagnostics(WifiNative wifiNative) {
+        if (mUseRealLogger) {
+            return new WifiDiagnostics(
+                    mContext, this, mWifiStateMachine, wifiNative, mBuildProperties,
+                    new LastMileLogger(this));
+        } else {
+            return new BaseWifiDiagnostics(wifiNative);
+        }
+    }
+
+    /**
+     * Obtain an instance of WifiScanner.
+     * If it was not already created, then obtain an instance.  Note, this must be done lazily since
+     * WifiScannerService is separate and created later.
+     */
+    public synchronized WifiScanner getWifiScanner() {
+        if (mWifiScanner == null) {
+            mWifiScanner = new WifiScanner(mContext,
+                    IWifiScanner.Stub.asInterface(ServiceManager.getService(
+                            Context.WIFI_SCANNING_SERVICE)),
+                    mWifiStateMachineHandlerThread.getLooper());
+        }
+        return mWifiScanner;
+    }
+
+    /**
+     * Obtain a new instance of WifiConnectivityManager.
+     *
+     * Create and return a new WifiConnectivityManager.
+     * @param wifiInfo WifiInfo object for updating wifi state.
+     * @param hasConnectionRequests boolean indicating if WifiConnectivityManager to start
+     * immediately based on connection requests.
+     */
+    public WifiConnectivityManager makeWifiConnectivityManager(WifiInfo wifiInfo,
+                                                               boolean hasConnectionRequests) {
+        return new WifiConnectivityManager(mContext, mWifiStateMachine, getWifiScanner(),
+                mWifiConfigManager, wifiInfo, mWifiNetworkSelector, mWifiConnectivityHelper,
+                mWifiLastResortWatchdog, mWifiMetrics, mWifiStateMachineHandlerThread.getLooper(),
+                mClock, mConnectivityLocalLog, hasConnectionRequests, mFrameworkFacade,
+                mSavedNetworkEvaluator, mScoredNetworkEvaluator, mPasspointNetworkEvaluator);
+    }
+
+    public WifiPermissionsUtil getWifiPermissionsUtil() {
+        return mWifiPermissionsUtil;
+    }
+
+    public WifiPermissionsWrapper getWifiPermissionsWrapper() {
+        return mWifiPermissionsWrapper;
+    }
+
+    /**
+     * Returns a singleton instance of a HandlerThread for injection. Uses lazy initialization.
+     *
+     * TODO: share worker thread with other Wi-Fi handlers (b/27924886)
+     */
+    public HandlerThread getWifiAwareHandlerThread() {
+        if (mWifiAwareHandlerThread == null) { // lazy initialization
+            mWifiAwareHandlerThread = new HandlerThread("wifiAwareService");
+            mWifiAwareHandlerThread.start();
+        }
+        return mWifiAwareHandlerThread;
+    }
+
+    /**
+     * Returns a single instance of HalDeviceManager for injection.
+     */
+    public HalDeviceManager getHalDeviceManager() {
+        return mHalDeviceManager;
+    }
+
+    public Runtime getJavaRuntime() {
+        return mJavaRuntime;
+    }
+
+    public WifiNative getWifiNative() {
+        return mWifiNative;
+    }
+
+    public WifiMonitor getWifiMonitor() {
+        return mWifiMonitor;
+    }
+
+    public WifiP2pNative getWifiP2pNative() {
+        return mWifiP2pNative;
+    }
+
+    public WifiP2pMonitor getWifiP2pMonitor() {
+        return mWifiP2pMonitor;
+    }
+
+    public SelfRecovery getSelfRecovery() {
+        return mSelfRecovery;
+    }
 }
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..e36c501
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiKeyStore.java
@@ -0,0 +1,293 @@
+/*
+ * 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;
+        Certificate[] clientCertificateChain = config.getClientCertificateChain();
+        if (clientCertificateChain != null && clientCertificateChain.length != 0) {
+            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 = putCertsInKeyStore(userCertName, clientCertificateChain);
+            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;
+    }
+
+    /**
+     * Install a certificate into the keystore.
+     *
+     * @param name The alias name of the certificate to be installed
+     * @param cert The certificate to be installed
+     * @return true on success
+     */
+    public boolean putCertInKeyStore(String name, Certificate cert) {
+        return putCertsInKeyStore(name, new Certificate[] {cert});
+    }
+
+    /**
+     * Install a client certificate chain into the keystore.
+     *
+     * @param name The alias name of the certificate to be installed
+     * @param certs The certificate chain to be installed
+     * @return true on success
+     */
+    public boolean putCertsInKeyStore(String name, Certificate[] certs) {
+        try {
+            byte[] certData = Credentials.convertToPem(certs);
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "putting " + certs.length + " certificate(s) "
+                        + name + " in keystore");
+            }
+            return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
+        } catch (IOException e1) {
+            return false;
+        } catch (CertificateException e2) {
+            return false;
+        }
+    }
+
+    /**
+     * Install a key into the keystore.
+     *
+     * @param name The alias name of the key to be installed
+     * @param key The key to be installed
+     * @return true on success
+     */
+    public boolean putKeyInKeyStore(String name, Key key) {
+        byte[] privKeyData = key.getEncoded();
+        return mKeyStore.importKey(name, privKeyData, Process.WIFI_UID, KeyStore.FLAG_NONE);
+    }
+
+    /**
+     * Remove a certificate or key entry specified by the alias name from the keystore.
+     *
+     * @param name The alias name of the entry to be removed
+     * @return true on success
+     */
+    public boolean removeEntryFromKeyStore(String name) {
+        return mKeyStore.delete(name, Process.WIFI_UID);
+    }
+
+    /**
+     * 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 0885e46..4cc8a20 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
      */
@@ -78,11 +77,11 @@
     // successfully connecting or a new network (SSID) becomes available to connect to.
     private boolean mWatchdogAllowedToTrigger = true;
 
+    private SelfRecovery mSelfRecovery;
     private WifiMetrics mWifiMetrics;
 
-    private WifiController mWifiController = null;
-
-    WifiLastResortWatchdog(WifiMetrics wifiMetrics) {
+    WifiLastResortWatchdog(SelfRecovery selfRecovery, WifiMetrics wifiMetrics) {
+        mSelfRecovery = selfRecovery;
         mWifiMetrics = wifiMetrics;
     }
 
@@ -95,7 +94,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 +106,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 +164,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 +180,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,11 +189,14 @@
 
         // 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);
-            restartWifiStack();
+            Log.e(TAG, "Watchdog triggering recovery");
+            mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
             // increment various watchdog trigger count stats
             incrementWifiMetricsTriggerCounts();
             clearAllFailureCounts();
@@ -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 (!mWatchdogAllowedToTrigger) {
@@ -234,7 +239,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 + "]");
         }
@@ -255,9 +260,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;
@@ -273,22 +276,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 + "]");
             }
@@ -303,7 +302,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;
@@ -325,35 +324,17 @@
         }
         // 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;
     }
 
     /**
-     * Trigger a restart of the wifi stack.
-     */
-    private void restartWifiStack() {
-        if (VDBG) Log.v(TAG, "restartWifiStack.");
-
-        // First verify that we can send the trigger message.
-        if (mWifiController == null) {
-            Log.e(TAG, "WifiLastResortWatchdog unable to trigger: WifiController is null");
-            return;
-        }
-
-        if (DBG) Log.d(TAG, toString());
-
-        mWifiController.sendMessage(WifiController.CMD_RESTART_WIFI);
-        Log.i(TAG, "Triggered WiFi stack restart.");
-    }
-
-    /**
      * 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());
@@ -385,7 +366,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();
@@ -409,7 +390,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;
     }
 
@@ -423,14 +404,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();
@@ -463,9 +445,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;
@@ -481,6 +461,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).
@@ -543,24 +531,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/WifiLog.java b/service/java/com/android/server/wifi/WifiLog.java
new file mode 100644
index 0000000..24dcda6
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiLog.java
@@ -0,0 +1,250 @@
+/*
+ * 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.annotation.NonNull;
+
+import javax.annotation.CheckReturnValue;
+
+/**
+ * Provides an abstraction of logging back-ends.
+ *
+ * The abstraction is designed to
+ * a) minimize the cost of disabled log messages,
+ * b) allow callers to tag message parameters as containing sensitive
+ *    information,
+ * c) avoid the use of format codes, and
+ * d) easily support additional data types.
+ *
+ * Implementations of WifiLog may or may not be thread-safe.
+ * Implementations of LogMessage are expected _not_ to be thread-safe,
+ * as LogMessage instances are not expected to be shared between threads.
+ */
+public interface WifiLog {
+    char PLACEHOLDER = '%';
+
+    // New-style API.
+    /**
+     * Allocate an error-level log message, which the caller will fill with
+     * additional parameters according to |format|. After filling the message
+     * with parameters, the caller must call flush(), to actually log the message.
+     *
+     * Error-level messages should be used when a malfunction has occurred,
+     * and the malfunction is likely to cause an externally visible problem.
+     * For example: we failed to initialize the Wifi interface.
+     *
+     * Typical usage is as follows:
+     *     WifiDevice() {
+     *         mLog = new LogcatLog("ModuleName");
+     *     }
+     *
+     *     void start() {
+     *         // ...
+     *         mLog.err("error % while starting interface %").c(errNum).c(ifaceName).flush();
+     *     }
+     *
+     *     void stop() {
+     *         // ...
+     *         mLog.err("error % while stopping interface %").c(errNum).c(ifaceName).flush();
+     *     }
+     */
+    @CheckReturnValue
+    @NonNull
+    LogMessage err(@NonNull String format);
+
+    /**
+     * Like {@link #err(String) err()}, except that a warning-level message is
+     * allocated.
+     *
+     * Warning-level messages should be used when a malfunction has occurred,
+     * but the malfunction is _unlikely_ to cause an externally visible problem
+     * on its own. For example: if we fail to start the debugging subsystem.
+     */
+    @CheckReturnValue
+    @NonNull
+    LogMessage warn(@NonNull String format);
+
+    /**
+     * Like {@link #err(String) err()}, except that a info-level message is
+     * allocated.
+     *
+     * Info-level messages should be used to report progress or status messages
+     * that help understand the program's external behavior. For example: we
+     * might log an info message before initiating a Wifi association.
+     */
+    @CheckReturnValue
+    @NonNull
+    LogMessage info(@NonNull String format);
+
+    /**
+     * Like {@link #err(String) err()}, except that a trace-level message is
+     * allocated.
+     *
+     * Trace-level messages should be used to report progress or status messages
+     * that help understand the program's internal behavior. For example:
+     * "Reached myCoolMethod()".
+     */
+    @CheckReturnValue
+    @NonNull
+    LogMessage trace(@NonNull String format);
+
+    /**
+     * Like {@link #err(String) err()}, except that a dump-level message is
+     * allocated.
+     *
+     * Dump-level messages should be used to report detailed internal state.
+     */
+    @CheckReturnValue
+    @NonNull
+    LogMessage dump(@NonNull String format);
+
+    /**
+     * Log a warning using the default tag for this WifiLog instance. Mark
+     * the message as 'clean' (i.e. _not_ containing any sensitive data).
+     *
+     * NOTE: this method should only be used for literal strings. For messages with
+     * parameters, use err().
+     *
+     * @param msg the message to be logged
+     */
+    void eC(String msg);
+
+    /**
+     * Like {@link #eC(String)} eC()}, except that a warning-level message
+     * is logged.
+     */
+    void wC(String msg);
+
+    /**
+     * Like {@link #eC(String)} eC()}, except that an info-level message
+     * is logged.
+     */
+    void iC(String msg);
+
+    /**
+     * Like {@link #eC(String)} eC()}, except that a trace-level message
+     * is logged.
+     */
+    void tC(String msg);
+
+    /**
+     * Note: dC() is deliberately omitted, as "dumping" is inherently at
+     * odds with the intention that the caller pass in a literal string.
+     */
+
+    /**
+     * Represents a single log message.
+     *
+     * Implementations are expected _not_ to be thread-safe.
+     */
+    interface LogMessage {
+        /**
+         * Replace the first available placeholder in this LogMessage's format
+         * with the specified value. Mark the value as 'raw', to inform the
+         * logging daemon that the value may contain sensitive data.
+         *
+         * @return |this|, to allow chaining of calls
+         */
+        @CheckReturnValue
+        @NonNull
+        LogMessage r(String value);
+
+        /**
+         * Like {@link #r(String) r()}, except that the value is marked
+         * as 'clean', to inform the logging daemon that the value does _not_
+         * contain sensitive data.
+         */
+        @CheckReturnValue
+        @NonNull
+        LogMessage c(String value);
+
+        /**
+         * Like {@link #c(String) c(String)}, except that the value is a long.
+         */
+        @CheckReturnValue
+        @NonNull
+        LogMessage c(long value);
+
+        /**
+         * Like {@link #c(String) c(String)}, except that the value is a char.
+         */
+        @CheckReturnValue
+        @NonNull
+        LogMessage c(char value);
+
+        /**
+         * Like {@link #c(String) c(String)}, except that the value is a boolean.
+         */
+        @CheckReturnValue
+        @NonNull
+        LogMessage c(boolean value);
+
+        /**
+         * Write this LogMessage to the logging daemon. Writing the
+         * message is best effort. More specifically:
+         * 1) The operation is non-blocking. If we’re unable to write
+         *    the log message to the IPC channel, the message is
+         *    dropped silently.
+         * 2) If the number of |value|s provided exceeds the number of
+         *    placeholders in the |format|, then extraneous |value|s
+         *    are silently dropped.
+         * 3) If the number of placeholders in the |format| exceeds
+         *    the number of |value|s provided, the message is sent to
+         *    the logging daemon without generating an Exception.
+         * 4) If the total message length exceeds the logging
+         *    protocol’s maximum message length, the message is
+         *    silently truncated.
+         */
+        void flush();
+    }
+
+    // Legacy API.
+    /**
+     * Log an error using the default tag for this WifiLog instance.
+     * @param msg the message to be logged
+     * TODO(b/30736737): Remove this method, once all code has migrated to alternatives.
+     */
+    void e(String msg);
+
+    /**
+     * Log a warning using the default tag for this WifiLog instance.
+     * @param msg the message to be logged
+     * TODO(b/30736737): Remove this method, once all code has migrated to alternatives.
+     */
+    void w(String msg);
+
+    /**
+     * Log an informational message using the default tag for this WifiLog instance.
+     * @param msg the message to be logged
+     * TODO(b/30736737): Remove this method, once all code has migrated to alternatives.
+     */
+    void i(String msg);
+
+    /**
+     * Log a debug message using the default tag for this WifiLog instance.
+     * @param msg the message to be logged
+     * TODO(b/30736737): Remove this method, once all code has migrated to alternatives.
+     */
+    void d(String msg);
+
+    /**
+     * Log a verbose message using the default tag for this WifiLog instance.
+     * @param msg the message to be logged
+     * TODO(b/30736737): Remove this method, once all code has migrated to alternatives.
+     */
+    void v(String msg);
+}
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 6583db2..92a6fd6 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -16,20 +16,33 @@
 
 package com.android.server.wifi;
 
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback;
 import android.net.NetworkAgent;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.util.Base64;
 import android.util.Log;
 import android.util.SparseIntArray;
 
 import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
+import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
 import com.android.server.wifi.util.InformationElementUtil;
+import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Calendar;
+import java.util.LinkedList;
 import java.util.List;
 
 /**
@@ -48,6 +61,10 @@
      */
     private static final int MAX_RSSI_POLL = 0;
     private static final int MIN_RSSI_POLL = -127;
+    public static final int MAX_RSSI_DELTA = 127;
+    public static final int MIN_RSSI_DELTA = -127;
+    /** Maximum time period between ScanResult and RSSI poll to generate rssi delta datapoint */
+    public static final long TIMEOUT_RSSI_DELTA_MILLIS =  3000;
     private static final int MIN_WIFI_SCORE = 0;
     private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
     private final Object mLock = new Object();
@@ -55,6 +72,7 @@
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
+    private Handler mHandler;
     /**
      * Metrics are stored within an instance of the WifiLog proto during runtime,
      * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during
@@ -81,15 +99,24 @@
     private final SparseIntArray mWifiSystemStateEntries = new SparseIntArray();
     /** Mapping of RSSI values to counts. */
     private final SparseIntArray mRssiPollCounts = new SparseIntArray();
+    /** Mapping of RSSI scan-poll delta values to counts. */
+    private final SparseIntArray mRssiDeltaCounts = new SparseIntArray();
+    /** RSSI of the scan result for the last connection event*/
+    private int mScanResultRssi = 0;
+    /** Boot-relative timestamp when the last candidate scanresult was received, used to calculate
+        RSSI deltas. -1 designates no candidate scanResult being tracked */
+    private long mScanResultRssiTimestampMillis = -1;
     /** Mapping of alert reason to the respective alert count. */
     private final SparseIntArray mWifiAlertReasonCounts = 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 long mRecordStartTimeSec;
     /** Mapping of Wifi Scores to counts */
     private final SparseIntArray mWifiScoreCounts = new SparseIntArray();
+    /** Mapping of SoftApManager start SoftAp return codes to counts */
+    private final SparseIntArray mSoftApManagerReturnCodeCounts = new SparseIntArray();
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -317,12 +344,20 @@
         }
     }
 
-    public WifiMetrics(Clock clock) {
+    public WifiMetrics(Clock clock, Looper looper) {
         mClock = clock;
         mCurrentConnectionEvent = null;
         mScreenOn = true;
         mWifiState = WifiMetricsProto.WifiLog.WIFI_DISABLED;
-        mRecordStartTimeSec = mClock.elapsedRealtime() / 1000;
+        mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000;
+
+        mHandler = new Handler(looper) {
+            public void handleMessage(Message msg) {
+                synchronized (mLock) {
+                    processMessage(msg);
+                }
+            }
+        };
     }
 
     // Values used for indexing SystemStateEntries
@@ -365,15 +400,26 @@
             }
             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);
+            mScanResultRssiTimestampMillis = -1;
+            if (config != null) {
+                ScanResult candidate = config.getNetworkSelectionStatus().getCandidate();
+                if (candidate != null) {
+                    // Cache the RSSI of the candidate, as the connection event level is updated
+                    // from other sources (polls, bssid_associations) and delta requires the
+                    // scanResult rssi
+                    mScanResultRssi = candidate.level;
+                    mScanResultRssiTimestampMillis = mClock.getElapsedSinceBootMillis();
+                }
+            }
         }
     }
 
@@ -423,7 +469,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);
@@ -432,6 +478,9 @@
                         connectivityFailureCode;
                 // ConnectionEvent already added to ConnectionEvents List. Safe to null current here
                 mCurrentConnectionEvent = null;
+                if (!result) {
+                    mScanResultRssiTimestampMillis = -1;
+                }
             }
         }
     }
@@ -482,13 +531,13 @@
                 WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
         mCurrentConnectionEvent.mConfigBssid = scanResult.BSSID;
         if (scanResult.capabilities != null) {
-            if (scanResult.capabilities.contains("WEP")) {
+            if (ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
                 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
                         WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
-            } else if (scanResult.capabilities.contains("PSK")) {
+            } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)) {
                 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
                         WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
-            } else if (scanResult.capabilities.contains("EAP")) {
+            } else if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) {
                 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
                         WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
             }
@@ -497,54 +546,6 @@
                 scanResult.frequency;
     }
 
-    void setNumSavedNetworks(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numSavedNetworks = num;
-        }
-    }
-
-    void setNumOpenNetworks(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numOpenNetworks = num;
-        }
-    }
-
-    void setNumPersonalNetworks(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numPersonalNetworks = num;
-        }
-    }
-
-    void setNumEnterpriseNetworks(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numEnterpriseNetworks = num;
-        }
-    }
-
-    void setNumHiddenNetworks(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numHiddenNetworks = num;
-        }
-    }
-
-    void setNumPasspointNetworks(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numPasspointNetworks = num;
-        }
-    }
-
-    void setNumNetworksAddedByUser(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numNetworksAddedByUser = num;
-        }
-    }
-
-    void setNumNetworksAddedByApps(int num) {
-        synchronized (mLock) {
-            mWifiLogProto.numNetworksAddedByApps = num;
-        }
-    }
-
     void setIsLocationEnabled(boolean enabled) {
         synchronized (mLock) {
             mWifiLogProto.isLocationEnabled = enabled;
@@ -814,6 +815,16 @@
     }
 
     /**
+     * Increment various poll related metrics, and cache performance data for StaEvent logging
+     */
+    public void handlePollResult(WifiInfo wifiInfo) {
+        mLastPollRssi = wifiInfo.getRssi();
+        mLastPollLinkSpeed = wifiInfo.getLinkSpeed();
+        mLastPollFreq = wifiInfo.getFrequency();
+        incrementRssiPollRssiCount(mLastPollRssi);
+    }
+
+    /**
      * Increment occurence count of RSSI level from RSSI poll.
      * Ignores rssi values outside the bounds of [MIN_RSSI_POLL, MAX_RSSI_POLL]
      */
@@ -824,6 +835,26 @@
         synchronized (mLock) {
             int count = mRssiPollCounts.get(rssi);
             mRssiPollCounts.put(rssi, count + 1);
+            maybeIncrementRssiDeltaCount(rssi - mScanResultRssi);
+        }
+    }
+
+    /**
+     * Increment occurence count of difference between scan result RSSI and the first RSSI poll.
+     * Ignores rssi values outside the bounds of [MIN_RSSI_DELTA, MAX_RSSI_DELTA]
+     * mLock must be held when calling this method.
+     */
+    private void maybeIncrementRssiDeltaCount(int rssi) {
+        // Check if this RSSI poll is close enough to a scan result RSSI to log a delta value
+        if (mScanResultRssiTimestampMillis >= 0) {
+            long timeDelta = mClock.getElapsedSinceBootMillis() - mScanResultRssiTimestampMillis;
+            if (timeDelta <= TIMEOUT_RSSI_DELTA_MILLIS) {
+                if (rssi >= MIN_RSSI_DELTA && rssi <= MAX_RSSI_DELTA) {
+                    int count = mRssiDeltaCounts.get(rssi);
+                    mRssiDeltaCounts.put(rssi, count + 1);
+                }
+            }
+            mScanResultRssiTimestampMillis = -1;
         }
     }
 
@@ -883,10 +914,10 @@
                 }
             }
             if (scanResult != null && scanResult.capabilities != null) {
-                if (scanResult.capabilities.contains("EAP")) {
+                if (ScanResultUtil.isScanResultForEapNetwork(scanResult)) {
                     enterpriseNetworks++;
-                } else if (scanResult.capabilities.contains("PSK")
-                        || scanResult.capabilities.contains("WEP")) {
+                } else if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
+                        || ScanResultUtil.isScanResultForWepNetwork(scanResult)) {
                     personalNetworks++;
                 } else {
                     openNetworks++;
@@ -920,6 +951,78 @@
         }
     }
 
+    /**
+     * Increments occurence of the results from attempting to start SoftAp.
+     * Maps the |result| and WifiManager |failureCode| constant to proto defined SoftApStartResult
+     * codes.
+     */
+    public void incrementSoftApStartResult(boolean result, int failureCode) {
+        synchronized (mLock) {
+            if (result) {
+                int count = mSoftApManagerReturnCodeCounts.get(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_STARTED_SUCCESSFULLY);
+                mSoftApManagerReturnCodeCounts.put(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_STARTED_SUCCESSFULLY,
+                        count + 1);
+                return;
+            }
+
+            // now increment failure modes - if not explicitly handled, dump into the general
+            // error bucket.
+            if (failureCode == WifiManager.SAP_START_FAILURE_NO_CHANNEL) {
+                int count = mSoftApManagerReturnCodeCounts.get(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_NO_CHANNEL);
+                mSoftApManagerReturnCodeCounts.put(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_NO_CHANNEL,
+                        count + 1);
+            } else {
+                // failure mode not tracked at this time...  count as a general error for now.
+                int count = mSoftApManagerReturnCodeCounts.get(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_GENERAL_ERROR);
+                mSoftApManagerReturnCodeCounts.put(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_GENERAL_ERROR,
+                        count + 1);
+            }
+        }
+    }
+
+    /**
+     * Increment number of times the HAL crashed.
+     */
+    public void incrementNumHalCrashes() {
+        synchronized (mLock) {
+            mWifiLogProto.numHalCrashes++;
+        }
+    }
+
+    /**
+     * Increment number of times the Wificond crashed.
+     */
+    public void incrementNumWificondCrashes() {
+        synchronized (mLock) {
+            mWifiLogProto.numWificondCrashes++;
+        }
+    }
+
+    /**
+     * Increment number of times the wifi on failed due to an error in HAL.
+     */
+    public void incrementNumWifiOnFailureDueToHal() {
+        synchronized (mLock) {
+            mWifiLogProto.numWifiOnFailureDueToHal++;
+        }
+    }
+
+    /**
+     * Increment number of times the wifi on failed due to an error in wificond.
+     */
+    public void incrementNumWifiOnFailureDueToWificond() {
+        synchronized (mLock) {
+            mWifiLogProto.numWifiOnFailureDueToWificond++;
+        }
+    }
+
+
     public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
     public static final String CLEAN_DUMP_ARG = "clean";
 
@@ -933,7 +1036,7 @@
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mLock) {
-            if (args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) {
+            if (args != null && args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) {
                 // Dump serialized WifiLog proto
                 consolidateProto(true);
                 for (ConnectionEvent event : mConnectionEventList) {
@@ -1050,7 +1153,7 @@
                 pw.println("mWifiLogProto.numLastResortWatchdogSuccesses="
                         + mWifiLogProto.numLastResortWatchdogSuccesses);
                 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();
@@ -1058,6 +1161,13 @@
                     sb.append(mRssiPollCounts.get(i) + " ");
                 }
                 pw.println("  " + sb.toString());
+                pw.println("mWifiLogProto.rssiPollDeltaCount: Printing counts for ["
+                        + MIN_RSSI_DELTA + ", " + MAX_RSSI_DELTA + "]");
+                sb.setLength(0);
+                for (int i = MIN_RSSI_DELTA; i <= MAX_RSSI_DELTA; i++) {
+                    sb.append(mRssiDeltaCounts.get(i) + " ");
+                }
+                pw.println("  " + sb.toString());
                 pw.print("mWifiLogProto.alertReasonCounts=");
                 sb.setLength(0);
                 for (int i = WifiLoggerHal.WIFI_ALERT_REASON_MIN;
@@ -1093,7 +1203,65 @@
                 for (int i = 0; i <= MAX_WIFI_SCORE; i++) {
                     pw.print(mWifiScoreCounts.get(i) + " ");
                 }
+                pw.println(); // add a line after wifi scores
+                pw.println("mWifiLogProto.SoftApManagerReturnCodeCounts:");
+                pw.println("  SUCCESS: " + mSoftApManagerReturnCodeCounts.get(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_STARTED_SUCCESSFULLY));
+                pw.println("  FAILED_GENERAL_ERROR: " + mSoftApManagerReturnCodeCounts.get(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_GENERAL_ERROR));
+                pw.println("  FAILED_NO_CHANNEL: " + mSoftApManagerReturnCodeCounts.get(
+                        WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_NO_CHANNEL));
                 pw.print("\n");
+                pw.println("mWifiLogProto.numHalCrashes="
+                        + mWifiLogProto.numHalCrashes);
+                pw.println("mWifiLogProto.numWificondCrashes="
+                        + mWifiLogProto.numWificondCrashes);
+                pw.println("mWifiLogProto.numWifiOnFailureDueToHal="
+                        + mWifiLogProto.numWifiOnFailureDueToHal);
+                pw.println("mWifiLogProto.numWifiOnFailureDueToWificond="
+                        + mWifiLogProto.numWifiOnFailureDueToWificond);
+                pw.println("StaEventList:");
+                for (StaEvent event : mStaEventList) {
+                    pw.println(staEventToString(event));
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Update various counts of saved network types
+     * @param networks List of WifiConfigurations representing all saved networks, must not be null
+     */
+    public void updateSavedNetworks(List<WifiConfiguration> networks) {
+        synchronized (mLock) {
+            mWifiLogProto.numSavedNetworks = networks.size();
+            mWifiLogProto.numOpenNetworks = 0;
+            mWifiLogProto.numPersonalNetworks = 0;
+            mWifiLogProto.numEnterpriseNetworks = 0;
+            mWifiLogProto.numNetworksAddedByUser = 0;
+            mWifiLogProto.numNetworksAddedByApps = 0;
+            mWifiLogProto.numHiddenNetworks = 0;
+            mWifiLogProto.numPasspointNetworks = 0;
+            for (WifiConfiguration config : networks) {
+                if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
+                    mWifiLogProto.numOpenNetworks++;
+                } else if (config.isEnterprise()) {
+                    mWifiLogProto.numEnterpriseNetworks++;
+                } else {
+                    mWifiLogProto.numPersonalNetworks++;
+                }
+                if (config.selfAdded) {
+                    mWifiLogProto.numNetworksAddedByUser++;
+                } else {
+                    mWifiLogProto.numNetworksAddedByApps++;
+                }
+                if (config.hiddenSSID) {
+                    mWifiLogProto.numHiddenNetworks++;
+                }
+                if (config.isPasspoint()) {
+                    mWifiLogProto.numPasspointNetworks++;
+                }
             }
         }
     }
@@ -1107,6 +1275,7 @@
     private void consolidateProto(boolean incremental) {
         List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>();
         List<WifiMetricsProto.RssiPollCount> rssis = new ArrayList<>();
+        List<WifiMetricsProto.RssiPollCount> rssiDeltas = new ArrayList<>();
         List<WifiMetricsProto.AlertReasonCount> alertReasons = new ArrayList<>();
         List<WifiMetricsProto.WifiScoreCount> scores = new ArrayList<>();
         synchronized (mLock) {
@@ -1152,7 +1321,7 @@
                 mWifiLogProto.wifiSystemStateEntries[i].isScreenOn =
                         (mWifiSystemStateEntries.keyAt(i) % 2) > 0;
             }
-            mWifiLogProto.recordDurationSec = (int) ((mClock.elapsedRealtime() / 1000)
+            mWifiLogProto.recordDurationSec = (int) ((mClock.getElapsedSinceBootMillis() / 1000)
                     - mRecordStartTimeSec);
 
             /**
@@ -1168,6 +1337,18 @@
             mWifiLogProto.rssiPollRssiCount = rssis.toArray(mWifiLogProto.rssiPollRssiCount);
 
             /**
+             * Convert the SparseIntArray of RSSI delta rssi's and counts to the proto's repeated
+             * IntKeyVal array.
+             */
+            for (int i = 0; i < mRssiDeltaCounts.size(); i++) {
+                WifiMetricsProto.RssiPollCount keyVal = new WifiMetricsProto.RssiPollCount();
+                keyVal.rssi = mRssiDeltaCounts.keyAt(i);
+                keyVal.count = mRssiDeltaCounts.valueAt(i);
+                rssiDeltas.add(keyVal);
+            }
+            mWifiLogProto.rssiPollDeltaCount = rssiDeltas.toArray(mWifiLogProto.rssiPollDeltaCount);
+
+            /**
              * Convert the SparseIntArray of alert reasons and counts to the proto's repeated
              * IntKeyVal array.
              */
@@ -1178,6 +1359,7 @@
                 alertReasons.add(keyVal);
             }
             mWifiLogProto.alertReasonCount = alertReasons.toArray(mWifiLogProto.alertReasonCount);
+
             /**
             *  Convert the SparseIntArray of Wifi Score and counts to proto's repeated
             * IntKeyVal array.
@@ -1189,6 +1371,23 @@
                 scores.add(keyVal);
             }
             mWifiLogProto.wifiScoreCount = scores.toArray(mWifiLogProto.wifiScoreCount);
+
+            /**
+             * Convert the SparseIntArray of SoftAp Return codes and counts to proto's repeated
+             * IntKeyVal array.
+             */
+            int codeCounts = mSoftApManagerReturnCodeCounts.size();
+            mWifiLogProto.softApReturnCode = new WifiMetricsProto.SoftApReturnCodeCount[codeCounts];
+            for (int sapCode = 0; sapCode < codeCounts; sapCode++) {
+                mWifiLogProto.softApReturnCode[sapCode] =
+                        new WifiMetricsProto.SoftApReturnCodeCount();
+                mWifiLogProto.softApReturnCode[sapCode].startResult =
+                        mSoftApManagerReturnCodeCounts.keyAt(sapCode);
+                mWifiLogProto.softApReturnCode[sapCode].count =
+                        mSoftApManagerReturnCodeCounts.valueAt(sapCode);
+            }
+
+            mWifiLogProto.staEventList = mStaEventList.toArray(mWifiLogProto.staEventList);
         }
     }
 
@@ -1203,11 +1402,15 @@
             }
             mScanReturnEntries.clear();
             mWifiSystemStateEntries.clear();
-            mRecordStartTimeSec = mClock.elapsedRealtime() / 1000;
+            mRecordStartTimeSec = mClock.getElapsedSinceBootMillis() / 1000;
             mRssiPollCounts.clear();
+            mRssiDeltaCounts.clear();
             mWifiAlertReasonCounts.clear();
             mWifiScoreCounts.clear();
             mWifiLogProto.clear();
+            mScanResultRssiTimestampMillis = -1;
+            mSoftApManagerReturnCodeCounts.clear();
+            mStaEventList.clear();
         }
     }
 
@@ -1228,4 +1431,370 @@
             mWifiState = wifiState;
         }
     }
+
+    /**
+     * Message handler for interesting WifiMonitor messages. Generates StaEvents
+     */
+    private void processMessage(Message msg) {
+        StaEvent event = new StaEvent();
+        boolean logEvent = true;
+        switch (msg.what) {
+            case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
+                event.type = StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT;
+                event.associationTimedOut = msg.arg1 > 0 ? true : false;
+                event.status = msg.arg2;
+                break;
+            case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
+                event.type = StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT;
+                switch (msg.arg2) {
+                    case WifiManager.ERROR_AUTH_FAILURE_NONE:
+                        event.authFailureReason = StaEvent.AUTH_FAILURE_NONE;
+                        break;
+                    case WifiManager.ERROR_AUTH_FAILURE_TIMEOUT:
+                        event.authFailureReason = StaEvent.AUTH_FAILURE_TIMEOUT;
+                        break;
+                    case WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD:
+                        event.authFailureReason = StaEvent.AUTH_FAILURE_WRONG_PSWD;
+                        break;
+                    case WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE:
+                        event.authFailureReason = StaEvent.AUTH_FAILURE_EAP_FAILURE;
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case WifiMonitor.NETWORK_CONNECTION_EVENT:
+                event.type = StaEvent.TYPE_NETWORK_CONNECTION_EVENT;
+                break;
+            case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
+                event.type = StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT;
+                event.reason = msg.arg2;
+                event.localGen = msg.arg1 == 0 ? false : true;
+                break;
+            case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
+                logEvent = false;
+                StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
+                mSupplicantStateChangeBitmask |= supplicantStateToBit(stateChangeResult.state);
+                break;
+            case WifiStateMachine.CMD_ASSOCIATED_BSSID:
+                event.type = StaEvent.TYPE_CMD_ASSOCIATED_BSSID;
+                break;
+            case WifiStateMachine.CMD_TARGET_BSSID:
+                event.type = StaEvent.TYPE_CMD_TARGET_BSSID;
+                break;
+            default:
+                return;
+        }
+        if (logEvent) {
+            addStaEvent(event);
+        }
+    }
+    /**
+     * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant
+     * generated event types, which are logged through 'sendMessage'
+     * @param type StaEvent.EventType describing the event
+     */
+    public void logStaEvent(int type) {
+        logStaEvent(type, StaEvent.DISCONNECT_UNKNOWN, null);
+    }
+    /**
+     * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant
+     * generated event types, which are logged through 'sendMessage'
+     * @param type StaEvent.EventType describing the event
+     * @param config WifiConfiguration for a framework initiated connection attempt
+     */
+    public void logStaEvent(int type, WifiConfiguration config) {
+        logStaEvent(type, StaEvent.DISCONNECT_UNKNOWN, config);
+    }
+    /**
+     * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant
+     * generated event types, which are logged through 'sendMessage'
+     * @param type StaEvent.EventType describing the event
+     * @param frameworkDisconnectReason StaEvent.FrameworkDisconnectReason explaining why framework
+     *                                  initiated a FRAMEWORK_DISCONNECT
+     */
+    public void logStaEvent(int type, int frameworkDisconnectReason) {
+        logStaEvent(type, frameworkDisconnectReason, null);
+    }
+    /**
+     * Log a StaEvent from WifiStateMachine. The StaEvent must not be one of the supplicant
+     * generated event types, which are logged through 'sendMessage'
+     * @param type StaEvent.EventType describing the event
+     * @param frameworkDisconnectReason StaEvent.FrameworkDisconnectReason explaining why framework
+     *                                  initiated a FRAMEWORK_DISCONNECT
+     * @param config WifiConfiguration for a framework initiated connection attempt
+     */
+    public void logStaEvent(int type, int frameworkDisconnectReason, WifiConfiguration config) {
+        switch (type) {
+            case StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL:
+            case StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST:
+            case StaEvent.TYPE_CMD_IP_REACHABILITY_LOST:
+            case StaEvent.TYPE_CMD_START_CONNECT:
+            case StaEvent.TYPE_CMD_START_ROAM:
+            case StaEvent.TYPE_CONNECT_NETWORK:
+            case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
+            case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
+                break;
+            default:
+                Log.e(TAG, "Unknown StaEvent:" + type);
+                return;
+        }
+        StaEvent event = new StaEvent();
+        event.type = type;
+        if (frameworkDisconnectReason != StaEvent.DISCONNECT_UNKNOWN) {
+            event.frameworkDisconnectReason = frameworkDisconnectReason;
+        }
+        event.configInfo = createConfigInfo(config);
+        addStaEvent(event);
+    }
+
+    private void addStaEvent(StaEvent staEvent) {
+        staEvent.startTimeMillis = mClock.getElapsedSinceBootMillis();
+        staEvent.lastRssi = mLastPollRssi;
+        staEvent.lastFreq = mLastPollFreq;
+        staEvent.lastLinkSpeed = mLastPollLinkSpeed;
+        staEvent.supplicantStateChangesBitmask = mSupplicantStateChangeBitmask;
+        mSupplicantStateChangeBitmask = 0;
+        mLastPollRssi = -127;
+        mLastPollFreq = -1;
+        mLastPollLinkSpeed = -1;
+        mStaEventList.add(staEvent);
+        // Prune StaEventList if it gets too long
+        if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
+    }
+
+    private ConfigInfo createConfigInfo(WifiConfiguration config) {
+        if (config == null) return null;
+        ConfigInfo info = new ConfigInfo();
+        info.allowedKeyManagement = bitSetToInt(config.allowedKeyManagement);
+        info.allowedProtocols = bitSetToInt(config.allowedProtocols);
+        info.allowedAuthAlgorithms = bitSetToInt(config.allowedAuthAlgorithms);
+        info.allowedPairwiseCiphers = bitSetToInt(config.allowedPairwiseCiphers);
+        info.allowedGroupCiphers = bitSetToInt(config.allowedGroupCiphers);
+        info.hiddenSsid = config.hiddenSSID;
+        info.isPasspoint = config.isPasspoint();
+        info.isEphemeral = config.isEphemeral();
+        info.hasEverConnected = config.getNetworkSelectionStatus().getHasEverConnected();
+        ScanResult candidate = config.getNetworkSelectionStatus().getCandidate();
+        if (candidate != null) {
+            info.scanRssi = candidate.level;
+            info.scanFreq = candidate.frequency;
+        }
+        return info;
+    }
+
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    // Rather than generate a StaEvent for each SUPPLICANT_STATE_CHANGE, cache these in a bitmask
+    // and attach it to the next event which is generated.
+    private int mSupplicantStateChangeBitmask = 0;
+
+    /**
+     * Converts a SupplicantState value to a single bit, with position defined by
+     * {@code StaEvent.SupplicantState}
+     */
+    public static int supplicantStateToBit(SupplicantState state) {
+        switch(state) {
+            case DISCONNECTED:
+                return 1 << StaEvent.STATE_DISCONNECTED;
+            case INTERFACE_DISABLED:
+                return 1 << StaEvent.STATE_INTERFACE_DISABLED;
+            case INACTIVE:
+                return 1 << StaEvent.STATE_INACTIVE;
+            case SCANNING:
+                return 1 << StaEvent.STATE_SCANNING;
+            case AUTHENTICATING:
+                return 1 << StaEvent.STATE_AUTHENTICATING;
+            case ASSOCIATING:
+                return 1 << StaEvent.STATE_ASSOCIATING;
+            case ASSOCIATED:
+                return 1 << StaEvent.STATE_ASSOCIATED;
+            case FOUR_WAY_HANDSHAKE:
+                return 1 << StaEvent.STATE_FOUR_WAY_HANDSHAKE;
+            case GROUP_HANDSHAKE:
+                return 1 << StaEvent.STATE_GROUP_HANDSHAKE;
+            case COMPLETED:
+                return 1 << StaEvent.STATE_COMPLETED;
+            case DORMANT:
+                return 1 << StaEvent.STATE_DORMANT;
+            case UNINITIALIZED:
+                return 1 << StaEvent.STATE_UNINITIALIZED;
+            case INVALID:
+                return 1 << StaEvent.STATE_INVALID;
+            default:
+                Log.wtf(TAG, "Got unknown supplicant state: " + state.ordinal());
+                return 0;
+        }
+    }
+
+    private static String supplicantStateChangesBitmaskToString(int mask) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SUPPLICANT_STATE_CHANGE_EVENTS: {");
+        if ((mask & (1 << StaEvent.STATE_DISCONNECTED)) > 0) sb.append(" DISCONNECTED");
+        if ((mask & (1 << StaEvent.STATE_INTERFACE_DISABLED)) > 0) sb.append(" INTERFACE_DISABLED");
+        if ((mask & (1 << StaEvent.STATE_INACTIVE)) > 0) sb.append(" INACTIVE");
+        if ((mask & (1 << StaEvent.STATE_SCANNING)) > 0) sb.append(" SCANNING");
+        if ((mask & (1 << StaEvent.STATE_AUTHENTICATING)) > 0) sb.append(" AUTHENTICATING");
+        if ((mask & (1 << StaEvent.STATE_ASSOCIATING)) > 0) sb.append(" ASSOCIATING");
+        if ((mask & (1 << StaEvent.STATE_ASSOCIATED)) > 0) sb.append(" ASSOCIATED");
+        if ((mask & (1 << StaEvent.STATE_FOUR_WAY_HANDSHAKE)) > 0) sb.append(" FOUR_WAY_HANDSHAKE");
+        if ((mask & (1 << StaEvent.STATE_GROUP_HANDSHAKE)) > 0) sb.append(" GROUP_HANDSHAKE");
+        if ((mask & (1 << StaEvent.STATE_COMPLETED)) > 0) sb.append(" COMPLETED");
+        if ((mask & (1 << StaEvent.STATE_DORMANT)) > 0) sb.append(" DORMANT");
+        if ((mask & (1 << StaEvent.STATE_UNINITIALIZED)) > 0) sb.append(" UNINITIALIZED");
+        if ((mask & (1 << StaEvent.STATE_INVALID)) > 0) sb.append(" INVALID");
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Returns a human readable string from a Sta Event. Only adds information relevant to the event
+     * type.
+     */
+    public static String staEventToString(StaEvent event) {
+        if (event == null) return "<NULL>";
+        StringBuilder sb = new StringBuilder();
+        Long time = event.startTimeMillis;
+        sb.append(String.format("%9d ", time.longValue())).append(" ");
+        switch (event.type) {
+            case StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT:
+                sb.append("ASSOCIATION_REJECTION_EVENT:")
+                        .append(" timedOut=").append(event.associationTimedOut)
+                        .append(" status=").append(event.status).append(":")
+                        .append(ISupplicantStaIfaceCallback.StatusCode.toString(event.status));
+                break;
+            case StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT:
+                sb.append("AUTHENTICATION_FAILURE_EVENT: reason=").append(event.authFailureReason)
+                        .append(":").append(authFailureReasonToString(event.authFailureReason));
+                break;
+            case StaEvent.TYPE_NETWORK_CONNECTION_EVENT:
+                sb.append("NETWORK_CONNECTION_EVENT:");
+                break;
+            case StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT:
+                sb.append("NETWORK_DISCONNECTION_EVENT:")
+                        .append(" local_gen=").append(event.localGen)
+                        .append(" reason=").append(event.reason).append(":")
+                        .append(ISupplicantStaIfaceCallback.ReasonCode.toString(
+                                (event.reason >= 0 ? event.reason : -1 * event.reason)));
+                break;
+            case StaEvent.TYPE_CMD_ASSOCIATED_BSSID:
+                sb.append("CMD_ASSOCIATED_BSSID:");
+                break;
+            case StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL:
+                sb.append("CMD_IP_CONFIGURATION_SUCCESSFUL:");
+                break;
+            case StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST:
+                sb.append("CMD_IP_CONFIGURATION_LOST:");
+                break;
+            case StaEvent.TYPE_CMD_IP_REACHABILITY_LOST:
+                sb.append("CMD_IP_REACHABILITY_LOST:");
+                break;
+            case StaEvent.TYPE_CMD_TARGET_BSSID:
+                sb.append("CMD_TARGET_BSSID:");
+                break;
+            case StaEvent.TYPE_CMD_START_CONNECT:
+                sb.append("CMD_START_CONNECT:");
+                break;
+            case StaEvent.TYPE_CMD_START_ROAM:
+                sb.append("CMD_START_ROAM:");
+                break;
+            case StaEvent.TYPE_CONNECT_NETWORK:
+                sb.append("CONNECT_NETWORK:");
+                break;
+            case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
+                sb.append("NETWORK_AGENT_VALID_NETWORK:");
+                break;
+            case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
+                sb.append("FRAMEWORK_DISCONNECT:")
+                        .append(" reason=")
+                        .append(frameworkDisconnectReasonToString(event.frameworkDisconnectReason));
+                break;
+            default:
+                sb.append("UNKNOWN " + event.type + ":");
+                break;
+        }
+        if (event.lastRssi != -127) sb.append(" lastRssi=").append(event.lastRssi);
+        if (event.lastFreq != -1) sb.append(" lastFreq=").append(event.lastFreq);
+        if (event.lastLinkSpeed != -1) sb.append(" lastLinkSpeed=").append(event.lastLinkSpeed);
+        if (event.supplicantStateChangesBitmask != 0) {
+            sb.append("\n             ").append(supplicantStateChangesBitmaskToString(
+                    event.supplicantStateChangesBitmask));
+        }
+        if (event.configInfo != null) {
+            sb.append("\n             ").append(configInfoToString(event.configInfo));
+        }
+
+        return sb.toString();
+    }
+
+    private static String authFailureReasonToString(int authFailureReason) {
+        switch (authFailureReason) {
+            case StaEvent.AUTH_FAILURE_NONE:
+                return "ERROR_AUTH_FAILURE_NONE";
+            case StaEvent.AUTH_FAILURE_TIMEOUT:
+                return "ERROR_AUTH_FAILURE_TIMEOUT";
+            case StaEvent.AUTH_FAILURE_WRONG_PSWD:
+                return "ERROR_AUTH_FAILURE_WRONG_PSWD";
+            case StaEvent.AUTH_FAILURE_EAP_FAILURE:
+                return "ERROR_AUTH_FAILURE_EAP_FAILURE";
+            default:
+                return "";
+        }
+    }
+
+    private static String frameworkDisconnectReasonToString(int frameworkDisconnectReason) {
+        switch (frameworkDisconnectReason) {
+            case StaEvent.DISCONNECT_API:
+                return "DISCONNECT_API";
+            case StaEvent.DISCONNECT_GENERIC:
+                return "DISCONNECT_GENERIC";
+            case StaEvent.DISCONNECT_UNWANTED:
+                return "DISCONNECT_UNWANTED";
+            case StaEvent.DISCONNECT_ROAM_WATCHDOG_TIMER:
+                return "DISCONNECT_ROAM_WATCHDOG_TIMER";
+            case StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST:
+                return "DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST";
+            case StaEvent.DISCONNECT_RESET_SIM_NETWORKS:
+                return "DISCONNECT_RESET_SIM_NETWORKS";
+            default:
+                return "DISCONNECT_UNKNOWN=" + frameworkDisconnectReason;
+        }
+    }
+
+    private static String configInfoToString(ConfigInfo info) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("ConfigInfo:")
+                .append(" allowed_key_management=").append(info.allowedKeyManagement)
+                .append(" allowed_protocols=").append(info.allowedProtocols)
+                .append(" allowed_auth_algorithms=").append(info.allowedAuthAlgorithms)
+                .append(" allowed_pairwise_ciphers=").append(info.allowedPairwiseCiphers)
+                .append(" allowed_group_ciphers=").append(info.allowedGroupCiphers)
+                .append(" hidden_ssid=").append(info.hiddenSsid)
+                .append(" is_passpoint=").append(info.isPasspoint)
+                .append(" is_ephemeral=").append(info.isEphemeral)
+                .append(" has_ever_connected=").append(info.hasEverConnected)
+                .append(" scan_rssi=").append(info.scanRssi)
+                .append(" scan_freq=").append(info.scanFreq);
+        return sb.toString();
+    }
+
+    public static final int MAX_STA_EVENTS = 512;
+    private LinkedList<StaEvent> mStaEventList = new LinkedList<StaEvent>();
+    private int mLastPollRssi = -127;
+    private int mLastPollLinkSpeed = -1;
+    private int mLastPollFreq = -1;
+
+    /**
+     * Converts the first 31 bits of a BitSet to a little endian int
+     */
+    private static int bitSetToInt(BitSet bits) {
+        int value = 0;
+        int nBits = bits.length() < 31 ? bits.length() : 31;
+        for (int i = 0; i < nBits; i++) {
+            value += bits.get(i) ? (1 << i) : 0;
+        }
+        return value;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java
index 1f2b397..b2fc56e 100644
--- a/service/java/com/android/server/wifi/WifiMonitor.java
+++ b/service/java/com/android/server/wifi/WifiMonitor.java
@@ -16,427 +16,37 @@
 
 package com.android.server.wifi;
 
-import android.net.NetworkInfo;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiSsid;
-import android.net.wifi.p2p.WifiP2pConfig;
-import android.net.wifi.p2p.WifiP2pDevice;
-import android.net.wifi.p2p.WifiP2pGroup;
-import android.net.wifi.p2p.WifiP2pProvDiscEvent;
-import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
 import android.os.Handler;
 import android.os.Message;
-import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.Base64;
-import android.util.LocalLog;
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.server.wifi.hotspot2.IconEvent;
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.StateMachine;
+import com.android.server.wifi.hotspot2.AnqpEvent;
+import com.android.server.wifi.hotspot2.IconEvent;
+import com.android.server.wifi.hotspot2.WnmData;
+import com.android.server.wifi.util.TelephonyUtil.SimAuthRequestData;
 
-import java.io.IOException;
 import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Listens for events from the wpa_supplicant server, and passes them on
- * to the {@link StateMachine} for handling. Runs in its own thread.
+ * to the {@link StateMachine} for handling.
  *
  * @hide
  */
 public class WifiMonitor {
-
-    private static boolean DBG = false;
-    private static final boolean VDBG = false;
     private static final String TAG = "WifiMonitor";
 
-    /** Events we receive from the supplicant daemon */
-
-    private static final int CONNECTED    = 1;
-    private static final int DISCONNECTED = 2;
-    private static final int STATE_CHANGE = 3;
-    private static final int SCAN_RESULTS = 4;
-    private static final int LINK_SPEED   = 5;
-    private static final int TERMINATING  = 6;
-    private static final int DRIVER_STATE = 7;
-    private static final int EAP_FAILURE  = 8;
-    private static final int ASSOC_REJECT = 9;
-    private static final int SSID_TEMP_DISABLE = 10;
-    private static final int SSID_REENABLE = 11;
-    private static final int BSS_ADDED    = 12;
-    private static final int BSS_REMOVED  = 13;
-    private static final int UNKNOWN      = 14;
-    private static final int SCAN_FAILED  = 15;
-
-    /** All events coming from the supplicant start with this prefix */
-    private static final String EVENT_PREFIX_STR = "CTRL-EVENT-";
-    private static final int EVENT_PREFIX_LEN_STR = EVENT_PREFIX_STR.length();
-
-    /** All events coming from the supplicant start with this prefix */
-    private static final String REQUEST_PREFIX_STR = "CTRL-REQ-";
-    private static final int REQUEST_PREFIX_LEN_STR = REQUEST_PREFIX_STR.length();
-
-
-    /** All WPA events coming from the supplicant start with this prefix */
-    private static final String WPA_EVENT_PREFIX_STR = "WPA:";
-    private static final String PASSWORD_MAY_BE_INCORRECT_STR =
-       "pre-shared key may be incorrect";
-
-    /* WPS events */
-    private static final String WPS_SUCCESS_STR = "WPS-SUCCESS";
-
-    /* Format: WPS-FAIL msg=%d [config_error=%d] [reason=%d (%s)] */
-    private static final String WPS_FAIL_STR    = "WPS-FAIL";
-    private static final String WPS_FAIL_PATTERN =
-            "WPS-FAIL msg=\\d+(?: config_error=(\\d+))?(?: reason=(\\d+))?";
-
-    /* config error code values for config_error=%d */
-    private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12;
-    private static final int CONFIG_AUTH_FAILURE = 18;
-
-    /* reason code values for reason=%d */
-    private static final int REASON_TKIP_ONLY_PROHIBITED = 1;
-    private static final int REASON_WEP_PROHIBITED = 2;
-
-    private static final String WPS_OVERLAP_STR = "WPS-OVERLAP-DETECTED";
-    private static final String WPS_TIMEOUT_STR = "WPS-TIMEOUT";
-
-    /* Hotspot 2.0 ANQP query events */
-    private static final String GAS_QUERY_PREFIX_STR = "GAS-QUERY-";
-    private static final String GAS_QUERY_START_STR = "GAS-QUERY-START";
-    private static final String GAS_QUERY_DONE_STR = "GAS-QUERY-DONE";
-    private static final String RX_HS20_ANQP_ICON_STR = "RX-HS20-ANQP-ICON";
-    private static final int RX_HS20_ANQP_ICON_STR_LEN = RX_HS20_ANQP_ICON_STR.length();
-
-    /* Hotspot 2.0 events */
-    private static final String HS20_PREFIX_STR = "HS20-";
-    public static final String HS20_SUB_REM_STR = "HS20-SUBSCRIPTION-REMEDIATION";
-    public static final String HS20_DEAUTH_STR = "HS20-DEAUTH-IMMINENT-NOTICE";
-
-    private static final String IDENTITY_STR = "IDENTITY";
-
-    private static final String SIM_STR = "SIM";
-
-
-    //used to debug and detect if we miss an event
-    private static int eventLogCounter = 0;
-
-    /**
-     * Names of events from wpa_supplicant (minus the prefix). In the
-     * format descriptions, * &quot;<code>x</code>&quot;
-     * designates a dynamic value that needs to be parsed out from the event
-     * string
-     */
-    /**
-     * <pre>
-     * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed
-     * </pre>
-     * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point
-     */
-    private static final String CONNECTED_STR =    "CONNECTED";
-    private static final String ConnectPrefix = "Connection to ";
-    private static final String ConnectSuffix = " completed";
-
-    /**
-     * <pre>
-     * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys
-     * </pre>
-     */
-    private static final String DISCONNECTED_STR = "DISCONNECTED";
-    /**
-     * <pre>
-     * CTRL-EVENT-STATE-CHANGE x
-     * </pre>
-     * <code>x</code> is the numerical value of the new state.
-     */
-    private static final String STATE_CHANGE_STR =  "STATE-CHANGE";
-    /**
-     * <pre>
-     * CTRL-EVENT-SCAN-RESULTS ready
-     * </pre>
-     */
-    private static final String SCAN_RESULTS_STR =  "SCAN-RESULTS";
-
-    /**
-     * <pre>
-     * CTRL-EVENT-SCAN-FAILED ret=code[ retry=1]
-     * </pre>
-     */
-    private static final String SCAN_FAILED_STR =  "SCAN-FAILED";
-
-    /**
-     * <pre>
-     * CTRL-EVENT-LINK-SPEED x Mb/s
-     * </pre>
-     * {@code x} is the link speed in Mb/sec.
-     */
-    private static final String LINK_SPEED_STR = "LINK-SPEED";
-    /**
-     * <pre>
-     * CTRL-EVENT-TERMINATING - signal x
-     * </pre>
-     * <code>x</code> is the signal that caused termination.
-     */
-    private static final String TERMINATING_STR =  "TERMINATING";
-    /**
-     * <pre>
-     * CTRL-EVENT-DRIVER-STATE state
-     * </pre>
-     * <code>state</code> can be HANGED
-     */
-    private static final String DRIVER_STATE_STR = "DRIVER-STATE";
-    /**
-     * <pre>
-     * CTRL-EVENT-EAP-FAILURE EAP authentication failed
-     * </pre>
-     */
-    private static final String EAP_FAILURE_STR = "EAP-FAILURE";
-
-    /**
-     * This indicates an authentication failure on EAP FAILURE event
-     */
-    private static final String EAP_AUTH_FAILURE_STR = "EAP authentication failed";
-
-    /* EAP authentication timeout events */
-    private static final String AUTH_EVENT_PREFIX_STR = "Authentication with";
-    private static final String AUTH_TIMEOUT_STR = "timed out.";
-
-    /**
-     * This indicates an assoc reject event
-     */
-    private static final String ASSOC_REJECT_STR = "ASSOC-REJECT";
-
-    /**
-     * This indicates auth or association failure bad enough so as network got disabled
-     * - WPA_PSK auth failure suspecting shared key mismatch
-     * - failed multiple Associations
-     */
-    private static final String TEMP_DISABLED_STR = "SSID-TEMP-DISABLED";
-
-    /**
-     * This indicates a previously disabled SSID was reenabled by supplicant
-     */
-    private static final String REENABLED_STR = "SSID-REENABLED";
-
-    /**
-     * This indicates supplicant found a given BSS
-     */
-    private static final String BSS_ADDED_STR = "BSS-ADDED";
-
-    /**
-     * This indicates supplicant removed a given BSS
-     */
-    private static final String BSS_REMOVED_STR = "BSS-REMOVED";
-
-    /**
-     * Regex pattern for extracting an Ethernet-style MAC address from a string.
-     * Matches a strings like the following:<pre>
-     * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre>
-     */
-    private static Pattern mConnectedEventPattern =
-        Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) ");
-
-    /**
-     * Regex pattern for extracting an Ethernet-style MAC address from a string.
-     * Matches a strings like the following:<pre>
-     * CTRL-EVENT-DISCONNECTED - bssid=ac:22:0b:24:70:74 reason=3 locally_generated=1
-     */
-    private static Pattern mDisconnectedEventPattern =
-            Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) +" +
-                    "reason=([0-9]+) +locally_generated=([0-1])");
-
-    /**
-     * Regex pattern for extracting an Ethernet-style MAC address from a string.
-     * Matches a strings like the following:<pre>
-     * CTRL-EVENT-ASSOC-REJECT - bssid=ac:22:0b:24:70:74 status_code=1
-     */
-    private static Pattern mAssocRejectEventPattern =
-            Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) +" +
-                    "status_code=([0-9]+)");
-
-    /**
-     * Regex pattern for extracting an Ethernet-style MAC address from a string.
-     * Matches a strings like the following:<pre>
-     * IFNAME=wlan0 Trying to associate with 6c:f3:7f:ae:87:71
-     */
-    private static final String TARGET_BSSID_STR =  "Trying to associate with ";
-
-    private static Pattern mTargetBSSIDPattern =
-            Pattern.compile("Trying to associate with ((?:[0-9a-f]{2}:){5}[0-9a-f]{2}).*");
-
-    /**
-     * Regex pattern for extracting an Ethernet-style MAC address from a string.
-     * Matches a strings like the following:<pre>
-     * IFNAME=wlan0 Associated with 6c:f3:7f:ae:87:71
-     */
-    private static final String ASSOCIATED_WITH_STR =  "Associated with ";
-
-    private static Pattern mAssociatedPattern =
-            Pattern.compile("Associated with ((?:[0-9a-f]{2}:){5}[0-9a-f]{2}).*");
-
-    /**
-     * Regex pattern for extracting an external GSM sim authentication request from a string.
-     * Matches a strings like the following:<pre>
-     * CTRL-REQ-SIM-<network id>:GSM-AUTH:<RAND1>:<RAND2>[:<RAND3>] needed for SSID <SSID>
-     * This pattern should find
-     *    0 - id
-     *    1 - Rand1
-     *    2 - Rand2
-     *    3 - Rand3
-     *    4 - SSID
-     */
-    private static Pattern mRequestGsmAuthPattern =
-            Pattern.compile("SIM-([0-9]*):GSM-AUTH((:[0-9a-f]+)+) needed for SSID (.+)");
-
-    /**
-     * Regex pattern for extracting an external 3G sim authentication request from a string.
-     * Matches a strings like the following:<pre>
-     * CTRL-REQ-SIM-<network id>:UMTS-AUTH:<RAND>:<AUTN> needed for SSID <SSID>
-     * This pattern should find
-     *    1 - id
-     *    2 - Rand
-     *    3 - Autn
-     *    4 - SSID
-     */
-    private static Pattern mRequestUmtsAuthPattern =
-            Pattern.compile("SIM-([0-9]*):UMTS-AUTH:([0-9a-f]+):([0-9a-f]+) needed for SSID (.+)");
-
-    /**
-     * Regex pattern for extracting SSIDs from request identity string.
-     * Matches a strings like the following:<pre>
-     * CTRL-REQ-IDENTITY-xx:Identity needed for SSID XXXX</pre>
-     */
-    private static Pattern mRequestIdentityPattern =
-            Pattern.compile("IDENTITY-([0-9]+):Identity needed for SSID (.+)");
-
-    /** P2P events */
-    private static final String P2P_EVENT_PREFIX_STR = "P2P";
-
-    /* P2P-DEVICE-FOUND fa:7b:7a:42:02:13 p2p_dev_addr=fa:7b:7a:42:02:13 pri_dev_type=1-0050F204-1
-       name='p2p-TEST1' config_methods=0x188 dev_capab=0x27 group_capab=0x0 */
-    private static final String P2P_DEVICE_FOUND_STR = "P2P-DEVICE-FOUND";
-
-    /* P2P-DEVICE-LOST p2p_dev_addr=42:fc:89:e1:e2:27 */
-    private static final String P2P_DEVICE_LOST_STR = "P2P-DEVICE-LOST";
-
-    /* P2P-FIND-STOPPED */
-    private static final String P2P_FIND_STOPPED_STR = "P2P-FIND-STOPPED";
-
-    /* P2P-GO-NEG-REQUEST 42:fc:89:a8:96:09 dev_passwd_id=4 */
-    private static final String P2P_GO_NEG_REQUEST_STR = "P2P-GO-NEG-REQUEST";
-
-    private static final String P2P_GO_NEG_SUCCESS_STR = "P2P-GO-NEG-SUCCESS";
-
-    /* P2P-GO-NEG-FAILURE status=x */
-    private static final String P2P_GO_NEG_FAILURE_STR = "P2P-GO-NEG-FAILURE";
-
-    private static final String P2P_GROUP_FORMATION_SUCCESS_STR =
-            "P2P-GROUP-FORMATION-SUCCESS";
-
-    private static final String P2P_GROUP_FORMATION_FAILURE_STR =
-            "P2P-GROUP-FORMATION-FAILURE";
-
-    /* P2P-GROUP-STARTED p2p-wlan0-0 [client|GO] ssid="DIRECT-W8" freq=2437
-       [psk=2182b2e50e53f260d04f3c7b25ef33c965a3291b9b36b455a82d77fd82ca15bc|passphrase="fKG4jMe3"]
-       go_dev_addr=fa:7b:7a:42:02:13 [PERSISTENT] */
-    private static final String P2P_GROUP_STARTED_STR = "P2P-GROUP-STARTED";
-
-    /* P2P-GROUP-REMOVED p2p-wlan0-0 [client|GO] reason=REQUESTED */
-    private static final String P2P_GROUP_REMOVED_STR = "P2P-GROUP-REMOVED";
-
-    /* P2P-INVITATION-RECEIVED sa=fa:7b:7a:42:02:13 go_dev_addr=f8:7b:7a:42:02:13
-        bssid=fa:7b:7a:42:82:13 unknown-network */
-    private static final String P2P_INVITATION_RECEIVED_STR = "P2P-INVITATION-RECEIVED";
-
-    /* P2P-INVITATION-RESULT status=1 */
-    private static final String P2P_INVITATION_RESULT_STR = "P2P-INVITATION-RESULT";
-
-    /* P2P-PROV-DISC-PBC-REQ 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27
-       pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
-       group_capab=0x0 */
-    private static final String P2P_PROV_DISC_PBC_REQ_STR = "P2P-PROV-DISC-PBC-REQ";
-
-    /* P2P-PROV-DISC-PBC-RESP 02:12:47:f2:5a:36 */
-    private static final String P2P_PROV_DISC_PBC_RSP_STR = "P2P-PROV-DISC-PBC-RESP";
-
-    /* P2P-PROV-DISC-ENTER-PIN 42:fc:89:e1:e2:27 p2p_dev_addr=42:fc:89:e1:e2:27
-       pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
-       group_capab=0x0 */
-    private static final String P2P_PROV_DISC_ENTER_PIN_STR = "P2P-PROV-DISC-ENTER-PIN";
-    /* P2P-PROV-DISC-SHOW-PIN 42:fc:89:e1:e2:27 44490607 p2p_dev_addr=42:fc:89:e1:e2:27
-       pri_dev_type=1-0050F204-1 name='p2p-TEST2' config_methods=0x188 dev_capab=0x27
-       group_capab=0x0 */
-    private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN";
-    /* P2P-PROV-DISC-FAILURE p2p_dev_addr=42:fc:89:e1:e2:27 */
-    private static final String P2P_PROV_DISC_FAILURE_STR = "P2P-PROV-DISC-FAILURE";
-
-    /*
-     * Protocol format is as follows.<br>
-     * See the Table.62 in the WiFi Direct specification for the detail.
-     * ______________________________________________________________
-     * |           Length(2byte)     | Type(1byte) | TransId(1byte)}|
-     * ______________________________________________________________
-     * | status(1byte)  |            vendor specific(variable)      |
-     *
-     * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300000101
-     * length=3, service type=0(ALL Service), transaction id=1,
-     * status=1(service protocol type not available)<br>
-     *
-     * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300020201
-     * length=3, service type=2(UPnP), transaction id=2,
-     * status=1(service protocol type not available)
-     *
-     * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 990002030010757569643a3131323
-     * 2646534652d383537342d353961622d393332322d3333333435363738393034343a3
-     * a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f6e746
-     * 56e744469726563746f72793a322c757569643a36383539646564652d383537342d3
-     * 53961622d393333322d3132333435363738393031323a3a75706e703a726f6f74646
-     * 576696365
-     * length=153,type=2(UPnP),transaction id=3,status=0
-     *
-     * UPnP Protocol format is as follows.
-     * ______________________________________________________
-     * |  Version (1)  |          USN (Variable)            |
-     *
-     * version=0x10(UPnP1.0) data=usn:uuid:1122de4e-8574-59ab-9322-33345678
-     * 9044::urn:schemas-upnp-org:service:ContentDirectory:2,usn:uuid:6859d
-     * ede-8574-59ab-9332-123456789012::upnp:rootdevice
-     *
-     * P2P-SERV-DISC-RESP 58:17:0c:bc:dd:ca 21 1900010200045f6970
-     * 70c00c000c01094d795072696e746572c027
-     * length=25, type=1(Bonjour),transaction id=2,status=0
-     *
-     * Bonjour Protocol format is as follows.
-     * __________________________________________________________
-     * |DNS Name(Variable)|DNS Type(1)|Version(1)|RDATA(Variable)|
-     *
-     * DNS Name=_ipp._tcp.local.,DNS type=12(PTR), Version=1,
-     * RDATA=MyPrinter._ipp._tcp.local.
-     *
-     */
-    private static final String P2P_SERV_DISC_RESP_STR = "P2P-SERV-DISC-RESP";
-
-    private static final String HOST_AP_EVENT_PREFIX_STR = "AP";
-    /* AP-STA-CONNECTED 42:fc:89:a8:96:09 dev_addr=02:90:4c:a0:92:54 */
-    private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED";
-    /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 */
-    private static final String AP_STA_DISCONNECTED_STR = "AP-STA-DISCONNECTED";
-    private static final String ANQP_DONE_STR = "ANQP-QUERY-DONE";
-    private static final String HS20_ICON_STR = "RX-HS20-ICON";
-
     /* Supplicant events reported to a state machine */
     private static final int BASE = Protocol.BASE_WIFI_MONITOR;
 
@@ -462,13 +72,6 @@
     public static final int WPS_OVERLAP_EVENT                    = BASE + 10;
      /* WPS timeout detected */
     public static final int WPS_TIMEOUT_EVENT                    = BASE + 11;
-    /* Driver was hung */
-    public static final int DRIVER_HUNG_EVENT                    = BASE + 12;
-    /* SSID was disabled due to auth failure or excessive
-     * connection failures */
-    public static final int SSID_TEMP_DISABLED                   = BASE + 13;
-    /* SSID reenabled by supplicant */
-    public static final int SSID_REENABLED                       = BASE + 14;
 
     /* Request Identity */
     public static final int SUP_REQUEST_IDENTITY                 = BASE + 15;
@@ -477,30 +80,9 @@
     public static final int SUP_REQUEST_SIM_AUTH                 = BASE + 16;
 
     public static final int SCAN_FAILED_EVENT                    = BASE + 17;
+    /* Pno scan results are available */
+    public static final int PNO_SCAN_RESULTS_EVENT               = BASE + 18;
 
-    /* P2P events */
-    public static final int P2P_DEVICE_FOUND_EVENT               = BASE + 21;
-    public static final int P2P_DEVICE_LOST_EVENT                = BASE + 22;
-    public static final int P2P_GO_NEGOTIATION_REQUEST_EVENT     = BASE + 23;
-    public static final int P2P_GO_NEGOTIATION_SUCCESS_EVENT     = BASE + 25;
-    public static final int P2P_GO_NEGOTIATION_FAILURE_EVENT     = BASE + 26;
-    public static final int P2P_GROUP_FORMATION_SUCCESS_EVENT    = BASE + 27;
-    public static final int P2P_GROUP_FORMATION_FAILURE_EVENT    = BASE + 28;
-    public static final int P2P_GROUP_STARTED_EVENT              = BASE + 29;
-    public static final int P2P_GROUP_REMOVED_EVENT              = BASE + 30;
-    public static final int P2P_INVITATION_RECEIVED_EVENT        = BASE + 31;
-    public static final int P2P_INVITATION_RESULT_EVENT          = BASE + 32;
-    public static final int P2P_PROV_DISC_PBC_REQ_EVENT          = BASE + 33;
-    public static final int P2P_PROV_DISC_PBC_RSP_EVENT          = BASE + 34;
-    public static final int P2P_PROV_DISC_ENTER_PIN_EVENT        = BASE + 35;
-    public static final int P2P_PROV_DISC_SHOW_PIN_EVENT         = BASE + 36;
-    public static final int P2P_FIND_STOPPED_EVENT               = BASE + 37;
-    public static final int P2P_SERV_DISC_RESP_EVENT             = BASE + 38;
-    public static final int P2P_PROV_DISC_FAILURE_EVENT          = BASE + 39;
-
-    /* hostap events */
-    public static final int AP_STA_DISCONNECTED_EVENT            = BASE + 41;
-    public static final int AP_STA_CONNECTED_EVENT               = BASE + 42;
 
     /* Indicates assoc reject event */
     public static final int ASSOCIATION_REJECTION_EVENT          = BASE + 43;
@@ -514,39 +96,30 @@
     /* hotspot 2.0 events */
     public static final int HS20_REMEDIATION_EVENT               = BASE + 61;
 
-    /**
-     * This indicates a read error on the monitor socket conenction
-     */
-    private static final String WPA_RECV_ERROR_STR = "recv error";
+    /* WPS config errrors */
+    private static final int CONFIG_MULTIPLE_PBC_DETECTED = 12;
+    private static final int CONFIG_AUTH_FAILURE = 18;
 
-    /**
-     * Max errors before we close supplicant connection
-     */
-    private static final int MAX_RECV_ERRORS    = 10;
+    /* WPS error indications */
+    private static final int REASON_TKIP_ONLY_PROHIBITED = 1;
+    private static final int REASON_WEP_PROHIBITED = 2;
 
-    // Singleton instance
-    private static WifiMonitor sWifiMonitor = new WifiMonitor();
-    public static WifiMonitor getInstance() {
-        return sWifiMonitor;
+    private final WifiInjector mWifiInjector;
+    private boolean mVerboseLoggingEnabled = false;
+    private boolean mConnected = false;
+
+    public WifiMonitor(WifiInjector wifiInjector) {
+        mWifiInjector = wifiInjector;
     }
 
-    private final WifiNative mWifiNative;
-    private WifiMonitor() {
-        mWifiNative = WifiNative.getWlanNativeInterface();
-    }
-
-    private int mRecvErrors = 0;
-
     void enableVerboseLogging(int verbose) {
         if (verbose > 0) {
-            DBG = true;
+            mVerboseLoggingEnabled = true;
         } else {
-            DBG = false;
+            mVerboseLoggingEnabled = false;
         }
     }
 
-    private boolean mConnected = false;
-
     // TODO(b/27569474) remove support for multiple handlers for the same event
     private final Map<String, SparseArray<Set<Handler>>> mHandlerMap = new HashMap<>();
     public synchronized void registerHandler(String iface, int what, Handler handler) {
@@ -568,38 +141,47 @@
         Boolean val = mMonitoringMap.get(iface);
         if (val == null) {
             return false;
-        }
-        else {
+        } else {
             return val.booleanValue();
         }
     }
 
-    private void setMonitoring(String iface, boolean enabled) {
+    /**
+     * Enable/Disable monitoring for the provided iface.
+     *
+     * @param iface Name of the iface.
+     * @param enabled true to enable, false to disable.
+     */
+    @VisibleForTesting
+    public void setMonitoring(String iface, boolean enabled) {
         mMonitoringMap.put(iface, enabled);
     }
+
     private void setMonitoringNone() {
         for (String iface : mMonitoringMap.keySet()) {
             setMonitoring(iface, false);
         }
     }
 
-
+    /**
+     * Wait for wpa_supplicant's control interface to be ready.
+     *
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
     private boolean ensureConnectedLocked() {
         if (mConnected) {
             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()) {
-                mConnected = true;
-                new MonitorThread(mWifiNative.getLocalLog()).start();
+            mConnected = mWifiInjector.getWifiNative().connectToSupplicant();
+            if (mConnected) {
                 return true;
             }
-            if (connectTries++ < 5) {
+            if (connectTries++ < 50) {
                 try {
-                    Thread.sleep(1000);
+                    Thread.sleep(100);
                 } catch (InterruptedException ignore) {
                 }
             } else {
@@ -608,43 +190,44 @@
         }
     }
 
-    public synchronized void startMonitoring(String iface) {
-        Log.d(TAG, "startMonitoring(" + iface + ") with mConnected = " + mConnected);
-
+    /**
+     * Start Monitoring for wpa_supplicant events.
+     *
+     * @param iface Name of iface.
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    public synchronized void startMonitoring(String iface, boolean isStaIface) {
         if (ensureConnectedLocked()) {
             setMonitoring(iface, true);
-            sendMessage(iface, SUP_CONNECTION_EVENT);
-        }
-        else {
+            broadcastSupplicantConnectionEvent(iface);
+        } else {
             boolean originalMonitoring = isMonitoring(iface);
             setMonitoring(iface, true);
-            sendMessage(iface, SUP_DISCONNECTION_EVENT);
+            broadcastSupplicantDisconnectionEvent(iface);
             setMonitoring(iface, originalMonitoring);
             Log.e(TAG, "startMonitoring(" + iface + ") failed!");
         }
     }
 
+    /**
+     * Stop Monitoring for wpa_supplicant events.
+     *
+     * @param iface Name of iface.
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
     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);
+        broadcastSupplicantDisconnectionEvent(iface);
         setMonitoring(iface, false);
     }
 
-    public synchronized void stopSupplicant() {
-        mWifiNative.stopSupplicant();
-    }
-
-    public synchronized void killSupplicant(boolean p2pSupported) {
-        String suppState = System.getProperty("init.svc.wpa_supplicant");
-        if (suppState == null) suppState = "unknown";
-        String p2pSuppState = System.getProperty("init.svc.p2p_supplicant");
-        if (p2pSuppState == null) p2pSuppState = "unknown";
-
-        Log.e(TAG, "killSupplicant p2p" + p2pSupported
-                + " init.svc.wpa_supplicant=" + suppState
-                + " init.svc.p2p_supplicant=" + p2pSuppState);
-        mWifiNative.killSupplicant(p2pSupported);
+    /**
+     * Stop Monitoring for wpa_supplicant events.
+     *
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    public synchronized void stopAllMonitoring() {
         mConnected = false;
         setMonitoringNone();
     }
@@ -679,776 +262,305 @@
         SparseArray<Set<Handler>> ifaceHandlers = mHandlerMap.get(iface);
         if (iface != null && ifaceHandlers != null) {
             if (isMonitoring(iface)) {
-                boolean firstHandler = true;
                 Set<Handler> ifaceWhatHandlers = ifaceHandlers.get(message.what);
                 if (ifaceWhatHandlers != null) {
                     for (Handler handler : ifaceWhatHandlers) {
-                        if (firstHandler) {
-                            firstHandler = false;
-                            sendMessage(handler, message);
-                        }
-                        else {
+                        if (handler != null) {
                             sendMessage(handler, Message.obtain(message));
                         }
                     }
                 }
             } 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");
-            boolean firstHandler = true;
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Sending to all monitors because there's no matching iface");
+            }
             for (Map.Entry<String, SparseArray<Set<Handler>>> entry : mHandlerMap.entrySet()) {
                 if (isMonitoring(entry.getKey())) {
                     Set<Handler> ifaceWhatHandlers = entry.getValue().get(message.what);
                     for (Handler handler : ifaceWhatHandlers) {
-                        if (firstHandler) {
-                            firstHandler = false;
-                            sendMessage(handler, message);
-                        }
-                        else {
+                        if (handler != null) {
                             sendMessage(handler, Message.obtain(message));
                         }
                     }
                 }
             }
         }
+
+        message.recycle();
     }
 
     private void sendMessage(Handler handler, Message message) {
-        if (handler != null) {
-            message.setTarget(handler);
-            message.sendToTarget();
-        }
-    }
-
-    private class MonitorThread extends Thread {
-        private final LocalLog mLocalLog;
-
-        public MonitorThread(LocalLog localLog) {
-            super("WifiMonitor");
-            mLocalLog = localLog;
-        }
-
-        public void run() {
-            if (DBG) {
-                Log.d(TAG, "MonitorThread start with mConnected=" + mConnected);
-            }
-            //noinspection InfiniteLoopStatement
-            for (;;) {
-                if (!mConnected) {
-                    if (DBG) 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 + "]");
-                    mLocalLog.log("Event [" + eventStr + "]");
-                }
-
-                if (dispatchEvent(eventStr)) {
-                    if (DBG) Log.d(TAG, "Disconnecting from the supplicant, no more events");
-                    break;
-                }
-            }
-        }
-    }
-
-    private synchronized boolean dispatchEvent(String eventStr) {
-        String iface;
-        // IFNAME=wlan0 ANQP-QUERY-DONE addr=18:cf:5e:26:a4:88 result=SUCCESS
-        if (eventStr.startsWith("IFNAME=")) {
-            int space = eventStr.indexOf(' ');
-            if (space != -1) {
-                iface = eventStr.substring(7, space);
-                if (!mHandlerMap.containsKey(iface) && iface.startsWith("p2p-")) {
-                    // p2p interfaces are created dynamically, but we have
-                    // only one P2p state machine monitoring all of them; look
-                    // for it explicitly, and send messages there ..
-                    iface = "p2p0";
-                }
-                eventStr = eventStr.substring(space + 1);
-            } else {
-                // No point dispatching this event to any interface, the dispatched
-                // event string will begin with "IFNAME=" which dispatchEvent can't really
-                // do anything about.
-                Log.e(TAG, "Dropping malformed event (unparsable iface): " + eventStr);
-                return false;
-            }
-        } else {
-            // events without prefix belong to p2p0 monitor
-            iface = "p2p0";
-        }
-
-        if (VDBG) Log.d(TAG, "Dispatching event to interface: " + iface);
-
-        if (dispatchEvent(eventStr, iface)) {
-            mConnected = false;
-            return true;
-        }
-        return false;
-    }
-
-    private Map<String, Long> mLastConnectBSSIDs = new HashMap<String, Long>() {
-        public Long get(String iface) {
-            Long value = super.get(iface);
-            if (value != null) {
-                return value;
-            }
-            return 0L;
-        }
-    };
-
-    /* @return true if the event was supplicant disconnection */
-    private boolean dispatchEvent(String eventStr, String iface) {
-        if (DBG) {
-            // 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)
-                        + " dispatchEvent: " + eventStr);
-            }
-        }
-
-        if (!eventStr.startsWith(EVENT_PREFIX_STR)) {
-            if (eventStr.startsWith(WPS_SUCCESS_STR)) {
-                sendMessage(iface, WPS_SUCCESS_EVENT);
-            } else if (eventStr.startsWith(WPS_FAIL_STR)) {
-                handleWpsFailEvent(eventStr, iface);
-            } else if (eventStr.startsWith(WPS_OVERLAP_STR)) {
-                sendMessage(iface, WPS_OVERLAP_EVENT);
-            } else if (eventStr.startsWith(WPS_TIMEOUT_STR)) {
-                sendMessage(iface, WPS_TIMEOUT_EVENT);
-            } else if (eventStr.startsWith(P2P_EVENT_PREFIX_STR)) {
-                handleP2pEvents(eventStr, iface);
-            } else if (eventStr.startsWith(HOST_AP_EVENT_PREFIX_STR)) {
-                handleHostApEvents(eventStr, iface);
-            } else if (eventStr.startsWith(ANQP_DONE_STR)) {
-                try {
-                    handleAnqpResult(eventStr, iface);
-                }
-                catch (IllegalArgumentException iae) {
-                    Log.e(TAG, "Bad ANQP event string: '" + eventStr + "': " + iae);
-                }
-            } else if (eventStr.startsWith(HS20_ICON_STR)) {
-                try {
-                    handleIconResult(eventStr, iface);
-                }
-                catch (IllegalArgumentException iae) {
-                    Log.e(TAG, "Bad Icon event string: '" + eventStr + "': " + iae);
-                }
-            }
-            else if (eventStr.startsWith(HS20_SUB_REM_STR)) {
-                // Tack on the last connected BSSID so we have some idea what AP the WNM pertains to
-                handleWnmFrame(String.format("%012x %s",
-                                mLastConnectBSSIDs.get(iface), eventStr), iface);
-            } else if (eventStr.startsWith(HS20_DEAUTH_STR)) {
-                handleWnmFrame(String.format("%012x %s",
-                                mLastConnectBSSIDs.get(iface), eventStr), iface);
-            } else if (eventStr.startsWith(REQUEST_PREFIX_STR)) {
-                handleRequests(eventStr, iface);
-            } else if (eventStr.startsWith(TARGET_BSSID_STR)) {
-                handleTargetBSSIDEvent(eventStr, iface);
-            } else if (eventStr.startsWith(ASSOCIATED_WITH_STR)) {
-                handleAssociatedBSSIDEvent(eventStr, iface);
-            } else if (eventStr.startsWith(AUTH_EVENT_PREFIX_STR) &&
-                    eventStr.endsWith(AUTH_TIMEOUT_STR)) {
-                sendMessage(iface, AUTHENTICATION_FAILURE_EVENT);
-            } else {
-                if (DBG) Log.w(TAG, "couldn't identify event type - " + eventStr);
-            }
-            eventLogCounter++;
-            return false;
-        }
-
-        String eventName = eventStr.substring(EVENT_PREFIX_LEN_STR);
-        int nameEnd = eventName.indexOf(' ');
-        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");
-            eventLogCounter++;
-            return false;
-        }
-        /*
-        * Map event name into event enum
-        */
-        int event;
-        if (eventName.equals(CONNECTED_STR)) {
-            event = CONNECTED;
-            long bssid = -1L;
-            int prefix = eventStr.indexOf(ConnectPrefix);
-            if (prefix >= 0) {
-                int suffix = eventStr.indexOf(ConnectSuffix);
-                if (suffix > prefix) {
-                    try {
-                        bssid = Utils.parseMac(
-                                eventStr.substring(prefix + ConnectPrefix.length(), suffix));
-                    } catch (IllegalArgumentException iae) {
-                        bssid = -1L;
-                    }
-                }
-            }
-            mLastConnectBSSIDs.put(iface, bssid);
-            if (bssid == -1L) {
-                Log.w(TAG, "Failed to parse out BSSID from '" + eventStr + "'");
-            }
-        }
-        else if (eventName.equals(DISCONNECTED_STR))
-            event = DISCONNECTED;
-        else if (eventName.equals(STATE_CHANGE_STR))
-            event = STATE_CHANGE;
-        else if (eventName.equals(SCAN_RESULTS_STR))
-            event = SCAN_RESULTS;
-        else if (eventName.equals(SCAN_FAILED_STR))
-            event = SCAN_FAILED;
-        else if (eventName.equals(LINK_SPEED_STR))
-            event = LINK_SPEED;
-        else if (eventName.equals(TERMINATING_STR))
-            event = TERMINATING;
-        else if (eventName.equals(DRIVER_STATE_STR))
-            event = DRIVER_STATE;
-        else if (eventName.equals(EAP_FAILURE_STR))
-            event = EAP_FAILURE;
-        else if (eventName.equals(ASSOC_REJECT_STR))
-            event = ASSOC_REJECT;
-        else if (eventName.equals(TEMP_DISABLED_STR)) {
-            event = SSID_TEMP_DISABLE;
-        } else if (eventName.equals(REENABLED_STR)) {
-            event = SSID_REENABLE;
-        } else if (eventName.equals(BSS_ADDED_STR)) {
-            event = BSS_ADDED;
-        } else if (eventName.equals(BSS_REMOVED_STR)) {
-            event = BSS_REMOVED;
-        } else
-            event = UNKNOWN;
-
-        String eventData = eventStr;
-        if (event == DRIVER_STATE || event == LINK_SPEED)
-            eventData = eventData.split(" ")[1];
-        else if (event == STATE_CHANGE || event == EAP_FAILURE) {
-            int ind = eventStr.indexOf(" ");
-            if (ind != -1) {
-                eventData = eventStr.substring(ind + 1);
-            }
-        } else {
-            int ind = eventStr.indexOf(" - ");
-            if (ind != -1) {
-                eventData = eventStr.substring(ind + 3);
-            }
-        }
-
-        if ((event == SSID_TEMP_DISABLE)||(event == SSID_REENABLE)) {
-            String substr = null;
-            int netId = -1;
-            int ind = eventStr.indexOf(" ");
-            if (ind != -1) {
-                substr = eventStr.substring(ind + 1);
-            }
-            if (substr != null) {
-                String status[] = substr.split(" ");
-                for (String key : status) {
-                    if (key.regionMatches(0, "id=", 0, 3)) {
-                        int idx = 3;
-                        netId = 0;
-                        while (idx < key.length()) {
-                            char c = key.charAt(idx);
-                            if ((c >= 0x30) && (c <= 0x39)) {
-                                netId *= 10;
-                                netId += c - 0x30;
-                                idx++;
-                            } else {
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-            sendMessage(iface, (event == SSID_TEMP_DISABLE)?
-                    SSID_TEMP_DISABLED:SSID_REENABLED, netId, 0, substr);
-        } else if (event == STATE_CHANGE) {
-            handleSupplicantStateChange(eventData, iface);
-        } else if (event == DRIVER_STATE) {
-            handleDriverEvent(eventData, iface);
-        } else if (event == TERMINATING) {
-            /**
-             * Close the supplicant connection if we see
-             * too many recv errors
-             */
-            if (eventData.startsWith(WPA_RECV_ERROR_STR)) {
-                if (++mRecvErrors > MAX_RECV_ERRORS) {
-                    if (DBG) {
-                        Log.d(TAG, "too many recv errors, closing connection");
-                    }
-                } else {
-                    eventLogCounter++;
-                    return false;
-                }
-            }
-
-            // Notify and exit
-            sendMessage(null, SUP_DISCONNECTION_EVENT, eventLogCounter);
-            return true;
-        } else if (event == EAP_FAILURE) {
-            if (eventData.startsWith(EAP_AUTH_FAILURE_STR)) {
-                sendMessage(iface, AUTHENTICATION_FAILURE_EVENT, eventLogCounter);
-            }
-        } else if (event == ASSOC_REJECT) {
-            Matcher match = mAssocRejectEventPattern.matcher(eventData);
-            String BSSID = "";
-            int status = -1;
-            if (!match.find()) {
-                if (DBG) Log.d(TAG, "Assoc Reject: Could not parse assoc reject string");
-            } else {
-                int groupNumber = match.groupCount();
-                int statusGroupNumber = -1;
-                if (groupNumber == 2) {
-                    BSSID = match.group(1);
-                    statusGroupNumber = 2;
-                } else {
-                    // Under such case Supplicant does not report BSSID
-                    BSSID = null;
-                    statusGroupNumber = 1;
-                }
-                try {
-                    status = Integer.parseInt(match.group(statusGroupNumber));
-                } catch (NumberFormatException e) {
-                    status = -1;
-                }
-            }
-            sendMessage(iface, ASSOCIATION_REJECTION_EVENT, eventLogCounter, status, BSSID);
-        } else if (event == BSS_ADDED && !VDBG) {
-            // Ignore that event - it is not handled, and dont log it as it is too verbose
-        } else if (event == BSS_REMOVED && !VDBG) {
-            // Ignore that event - it is not handled, and dont log it as it is too verbose
-        }  else {
-            handleEvent(event, eventData, iface);
-        }
-        mRecvErrors = 0;
-        eventLogCounter++;
-        return false;
-    }
-
-    private void handleDriverEvent(String state, String iface) {
-        if (state == null) {
-            return;
-        }
-        if (state.equals("HANGED")) {
-            sendMessage(iface, DRIVER_HUNG_EVENT);
-        }
+        message.setTarget(handler);
+        message.sendToTarget();
     }
 
     /**
-     * Handle all supplicant events except STATE-CHANGE
-     * @param event the event type
-     * @param remainder the rest of the string following the
-     * event name and &quot;&#8195;&#8212;&#8195;&quot;
+     * Broadcast the WPS fail event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param cfgError Configuration error code.
+     * @param vendorErrorCode Vendor specific error indication code.
      */
-    private void handleEvent(int event, String remainder, String iface) {
-        if (DBG) {
-            Log.d(TAG, "handleEvent " + Integer.toString(event) + " " + remainder);
-        }
-        switch (event) {
-            case DISCONNECTED:
-                handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder, iface);
-                break;
-
-            case CONNECTED:
-                handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder, iface);
-                break;
-
-            case SCAN_RESULTS:
-                sendMessage(iface, SCAN_RESULTS_EVENT);
-                break;
-
-            case SCAN_FAILED:
-                sendMessage(iface, SCAN_FAILED_EVENT);
-                break;
-
-            case UNKNOWN:
-                if (DBG) {
-                    Log.w(TAG, "handleEvent unknown: " + Integer.toString(event) + " " + remainder);
-                }
-                break;
-            default:
-                break;
-        }
-    }
-
-    private void handleTargetBSSIDEvent(String eventStr, String iface) {
-        String BSSID = null;
-        Matcher match = mTargetBSSIDPattern.matcher(eventStr);
-        if (match.find()) {
-            BSSID = match.group(1);
-        }
-        sendMessage(iface, WifiStateMachine.CMD_TARGET_BSSID, eventLogCounter, 0, BSSID);
-    }
-
-    private void handleAssociatedBSSIDEvent(String eventStr, String iface) {
-        String BSSID = null;
-        Matcher match = mAssociatedPattern.matcher(eventStr);
-        if (match.find()) {
-            BSSID = match.group(1);
-        }
-        sendMessage(iface, WifiStateMachine.CMD_ASSOCIATED_BSSID, eventLogCounter, 0, BSSID);
-    }
-
-
-    private void handleWpsFailEvent(String dataString, String iface) {
-        final Pattern p = Pattern.compile(WPS_FAIL_PATTERN);
-        Matcher match = p.matcher(dataString);
+    public void broadcastWpsFailEvent(String iface, int cfgError, int vendorErrorCode) {
         int reason = 0;
-        if (match.find()) {
-            String cfgErrStr = match.group(1);
-            String reasonStr = match.group(2);
-
-            if (reasonStr != null) {
-                int reasonInt = Integer.parseInt(reasonStr);
-                switch(reasonInt) {
-                    case REASON_TKIP_ONLY_PROHIBITED:
-                        sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_TKIP_ONLY_PROHIBITED);
-                        return;
-                    case REASON_WEP_PROHIBITED:
-                        sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_WEP_PROHIBITED);
-                        return;
-                    default:
-                        reason = reasonInt;
-                        break;
+        switch(vendorErrorCode) {
+            case REASON_TKIP_ONLY_PROHIBITED:
+                sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_TKIP_ONLY_PROHIBITED);
+                return;
+            case REASON_WEP_PROHIBITED:
+                sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_WEP_PROHIBITED);
+                return;
+            default:
+                reason = vendorErrorCode;
+                break;
+        }
+        switch(cfgError) {
+            case CONFIG_AUTH_FAILURE:
+                sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_AUTH_FAILURE);
+                return;
+            case CONFIG_MULTIPLE_PBC_DETECTED:
+                sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_OVERLAP_ERROR);
+                return;
+            default:
+                if (reason == 0) {
+                    reason = cfgError;
                 }
-            }
-            if (cfgErrStr != null) {
-                int cfgErrInt = Integer.parseInt(cfgErrStr);
-                switch(cfgErrInt) {
-                    case CONFIG_AUTH_FAILURE:
-                        sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_AUTH_FAILURE);
-                        return;
-                    case CONFIG_MULTIPLE_PBC_DETECTED:
-                        sendMessage(iface, WPS_FAIL_EVENT, WifiManager.WPS_OVERLAP_ERROR);
-                        return;
-                    default:
-                        if (reason == 0) reason = cfgErrInt;
-                        break;
-                }
-            }
+                break;
         }
         //For all other errors, return a generic internal error
         sendMessage(iface, WPS_FAIL_EVENT, WifiManager.ERROR, reason);
     }
 
-    /* <event> status=<err> and the special case of <event> reason=FREQ_CONFLICT */
-    private P2pStatus p2pError(String dataString) {
-        P2pStatus err = P2pStatus.UNKNOWN;
-        String[] tokens = dataString.split(" ");
-        if (tokens.length < 2) return err;
-        String[] nameValue = tokens[1].split("=");
-        if (nameValue.length != 2) return err;
-
-        /* Handle the special case of reason=FREQ+CONFLICT */
-        if (nameValue[1].equals("FREQ_CONFLICT")) {
-            return P2pStatus.NO_COMMON_CHANNEL;
-        }
-        try {
-            err = P2pStatus.valueOf(Integer.parseInt(nameValue[1]));
-        } catch (NumberFormatException e) {
-            e.printStackTrace();
-        }
-        return err;
-    }
-
-    private WifiP2pDevice getWifiP2pDevice(String dataString) {
-        try {
-            return new WifiP2pDevice(dataString);
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
-    }
-
-    private WifiP2pGroup getWifiP2pGroup(String dataString) {
-        try {
-            return new WifiP2pGroup(dataString);
-        } catch (IllegalArgumentException e) {
-            return null;
-        }
+   /**
+    * Broadcast the WPS succes event to all the handlers registered for this event.
+    *
+    * @param iface Name of iface on which this occurred.
+    */
+    public void broadcastWpsSuccessEvent(String iface) {
+        sendMessage(iface, WPS_SUCCESS_EVENT);
     }
 
     /**
-     * Handle p2p events
+     * Broadcast the WPS overlap event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
      */
-    private void handleP2pEvents(String dataString, String iface) {
-        if (dataString.startsWith(P2P_DEVICE_FOUND_STR)) {
-            WifiP2pDevice device = getWifiP2pDevice(dataString);
-            if (device != null) sendMessage(iface, P2P_DEVICE_FOUND_EVENT, device);
-        } else if (dataString.startsWith(P2P_DEVICE_LOST_STR)) {
-            WifiP2pDevice device = getWifiP2pDevice(dataString);
-            if (device != null) sendMessage(iface, P2P_DEVICE_LOST_EVENT, device);
-        } else if (dataString.startsWith(P2P_FIND_STOPPED_STR)) {
-            sendMessage(iface, P2P_FIND_STOPPED_EVENT);
-        } else if (dataString.startsWith(P2P_GO_NEG_REQUEST_STR)) {
-            sendMessage(iface, P2P_GO_NEGOTIATION_REQUEST_EVENT, new WifiP2pConfig(dataString));
-        } else if (dataString.startsWith(P2P_GO_NEG_SUCCESS_STR)) {
-            sendMessage(iface, P2P_GO_NEGOTIATION_SUCCESS_EVENT);
-        } else if (dataString.startsWith(P2P_GO_NEG_FAILURE_STR)) {
-            sendMessage(iface, P2P_GO_NEGOTIATION_FAILURE_EVENT, p2pError(dataString));
-        } else if (dataString.startsWith(P2P_GROUP_FORMATION_SUCCESS_STR)) {
-            sendMessage(iface, P2P_GROUP_FORMATION_SUCCESS_EVENT);
-        } else if (dataString.startsWith(P2P_GROUP_FORMATION_FAILURE_STR)) {
-            sendMessage(iface, P2P_GROUP_FORMATION_FAILURE_EVENT, p2pError(dataString));
-        } else if (dataString.startsWith(P2P_GROUP_STARTED_STR)) {
-            WifiP2pGroup group = getWifiP2pGroup(dataString);
-            if (group != null) sendMessage(iface, P2P_GROUP_STARTED_EVENT, group);
-        } else if (dataString.startsWith(P2P_GROUP_REMOVED_STR)) {
-            WifiP2pGroup group = getWifiP2pGroup(dataString);
-            if (group != null) sendMessage(iface, P2P_GROUP_REMOVED_EVENT, group);
-        } else if (dataString.startsWith(P2P_INVITATION_RECEIVED_STR)) {
-            sendMessage(iface, P2P_INVITATION_RECEIVED_EVENT, new WifiP2pGroup(dataString));
-        } else if (dataString.startsWith(P2P_INVITATION_RESULT_STR)) {
-            sendMessage(iface, P2P_INVITATION_RESULT_EVENT, p2pError(dataString));
-        } else if (dataString.startsWith(P2P_PROV_DISC_PBC_REQ_STR)) {
-            sendMessage(iface, P2P_PROV_DISC_PBC_REQ_EVENT, new WifiP2pProvDiscEvent(dataString));
-        } else if (dataString.startsWith(P2P_PROV_DISC_PBC_RSP_STR)) {
-            sendMessage(iface, P2P_PROV_DISC_PBC_RSP_EVENT, new WifiP2pProvDiscEvent(dataString));
-        } else if (dataString.startsWith(P2P_PROV_DISC_ENTER_PIN_STR)) {
-            sendMessage(iface, P2P_PROV_DISC_ENTER_PIN_EVENT, new WifiP2pProvDiscEvent(dataString));
-        } else if (dataString.startsWith(P2P_PROV_DISC_SHOW_PIN_STR)) {
-            sendMessage(iface, P2P_PROV_DISC_SHOW_PIN_EVENT, new WifiP2pProvDiscEvent(dataString));
-        } else if (dataString.startsWith(P2P_PROV_DISC_FAILURE_STR)) {
-            sendMessage(iface, P2P_PROV_DISC_FAILURE_EVENT);
-        } else if (dataString.startsWith(P2P_SERV_DISC_RESP_STR)) {
-            List<WifiP2pServiceResponse> list = WifiP2pServiceResponse.newInstance(dataString);
-            if (list != null) {
-                sendMessage(iface, P2P_SERV_DISC_RESP_EVENT, list);
-            } else {
-                Log.e(TAG, "Null service resp " + dataString);
-            }
-        }
+    public void broadcastWpsOverlapEvent(String iface) {
+        sendMessage(iface, WPS_OVERLAP_EVENT);
     }
 
     /**
-     * Handle hostap events
+     * Broadcast the WPS timeout event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
      */
-    private void handleHostApEvents(String dataString, String iface) {
-        String[] tokens = dataString.split(" ");
-        /* AP-STA-CONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */
-        if (tokens[0].equals(AP_STA_CONNECTED_STR)) {
-            sendMessage(iface, AP_STA_CONNECTED_EVENT, new WifiP2pDevice(dataString));
-            /* AP-STA-DISCONNECTED 42:fc:89:a8:96:09 p2p_dev_addr=02:90:4c:a0:92:54 */
-        } else if (tokens[0].equals(AP_STA_DISCONNECTED_STR)) {
-            sendMessage(iface, AP_STA_DISCONNECTED_EVENT, new WifiP2pDevice(dataString));
-        }
-    }
-
-    private static final String ADDR_STRING = "addr=";
-    private static final String RESULT_STRING = "result=";
-
-    // ANQP-QUERY-DONE addr=18:cf:5e:26:a4:88 result=SUCCESS
-
-    private void handleAnqpResult(String eventStr, String iface) {
-        int addrPos = eventStr.indexOf(ADDR_STRING);
-        int resPos = eventStr.indexOf(RESULT_STRING);
-        if (addrPos < 0 || resPos < 0) {
-            throw new IllegalArgumentException("Unexpected ANQP result notification");
-        }
-        int eoaddr = eventStr.indexOf(' ', addrPos + ADDR_STRING.length());
-        if (eoaddr < 0) {
-            eoaddr = eventStr.length();
-        }
-        int eoresult = eventStr.indexOf(' ', resPos + RESULT_STRING.length());
-        if (eoresult < 0) {
-            eoresult = eventStr.length();
-        }
-
-        try {
-            long bssid = Utils.parseMac(eventStr.substring(addrPos + ADDR_STRING.length(), eoaddr));
-            int result = eventStr.substring(
-                    resPos + RESULT_STRING.length(), eoresult).equalsIgnoreCase("success") ? 1 : 0;
-
-            sendMessage(iface, ANQP_DONE_EVENT, result, 0, bssid);
-        }
-        catch (IllegalArgumentException iae) {
-            Log.e(TAG, "Bad MAC address in ANQP response: " + iae.getMessage());
-        }
-    }
-
-    private void handleIconResult(String eventStr, String iface) {
-        // RX-HS20-ICON c0:c5:20:27:d1:e8 <file> <size>
-        String[] segments = eventStr.split(" ");
-        if (segments.length != 4) {
-            throw new IllegalArgumentException("Incorrect number of segments");
-        }
-
-        try {
-            String bssid = segments[1];
-            String fileName = segments[2];
-            int size = Integer.parseInt(segments[3]);
-            sendMessage(iface, RX_HS20_ANQP_ICON_EVENT,
-                    new IconEvent(Utils.parseMac(bssid), fileName, size));
-        }
-        catch (NumberFormatException nfe) {
-            throw new IllegalArgumentException("Bad numeral");
-        }
-    }
-
-    private void handleWnmFrame(String eventStr, String iface) {
-        try {
-            WnmData wnmData = WnmData.buildWnmData(eventStr);
-            sendMessage(iface, HS20_REMEDIATION_EVENT, wnmData);
-        } catch (IOException | NumberFormatException e) {
-            Log.w(TAG, "Bad WNM event: '" + eventStr + "'");
-        }
+    public void broadcastWpsTimeoutEvent(String iface) {
+        sendMessage(iface, WPS_TIMEOUT_EVENT);
     }
 
     /**
-     * Handle Supplicant Requests
+     * Broadcast the ANQP done event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param anqpEvent ANQP result retrieved.
      */
-    private void handleRequests(String dataString, String iface) {
-        String SSID = null;
-        int reason = -2;
-        String requestName = dataString.substring(REQUEST_PREFIX_LEN_STR);
-        if (TextUtils.isEmpty(requestName)) {
-            return;
-        }
-        if (requestName.startsWith(IDENTITY_STR)) {
-            Matcher match = mRequestIdentityPattern.matcher(requestName);
-            if (match.find()) {
-                SSID = match.group(2);
-                try {
-                    reason = Integer.parseInt(match.group(1));
-                } catch (NumberFormatException e) {
-                    reason = -1;
-                }
-            } else {
-                Log.e(TAG, "didn't find SSID " + requestName);
-            }
-            sendMessage(iface, SUP_REQUEST_IDENTITY, eventLogCounter, reason, SSID);
-        } else if (requestName.startsWith(SIM_STR)) {
-            Matcher matchGsm = mRequestGsmAuthPattern.matcher(requestName);
-            Matcher matchUmts = mRequestUmtsAuthPattern.matcher(requestName);
-            WifiStateMachine.SimAuthRequestData data =
-                    new WifiStateMachine.SimAuthRequestData();
-            if (matchGsm.find()) {
-                data.networkId = Integer.parseInt(matchGsm.group(1));
-                data.protocol = WifiEnterpriseConfig.Eap.SIM;
-                data.ssid = matchGsm.group(4);
-                data.data = matchGsm.group(2).split(":");
-                sendMessage(iface, SUP_REQUEST_SIM_AUTH, data);
-            } else if (matchUmts.find()) {
-                data.networkId = Integer.parseInt(matchUmts.group(1));
-                data.protocol = WifiEnterpriseConfig.Eap.AKA;
-                data.ssid = matchUmts.group(4);
-                data.data = new String[2];
-                data.data[0] = matchUmts.group(2);
-                data.data[1] = matchUmts.group(3);
-                sendMessage(iface, SUP_REQUEST_SIM_AUTH, data);
-            } else {
-                Log.e(TAG, "couldn't parse SIM auth request - " + requestName);
-            }
-
-        } else {
-            if (DBG) Log.w(TAG, "couldn't identify request type - " + dataString);
-        }
+    public void broadcastAnqpDoneEvent(String iface, AnqpEvent anqpEvent) {
+        sendMessage(iface, ANQP_DONE_EVENT, anqpEvent);
     }
 
     /**
-     * Handle the supplicant STATE-CHANGE event
-     * @param dataString New supplicant state string in the format:
-     * id=network-id state=new-state
+     * Broadcast the Icon done event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param iconEvent Instance of IconEvent containing the icon data retrieved.
      */
-    private void handleSupplicantStateChange(String dataString, String iface) {
-        WifiSsid wifiSsid = null;
-        int index = dataString.lastIndexOf("SSID=");
-        if (index != -1) {
-            wifiSsid = WifiSsid.createFromAsciiEncoded(
-                    dataString.substring(index + 5));
-        }
-        String[] dataTokens = dataString.split(" ");
-
-        String BSSID = null;
-        int networkId = -1;
-        int newState  = -1;
-        for (String token : dataTokens) {
-            String[] nameValue = token.split("=");
-            if (nameValue.length != 2) {
-                continue;
-            }
-
-            if (nameValue[0].equals("BSSID")) {
-                BSSID = nameValue[1];
-                continue;
-            }
-
-            int value;
-            try {
-                value = Integer.parseInt(nameValue[1]);
-            } catch (NumberFormatException e) {
-                continue;
-            }
-
-            if (nameValue[0].equals("id")) {
-                networkId = value;
-            } else if (nameValue[0].equals("state")) {
-                newState = value;
-            }
-        }
-
-        if (newState == -1) return;
-
-        SupplicantState newSupplicantState = SupplicantState.INVALID;
-        for (SupplicantState state : SupplicantState.values()) {
-            if (state.ordinal() == newState) {
-                newSupplicantState = state;
-                break;
-            }
-        }
-        if (newSupplicantState == SupplicantState.INVALID) {
-            Log.w(TAG, "Invalid supplicant state: " + newState);
-        }
-        sendMessage(iface, SUPPLICANT_STATE_CHANGE_EVENT, eventLogCounter, 0,
-                new StateChangeResult(networkId, wifiSsid, BSSID, newSupplicantState));
+    public void broadcastIconDoneEvent(String iface, IconEvent iconEvent) {
+        sendMessage(iface, RX_HS20_ANQP_ICON_EVENT, iconEvent);
     }
 
-    private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data,
-            String iface) {
-        String BSSID = null;
-        int networkId = -1;
-        int reason = 0;
-        int ind = -1;
-        int local = 0;
-        Matcher match;
-        if (newState == NetworkInfo.DetailedState.CONNECTED) {
-            match = mConnectedEventPattern.matcher(data);
-            if (!match.find()) {
-               if (DBG) Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");
-            } else {
-                BSSID = match.group(1);
-                try {
-                    networkId = Integer.parseInt(match.group(2));
-                } catch (NumberFormatException e) {
-                    networkId = -1;
-                }
-            }
-            sendMessage(iface, NETWORK_CONNECTION_EVENT, networkId, reason, BSSID);
-        } else if (newState == NetworkInfo.DetailedState.DISCONNECTED) {
-            match = mDisconnectedEventPattern.matcher(data);
-            if (!match.find()) {
-               if (DBG) Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");
-            } else {
-                BSSID = match.group(1);
-                try {
-                    reason = Integer.parseInt(match.group(2));
-                } catch (NumberFormatException e) {
-                    reason = -1;
-                }
-                try {
-                    local = Integer.parseInt(match.group(3));
-                } catch (NumberFormatException e) {
-                    local = -1;
-                }
-            }
-            if (DBG) Log.d(TAG, "WifiMonitor notify network disconnect: "
-                    + BSSID
-                    + " reason=" + Integer.toString(reason));
-            sendMessage(iface, NETWORK_DISCONNECTION_EVENT, local, reason, BSSID);
-        }
+    /**
+     * Broadcast the WNM event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param wnmData Instance of WnmData containing the event data.
+     */
+    public void broadcastWnmEvent(String iface, WnmData wnmData) {
+        sendMessage(iface, HS20_REMEDIATION_EVENT, wnmData);
+    }
+
+    /**
+     * Broadcast the Network identity request event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param networkId ID of the network in wpa_supplicant.
+     * @param ssid SSID of the network.
+     */
+    public void broadcastNetworkIdentityRequestEvent(String iface, int networkId, String ssid) {
+        sendMessage(iface, SUP_REQUEST_IDENTITY, 0, networkId, ssid);
+    }
+
+    /**
+     * Broadcast the Network Gsm Sim auth request event to all the handlers registered for this
+     * event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param networkId ID of the network in wpa_supplicant.
+     * @param ssid SSID of the network.
+     * @param data Accompanying event data.
+     */
+    public void broadcastNetworkGsmAuthRequestEvent(String iface, int networkId, String ssid,
+                                                    String[] data) {
+        sendMessage(iface, SUP_REQUEST_SIM_AUTH,
+                new SimAuthRequestData(networkId, WifiEnterpriseConfig.Eap.SIM, ssid, data));
+    }
+
+    /**
+     * Broadcast the Network Umts Sim auth request event to all the handlers registered for this
+     * event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param networkId ID of the network in wpa_supplicant.
+     * @param ssid SSID of the network.
+     * @param data Accompanying event data.
+     */
+    public void broadcastNetworkUmtsAuthRequestEvent(String iface, int networkId, String ssid,
+                                                     String[] data) {
+        sendMessage(iface, SUP_REQUEST_SIM_AUTH,
+                new SimAuthRequestData(networkId, WifiEnterpriseConfig.Eap.AKA, ssid, data));
+    }
+
+    /**
+     * Broadcast scan result event to all the handlers registered for this event.
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastScanResultEvent(String iface) {
+        sendMessage(iface, SCAN_RESULTS_EVENT);
+    }
+
+    /**
+     * Broadcast pno scan result event to all the handlers registered for this event.
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastPnoScanResultEvent(String iface) {
+        sendMessage(iface, PNO_SCAN_RESULTS_EVENT);
+    }
+
+    /**
+     * Broadcast scan failed event to all the handlers registered for this event.
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastScanFailedEvent(String iface) {
+        sendMessage(iface, SCAN_FAILED_EVENT);
+    }
+
+    /**
+     * Broadcast the authentication failure event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param reason Reason for authentication failure. This has to be one of the
+     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_NONE},
+     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_TIMEOUT},
+     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_WRONG_PSWD},
+     *               {@link android.net.wifi.WifiManager#ERROR_AUTH_FAILURE_EAP_FAILURE}
+     */
+    public void broadcastAuthenticationFailureEvent(String iface, int reason) {
+        sendMessage(iface, AUTHENTICATION_FAILURE_EVENT, 0, reason);
+    }
+
+    /**
+     * Broadcast the association rejection event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param status Status code for association rejection.
+     * @param timedOut Indicates if the association timed out.
+     * @param bssid BSSID of the access point from which we received the reject.
+     */
+    public void broadcastAssociationRejectionEvent(String iface, int status, boolean timedOut,
+                                                   String bssid) {
+        sendMessage(iface, ASSOCIATION_REJECTION_EVENT, timedOut ? 1 : 0, status, bssid);
+    }
+
+    /**
+     * Broadcast the association success event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param bssid BSSID of the access point.
+     */
+    public void broadcastAssociatedBssidEvent(String iface, String bssid) {
+        sendMessage(iface, WifiStateMachine.CMD_ASSOCIATED_BSSID, 0, 0, bssid);
+    }
+
+    /**
+     * Broadcast the start of association event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param bssid BSSID of the access point.
+     */
+    public void broadcastTargetBssidEvent(String iface, String bssid) {
+        sendMessage(iface, WifiStateMachine.CMD_TARGET_BSSID, 0, 0, bssid);
+    }
+
+    /**
+     * Broadcast the network connection event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param networkId ID of the network in wpa_supplicant.
+     * @param bssid BSSID of the access point.
+     */
+    public void broadcastNetworkConnectionEvent(String iface, int networkId, String bssid) {
+        sendMessage(iface, NETWORK_CONNECTION_EVENT, networkId, 0, bssid);
+    }
+
+    /**
+     * Broadcast the network disconnection event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param local Whether the disconnect was locally triggered.
+     * @param reason Disconnect reason code.
+     * @param bssid BSSID of the access point.
+     */
+    public void broadcastNetworkDisconnectionEvent(String iface, int local, int reason,
+                                                   String bssid) {
+        sendMessage(iface, NETWORK_DISCONNECTION_EVENT, local, reason, bssid);
+    }
+
+    /**
+     * Broadcast the supplicant state change event to all the handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param networkId ID of the network in wpa_supplicant.
+     * @param bssid BSSID of the access point.
+     * @param newSupplicantState New supplicant state.
+     */
+    public void broadcastSupplicantStateChangeEvent(String iface, int networkId, WifiSsid wifiSsid,
+                                                    String bssid,
+                                                    SupplicantState newSupplicantState) {
+        sendMessage(iface, SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(networkId, wifiSsid, bssid, newSupplicantState));
+    }
+
+    /**
+     * Broadcast the connection to wpa_supplicant event to all the handlers registered for
+     * this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastSupplicantConnectionEvent(String iface) {
+        sendMessage(iface, SUP_CONNECTION_EVENT);
+    }
+
+    /**
+     * Broadcast the loss of connection to wpa_supplicant event to all the handlers registered for
+     * this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastSupplicantDisconnectionEvent(String iface) {
+        sendMessage(iface, SUP_DISCONNECTION_EVENT);
     }
 }
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 13876f3..90f6ac1 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -16,54 +16,28 @@
 
 package com.android.server.wifi;
 
-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.IApInterface;
+import android.net.wifi.IClientInterface;
 import android.net.wifi.RttManager;
 import android.net.wifi.RttManager.ResponderConfig;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiLinkLayerStats;
-import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
-import android.net.wifi.WifiSsid;
 import android.net.wifi.WifiWakeReasonAndCounts;
-import android.net.wifi.WpsInfo;
-import android.net.wifi.p2p.WifiP2pConfig;
-import android.net.wifi.p2p.WifiP2pGroup;
-import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
 import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.LocalLog;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.Immutable;
 import com.android.internal.util.HexDump;
 import com.android.server.connectivity.KeepalivePacketData;
-import com.android.server.wifi.hotspot2.NetworkDetail;
-import com.android.server.wifi.hotspot2.SupplicantBridge;
-import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.util.FrameParser;
-import com.android.server.wifi.util.InformationElementUtil;
-
-import libcore.util.HexEncoding;
-
-import org.json.JSONException;
-import org.json.JSONObject;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
@@ -71,12 +45,7 @@
 import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -87,703 +56,264 @@
  * Native calls for bring up/shut down of the supplicant daemon and for
  * sending requests to the supplicant daemon
  *
- * waitForEvent() is called on the monitor thread for events. All other methods
- * must be serialized from the framework.
- *
  * {@hide}
  */
 public class WifiNative {
-    private static boolean DBG = false;
-
-    // Must match wifi_hal.h
-    public static final int WIFI_SUCCESS = 0;
-
-    /**
-     * Hold this lock before calling supplicant or HAL methods
-     * it is required to mutually exclude access to the driver
-     */
-    public static final Object sLock = new Object();
-
-    private static final LocalLog sLocalLog = new LocalLog(8192);
-
-    public @NonNull LocalLog getLocalLog() {
-        return sLocalLog;
-    }
-
-    /* Register native functions */
-    static {
-        /* Native functions are defined in libwifi-service.so */
-        System.loadLibrary("wifi-service");
-        registerNatives();
-    }
-
-    private static native int registerNatives();
-
-    /*
-     * Singleton WifiNative instances
-     */
-    private static WifiNative wlanNativeInterface =
-            new WifiNative(SystemProperties.get("wifi.interface", "wlan0"), true);
-    public static WifiNative getWlanNativeInterface() {
-        return wlanNativeInterface;
-    }
-
-    private static WifiNative p2pNativeInterface =
-            // commands for p2p0 interface don't need prefix
-            new WifiNative(SystemProperties.get("wifi.direct.interface", "p2p0"), false);
-    public static WifiNative getP2pNativeInterface() {
-        return p2pNativeInterface;
-    }
-
-
     private final String mTAG;
     private final String mInterfaceName;
-    private final String mInterfacePrefix;
+    private final SupplicantStaIfaceHal mSupplicantStaIfaceHal;
+    private final WifiVendorHal mWifiVendorHal;
+    private final WificondControl mWificondControl;
 
-    private Context mContext = null;
-    public void initContext(Context context) {
-        if (mContext == null && context != null) {
-            mContext = context;
-        }
-    }
-
-    private WifiNative(String interfaceName,
-                       boolean requiresPrefix) {
-        mInterfaceName = interfaceName;
+    public WifiNative(String interfaceName, WifiVendorHal vendorHal,
+                      SupplicantStaIfaceHal staIfaceHal, WificondControl condControl) {
         mTAG = "WifiNative-" + interfaceName;
-
-        if (requiresPrefix) {
-            mInterfacePrefix = "IFNAME=" + interfaceName + " ";
-        } else {
-            mInterfacePrefix = "";
-        }
+        mInterfaceName = interfaceName;
+        mWifiVendorHal = vendorHal;
+        mSupplicantStaIfaceHal = staIfaceHal;
+        mWificondControl = condControl;
     }
 
     public String getInterfaceName() {
         return mInterfaceName;
     }
 
-    // Note this affects logging on for all interfaces
-    void enableVerboseLogging(int verbose) {
-        if (verbose > 0) {
-            DBG = true;
-        } else {
-            DBG = false;
-        }
-    }
-
-    private void localLog(String s) {
-        if (sLocalLog != null) sLocalLog.log(mInterfaceName + ": " + s);
-    }
-
-
-
-    /*
-     * Driver and Supplicant management
+    /**
+     * Enable verbose logging for all sub modules.
      */
-    private native static boolean loadDriverNative();
-    public boolean loadDriver() {
-        synchronized (sLock) {
-            return loadDriverNative();
-        }
+    public void enableVerboseLogging(int verbose) {
+        mWificondControl.enableVerboseLogging(verbose > 0 ? true : false);
+        mSupplicantStaIfaceHal.enableVerboseLogging(verbose > 0);
+        mWifiVendorHal.enableVerboseLogging(verbose > 0);
     }
 
-    private native static boolean isDriverLoadedNative();
-    public boolean isDriverLoaded() {
-        synchronized (sLock) {
-            return isDriverLoadedNative();
-        }
-    }
+   /********************************************************
+    * Native Initialization/Deinitialization
+    ********************************************************/
 
-    private native static boolean unloadDriverNative();
-    public boolean unloadDriver() {
-        synchronized (sLock) {
-            return unloadDriverNative();
+   /**
+    * Setup wifi native for Client mode operations.
+    *
+    * 1. Starts the Wifi HAL and configures it in client/STA mode.
+    * 2. Setup Wificond to operate in client mode and retrieve the handle to use for client
+    * operations.
+    *
+    * @return An IClientInterface as wificond client interface binder handler.
+    * Returns null on failure.
+    */
+    public IClientInterface setupForClientMode() {
+        if (!startHalIfNecessary(true)) {
+            Log.e(mTAG, "Failed to start HAL for client mode");
+            return null;
         }
-    }
-
-    private native static boolean startSupplicantNative(boolean p2pSupported);
-    public boolean startSupplicant(boolean p2pSupported) {
-        synchronized (sLock) {
-            return startSupplicantNative(p2pSupported);
-        }
-    }
-
-    /* Sends a kill signal to supplicant. To be used when we have lost connection
-       or when the supplicant is hung */
-    private native static boolean killSupplicantNative(boolean p2pSupported);
-    public boolean killSupplicant(boolean p2pSupported) {
-        synchronized (sLock) {
-            return killSupplicantNative(p2pSupported);
-        }
-    }
-
-    private native static boolean connectToSupplicantNative();
-    public boolean connectToSupplicant() {
-        synchronized (sLock) {
-            localLog(mInterfacePrefix + "connectToSupplicant");
-            return connectToSupplicantNative();
-        }
-    }
-
-    private native static void closeSupplicantConnectionNative();
-    public void closeSupplicantConnection() {
-        synchronized (sLock) {
-            localLog(mInterfacePrefix + "closeSupplicantConnection");
-            closeSupplicantConnectionNative();
-        }
+        return mWificondControl.setupDriverForClientMode();
     }
 
     /**
-     * Wait for the supplicant to send an event, returning the event string.
-     * @return the event string sent by the supplicant.
+     * Setup wifi native for AP mode operations.
+     *
+     * 1. Starts the Wifi HAL and configures it in AP mode.
+     * 2. Setup Wificond to operate in AP mode and retrieve the handle to use for ap operations.
+     *
+     * @return An IApInterface as wificond Ap interface binder handler.
+     * Returns null on failure.
      */
-    private native static String waitForEventNative();
-    public String waitForEvent() {
-        // No synchronization necessary .. it is implemented in WifiMonitor
-        return waitForEventNative();
-    }
-
-
-    /*
-     * Supplicant Command Primitives
-     */
-    private native boolean doBooleanCommandNative(String command);
-
-    private native int doIntCommandNative(String command);
-
-    private native String doStringCommandNative(String command);
-
-    private boolean doBooleanCommand(String command) {
-        if (DBG) Log.d(mTAG, "doBoolean: " + command);
-        synchronized (sLock) {
-            String toLog = mInterfacePrefix + command;
-            boolean result = doBooleanCommandNative(mInterfacePrefix + command);
-            localLog(toLog + " -> " + result);
-            if (DBG) Log.d(mTAG, command + ": returned " + result);
-            return result;
+    public IApInterface setupForSoftApMode() {
+        if (!startHalIfNecessary(false)) {
+            Log.e(mTAG, "Failed to start HAL for AP mode");
+            return null;
         }
-    }
-
-    private boolean doBooleanCommandWithoutLogging(String command) {
-        if (DBG) Log.d(mTAG, "doBooleanCommandWithoutLogging: " + command);
-        synchronized (sLock) {
-            boolean result = doBooleanCommandNative(mInterfacePrefix + command);
-            if (DBG) Log.d(mTAG, command + ": returned " + result);
-            return result;
-        }
-    }
-
-    private int doIntCommand(String command) {
-        if (DBG) Log.d(mTAG, "doInt: " + command);
-        synchronized (sLock) {
-            String toLog = mInterfacePrefix + command;
-            int result = doIntCommandNative(mInterfacePrefix + command);
-            localLog(toLog + " -> " + result);
-            if (DBG) Log.d(mTAG, "   returned " + result);
-            return result;
-        }
-    }
-
-    private String doStringCommand(String command) {
-        if (DBG) {
-            //GET_NETWORK commands flood the logs
-            if (!command.startsWith("GET_NETWORK")) {
-                Log.d(mTAG, "doString: [" + command + "]");
-            }
-        }
-        synchronized (sLock) {
-            String toLog = mInterfacePrefix + command;
-            String result = doStringCommandNative(mInterfacePrefix + command);
-            if (result == null) {
-                if (DBG) Log.d(mTAG, "doStringCommandNative no result");
-            } else {
-                if (!command.startsWith("STATUS-")) {
-                    localLog(toLog + " -> " + result);
-                }
-                if (DBG) Log.d(mTAG, "   returned " + result.replace("\n", " "));
-            }
-            return result;
-        }
-    }
-
-    private String doStringCommandWithoutLogging(String command) {
-        if (DBG) {
-            //GET_NETWORK commands flood the logs
-            if (!command.startsWith("GET_NETWORK")) {
-                Log.d(mTAG, "doString: [" + command + "]");
-            }
-        }
-        synchronized (sLock) {
-            return doStringCommandNative(mInterfacePrefix + command);
-        }
-    }
-
-    public String doCustomSupplicantCommand(String command) {
-        return doStringCommand(command);
-    }
-
-    /*
-     * Wrappers for supplicant commands
-     */
-    public boolean ping() {
-        String pong = doStringCommand("PING");
-        return (pong != null && pong.equals("PONG"));
-    }
-
-    public void setSupplicantLogLevel(String level) {
-        doStringCommand("LOG_LEVEL " + level);
-    }
-
-    public String getFreqCapability() {
-        return doStringCommand("GET_CAPABILITY freq");
+        return mWificondControl.setupDriverForSoftApMode();
     }
 
     /**
-     * Create a comma separate string from integer set.
-     * @param values List of integers.
-     * @return comma separated string.
+     * Teardown all mode configurations in wifi native.
+     *
+     * 1. Tears down all the interfaces from Wificond.
+     * 2. Stops the Wifi HAL.
+     *
+     * @return Returns true on success.
      */
-    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;
+    public boolean tearDown() {
+        if (!mWificondControl.tearDownInterfaces()) {
+            // TODO(b/34859006): Handle failures.
+            Log.e(mTAG, "Failed to teardown interfaces from Wificond");
+            return false;
         }
-        return list.toString();
+        stopHalIfNecessary();
+        return true;
+    }
+
+    /********************************************************
+     * Wificond operations
+     ********************************************************/
+    /**
+     * Result of a signal poll.
+     */
+    public static class SignalPollResult {
+        // RSSI value in dBM.
+        public int currentRssi;
+        //Transmission bit rate in Mbps.
+        public int txBitrate;
+        // Association frequency in MHz.
+        public int associationFrequency;
     }
 
     /**
-     * Start a scan using wpa_supplicant for the given frequencies.
+     * WiFi interface transimission counters.
+     */
+    public static class TxPacketCounters {
+        // Number of successfully transmitted packets.
+        public int txSucceeded;
+        // Number of tramsmission failures.
+        public int txFailed;
+    }
+
+    /**
+    * Disable wpa_supplicant via wificond.
+    * @return Returns true on success.
+    */
+    public boolean disableSupplicant() {
+        return mWificondControl.disableSupplicant();
+    }
+
+    /**
+    * Enable wpa_supplicant via wificond.
+    * @return Returns true on success.
+    */
+    public boolean enableSupplicant() {
+        return mWificondControl.enableSupplicant();
+    }
+
+    /**
+    * Request signal polling to wificond.
+    * Returns an SignalPollResult object.
+    * Returns null on failure.
+    */
+    public SignalPollResult signalPoll() {
+        return mWificondControl.signalPoll();
+    }
+
+    /**
+     * Fetch TX packet counters on current connection from wificond.
+    * Returns an TxPacketCounters object.
+    * Returns null on failure.
+    */
+    public TxPacketCounters getTxPacketCounters() {
+        return mWificondControl.getTxPacketCounters();
+    }
+
+    /**
+     * Start a scan using wificond for the given parameters.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
-     * @param hiddenNetworkIds List of hidden networks to be scanned for.
+     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
+     * @return Returns true on success.
      */
-    public boolean scan(Set<Integer> freqs, Set<Integer> hiddenNetworkIds) {
-        String freqList = null;
-        String hiddenNetworkIdList = null;
-        if (freqs != null && freqs.size() != 0) {
-            freqList = createCSVStringFromIntegerSet(freqs);
-        }
-        if (hiddenNetworkIds != null && hiddenNetworkIds.size() != 0) {
-            hiddenNetworkIdList = createCSVStringFromIntegerSet(hiddenNetworkIds);
-        }
-        return scanWithParams(freqList, hiddenNetworkIdList);
-    }
-
-    private boolean scanWithParams(String freqList, String hiddenNetworkIdList) {
-        StringBuilder scanCommand = new StringBuilder();
-        scanCommand.append("SCAN TYPE=ONLY");
-        if (freqList != null) {
-            scanCommand.append(" freq=" + freqList);
-        }
-        if (hiddenNetworkIdList != null) {
-            scanCommand.append(" scan_id=" + hiddenNetworkIdList);
-        }
-        return doBooleanCommand(scanCommand.toString());
-    }
-
-    /* Does a graceful shutdown of supplicant. Is a common stop function for both p2p and sta.
-     *
-     * Note that underneath we use a harsh-sounding "terminate" supplicant command
-     * for a graceful stop and a mild-sounding "stop" interface
-     * to kill the process
-     */
-    public boolean stopSupplicant() {
-        return doBooleanCommand("TERMINATE");
-    }
-
-    public String listNetworks() {
-        return doStringCommand("LIST_NETWORKS");
-    }
-
-    public String listNetworks(int last_id) {
-        return doStringCommand("LIST_NETWORKS LAST_ID=" + last_id);
-    }
-
-    public int addNetwork() {
-        return doIntCommand("ADD_NETWORK");
-    }
-
-    public boolean setNetworkExtra(int netId, String name, 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;
-        } catch (UnsupportedEncodingException e) {
-            Log.e(TAG, "Unable to serialize networkExtra: " + e.toString());
-            return false;
-        }
-        return setNetworkVariable(netId, name, "\"" + encoded + "\"");
-    }
-
-    public boolean setNetworkVariable(int netId, String name, String value) {
-        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false;
-        if (name.equals(WifiConfiguration.pskVarName)
-                || name.equals(WifiEnterpriseConfig.PASSWORD_KEY)) {
-            return doBooleanCommandWithoutLogging("SET_NETWORK " + netId + " " + name + " " + value);
-        } else {
-            return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
-        }
-    }
-
-    public Map<String, String> getNetworkExtra(int netId, String name) {
-        final String wrapped = getNetworkVariable(netId, name);
-        if (wrapped == null || !wrapped.startsWith("\"") || !wrapped.endsWith("\"")) {
-            return null;
-        }
-        try {
-            final String encoded = wrapped.substring(1, wrapped.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
-            // constructor will thrown a JSONException and the method will return null.
-            final JSONObject json = new JSONObject(URLDecoder.decode(encoded, "UTF-8"));
-            final Map<String, String> values = new HashMap<String, String>();
-            final Iterator<?> it = json.keys();
-            while (it.hasNext()) {
-                final String key = (String) it.next();
-                final Object value = json.get(key);
-                if (value instanceof String) {
-                    values.put(key, (String) value);
-                }
-            }
-            return values;
-        } catch (UnsupportedEncodingException e) {
-            Log.e(TAG, "Unable to deserialize networkExtra: " + e.toString());
-            return null;
-        } catch (JSONException e) {
-            // This is not necessarily an error. This exception will also occur if we encounter a
-            // legacy FQDN stored as a plain string. We want to return null in this case as no JSON
-            // dictionary of extras was found.
-            return null;
-        }
-    }
-
-    public String getNetworkVariable(int netId, String name) {
-        if (TextUtils.isEmpty(name)) return null;
-
-        // GET_NETWORK will likely flood the logs ...
-        return doStringCommandWithoutLogging("GET_NETWORK " + netId + " " + name);
-    }
-
-    public boolean removeNetwork(int netId) {
-        return doBooleanCommand("REMOVE_NETWORK " + netId);
-    }
-
-
-    private void logDbg(String debug) {
-        long now = SystemClock.elapsedRealtimeNanos();
-        String ts = String.format("[%,d us] ", now/1000);
-        Log.e("WifiNative: ", ts+debug+ " stack:"
-                + Thread.currentThread().getStackTrace()[2].getMethodName() +" - "
-                + Thread.currentThread().getStackTrace()[3].getMethodName() +" - "
-                + Thread.currentThread().getStackTrace()[4].getMethodName() +" - "
-                + Thread.currentThread().getStackTrace()[5].getMethodName()+" - "
-                + Thread.currentThread().getStackTrace()[6].getMethodName());
-
+    public boolean scan(Set<Integer> freqs, Set<String> hiddenNetworkSSIDs) {
+        return mWificondControl.scan(freqs, hiddenNetworkSSIDs);
     }
 
     /**
-     * Enables a network in wpa_supplicant.
-     * @param netId - Network ID of the network to be enabled.
-     * @return true if command succeeded, false otherwise.
+     * Fetch the latest scan result from kernel via wificond.
+     * @return Returns an ArrayList of ScanDetail.
+     * Returns an empty ArrayList on failure.
      */
-    public boolean enableNetwork(int netId) {
-        if (DBG) logDbg("enableNetwork nid=" + Integer.toString(netId));
-        return doBooleanCommand("ENABLE_NETWORK " + netId);
-    }
-
-    /**
-     * Enable a network in wpa_supplicant, do not connect.
-     * @param netId - Network ID of the network to be enabled.
-     * @return true if command succeeded, false otherwise.
-     */
-    public boolean enableNetworkWithoutConnect(int netId) {
-        if (DBG) logDbg("enableNetworkWithoutConnect nid=" + Integer.toString(netId));
-        return doBooleanCommand("ENABLE_NETWORK " + netId + " " + "no-connect");
-    }
-
-    /**
-     * Disables a network in wpa_supplicant.
-     * @param netId - Network ID of the network to be disabled.
-     * @return true if command succeeded, false otherwise.
-     */
-    public boolean disableNetwork(int netId) {
-        if (DBG) logDbg("disableNetwork nid=" + Integer.toString(netId));
-        return doBooleanCommand("DISABLE_NETWORK " + netId);
-    }
-
-    /**
-     * Select a network in wpa_supplicant (Disables all others).
-     * @param netId - Network ID of the network to be selected.
-     * @return true if command succeeded, false otherwise.
-     */
-    public boolean selectNetwork(int netId) {
-        if (DBG) logDbg("selectNetwork nid=" + Integer.toString(netId));
-        return doBooleanCommand("SELECT_NETWORK " + netId);
-    }
-
-    public boolean reconnect() {
-        if (DBG) logDbg("RECONNECT ");
-        return doBooleanCommand("RECONNECT");
-    }
-
-    public boolean reassociate() {
-        if (DBG) logDbg("REASSOCIATE ");
-        return doBooleanCommand("REASSOCIATE");
-    }
-
-    public boolean disconnect() {
-        if (DBG) logDbg("DISCONNECT ");
-        return doBooleanCommand("DISCONNECT");
-    }
-
-    public String status() {
-        return status(false);
-    }
-
-    public String status(boolean noEvents) {
-        if (noEvents) {
-            return doStringCommand("STATUS-NO_EVENTS");
-        } else {
-            return doStringCommand("STATUS");
-        }
-    }
-
-    public String getMacAddress() {
-        //Macaddr = XX.XX.XX.XX.XX.XX
-        String ret = doStringCommand("DRIVER MACADDR");
-        if (!TextUtils.isEmpty(ret)) {
-            String[] tokens = ret.split(" = ");
-            if (tokens.length == 2) return tokens[1];
-        }
-        return null;
-    }
-
-
-
-    /**
-     * Format of results:
-     * =================
-     * id=1
-     * bssid=68:7f:76:d7:1a:6e
-     * freq=2412
-     * level=-44
-     * tsf=1344626243700342
-     * flags=[WPA2-PSK-CCMP][WPS][ESS]
-     * ssid=zfdy
-     * ====
-     * id=2
-     * bssid=68:5f:74:d7:1a:6f
-     * freq=5180
-     * level=-73
-     * tsf=1344626243700373
-     * flags=[WPA2-PSK-CCMP][WPS][ESS]
-     * ssid=zuby
-     * ====
-     *
-     * RANGE=ALL gets all scan results
-     * RANGE=ID- gets results from ID
-     * MASK=<N> BSS command information mask.
-     *
-     * The mask used in this method, 0x29d87, gets the following fields:
-     *
-     *     WPA_BSS_MASK_ID         (Bit 0)
-     *     WPA_BSS_MASK_BSSID      (Bit 1)
-     *     WPA_BSS_MASK_FREQ       (Bit 2)
-     *     WPA_BSS_MASK_LEVEL      (Bit 7)
-     *     WPA_BSS_MASK_TSF        (Bit 8)
-     *     WPA_BSS_MASK_IE         (Bit 10)
-     *     WPA_BSS_MASK_FLAGS      (Bit 11)
-     *     WPA_BSS_MASK_SSID       (Bit 12)
-     *     WPA_BSS_MASK_INTERNETW  (Bit 15) (adds ANQP info)
-     *     WPA_BSS_MASK_DELIM      (Bit 17)
-     *
-     * See wpa_supplicant/src/common/wpa_ctrl.h for details.
-     */
-    private String getRawScanResults(String range) {
-        return doStringCommandWithoutLogging("BSS RANGE=" + range + " MASK=0x29d87");
-    }
-
-    private static final String BSS_IE_STR = "ie=";
-    private static final String BSS_ID_STR = "id=";
-    private static final String BSS_BSSID_STR = "bssid=";
-    private static final String BSS_FREQ_STR = "freq=";
-    private static final String BSS_LEVEL_STR = "level=";
-    private static final String BSS_TSF_STR = "tsf=";
-    private static final String BSS_FLAGS_STR = "flags=";
-    private static final String BSS_SSID_STR = "ssid=";
-    private static final String BSS_DELIMITER_STR = "====";
-    private static final String BSS_END_STR = "####";
-
     public ArrayList<ScanDetail> getScanResults() {
-        int next_sid = 0;
-        ArrayList<ScanDetail> results = new ArrayList<>();
-        while(next_sid >= 0) {
-            String rawResult = getRawScanResults(next_sid+"-");
-            next_sid = -1;
-
-            if (TextUtils.isEmpty(rawResult))
-                break;
-
-            String[] lines = rawResult.split("\n");
-
-
-            // note that all these splits and substrings keep references to the original
-            // huge string buffer while the amount we really want is generally pretty small
-            // so make copies instead (one example b/11087956 wasted 400k of heap here).
-            final int bssidStrLen = BSS_BSSID_STR.length();
-            final int flagLen = BSS_FLAGS_STR.length();
-
-            String bssid = "";
-            int level = 0;
-            int freq = 0;
-            long tsf = 0;
-            String flags = "";
-            WifiSsid wifiSsid = null;
-            String infoElementsStr = null;
-            List<String> anqpLines = null;
-
-            for (String line : lines) {
-                if (line.startsWith(BSS_ID_STR)) { // Will find the last id line
-                    try {
-                        next_sid = Integer.parseInt(line.substring(BSS_ID_STR.length())) + 1;
-                    } catch (NumberFormatException e) {
-                        // Nothing to do
-                    }
-                } else if (line.startsWith(BSS_BSSID_STR)) {
-                    bssid = new String(line.getBytes(), bssidStrLen, line.length() - bssidStrLen);
-                } else if (line.startsWith(BSS_FREQ_STR)) {
-                    try {
-                        freq = Integer.parseInt(line.substring(BSS_FREQ_STR.length()));
-                    } catch (NumberFormatException e) {
-                        freq = 0;
-                    }
-                } else if (line.startsWith(BSS_LEVEL_STR)) {
-                    try {
-                        level = Integer.parseInt(line.substring(BSS_LEVEL_STR.length()));
-                        /* some implementations avoid negative values by adding 256
-                         * so we need to adjust for that here.
-                         */
-                        if (level > 0) level -= 256;
-                    } catch (NumberFormatException e) {
-                        level = 0;
-                    }
-                } else if (line.startsWith(BSS_TSF_STR)) {
-                    try {
-                        tsf = Long.parseLong(line.substring(BSS_TSF_STR.length()));
-                    } catch (NumberFormatException e) {
-                        tsf = 0;
-                    }
-                } else if (line.startsWith(BSS_FLAGS_STR)) {
-                    flags = new String(line.getBytes(), flagLen, line.length() - flagLen);
-                } else if (line.startsWith(BSS_SSID_STR)) {
-                    wifiSsid = WifiSsid.createFromAsciiEncoded(
-                            line.substring(BSS_SSID_STR.length()));
-                } else if (line.startsWith(BSS_IE_STR)) {
-                    infoElementsStr = line;
-                } else if (SupplicantBridge.isAnqpAttribute(line)) {
-                    if (anqpLines == null) {
-                        anqpLines = new ArrayList<>();
-                    }
-                    anqpLines.add(line);
-                } else if (line.startsWith(BSS_DELIMITER_STR) || line.startsWith(BSS_END_STR)) {
-                    if (bssid != null) {
-                        try {
-                            if (infoElementsStr == null) {
-                                throw new IllegalArgumentException("Null information element data");
-                            }
-                            int seperator = infoElementsStr.indexOf('=');
-                            if (seperator < 0) {
-                                throw new IllegalArgumentException("No element separator");
-                            }
-
-                            ScanResult.InformationElement[] infoElements =
-                                        InformationElementUtil.parseInformationElements(
-                                        Utils.hexToBytes(infoElementsStr.substring(seperator + 1)));
-
-                            NetworkDetail networkDetail = new NetworkDetail(bssid,
-                                    infoElements, anqpLines, freq);
-                            String xssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
-                            if (!xssid.equals(networkDetail.getTrimmedSSID())) {
-                                Log.d(TAG, String.format(
-                                        "Inconsistent SSID on BSSID '%s': '%s' vs '%s': %s",
-                                        bssid, xssid, networkDetail.getSSID(), infoElementsStr));
-                            }
-
-                            if (networkDetail.hasInterworking()) {
-                                if (DBG) Log.d(TAG, "HSNwk: '" + networkDetail);
-                            }
-                            ScanDetail scan = new ScanDetail(networkDetail, wifiSsid, bssid, flags,
-                                    level, freq, tsf, infoElements, anqpLines);
-                            results.add(scan);
-                        } catch (IllegalArgumentException iae) {
-                            Log.d(TAG, "Failed to parse information elements: " + iae);
-                        }
-                    }
-                    bssid = null;
-                    level = 0;
-                    freq = 0;
-                    tsf = 0;
-                    flags = "";
-                    wifiSsid = null;
-                    infoElementsStr = null;
-                    anqpLines = null;
-                }
-            }
-        }
-        return results;
+        return mWificondControl.getScanResults();
     }
 
     /**
-     * Format of result:
-     * id=1016
-     * bssid=00:03:7f:40:84:10
-     * freq=2462
-     * beacon_int=200
-     * capabilities=0x0431
-     * qual=0
-     * noise=0
-     * level=-46
-     * tsf=0000002669008476
-     * age=5
-     * ie=00105143412d485332302d52322d54455354010882848b960c12182403010b0706555...
-     * flags=[WPA2-EAP-CCMP][ESS][P2P][HS20]
-     * ssid=QCA-HS20-R2-TEST
-     * p2p_device_name=
-     * p2p_config_methods=0x0SET_NE
-     * anqp_venue_name=02083d656e6757692d466920416c6c69616e63650a3239383920436f...
-     * anqp_network_auth_type=010000
-     * anqp_roaming_consortium=03506f9a05001bc504bd
-     * anqp_ip_addr_type_availability=0c
-     * anqp_nai_realm=0200300000246d61696c2e6578616d706c652e636f6d3b636973636f2...
-     * anqp_3gpp=000600040132f465
-     * anqp_domain_name=0b65786d61706c652e636f6d
-     * hs20_operator_friendly_name=11656e6757692d466920416c6c69616e63650e636869...
-     * hs20_wan_metrics=01c40900008001000000000a00
-     * hs20_connection_capability=0100000006140001061600000650000106bb010106bb0...
-     * hs20_osu_providers_list=0b5143412d4f53552d425353010901310015656e6757692d...
+     * Start PNO scan.
+     * @param pnoSettings Pno scan configuration.
+     * @return true on success.
      */
-    public String scanResult(String bssid) {
-        return doStringCommand("BSS " + bssid);
+    public boolean startPnoScan(PnoSettings pnoSettings) {
+        return mWificondControl.startPnoScan(pnoSettings);
     }
 
-    public boolean startDriver() {
-        return doBooleanCommand("DRIVER START");
+    /**
+     * Stop PNO scan.
+     * @return true on success.
+     */
+    public boolean stopPnoScan() {
+        return mWificondControl.stopPnoScan();
     }
 
-    public boolean stopDriver() {
-        return doBooleanCommand("DRIVER STOP");
+    /********************************************************
+     * Supplicant operations
+     ********************************************************/
+
+    /**
+     * This method is called repeatedly until the connection to wpa_supplicant is established.
+     *
+     * @return true if connection is established, false otherwise.
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    public boolean connectToSupplicant() {
+        // Start initialization if not already started.
+        if (!mSupplicantStaIfaceHal.isInitializationStarted()
+                && !mSupplicantStaIfaceHal.initialize()) {
+            return false;
+        }
+        // Check if the initialization is complete.
+        return mSupplicantStaIfaceHal.isInitializationComplete();
     }
 
+    /**
+     * Close supplicant connection.
+     */
+    public void closeSupplicantConnection() {
+        // Nothing to do for HIDL.
+    }
 
     /**
+     * Set supplicant log level
+     *
+     * @param turnOnVerbose Whether to turn on verbose logging or not.
+     */
+    public void setSupplicantLogLevel(boolean turnOnVerbose) {
+        mSupplicantStaIfaceHal.setLogLevel(turnOnVerbose);
+    }
+
+    /**
+     * Trigger a reconnection if the iface is disconnected.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean reconnect() {
+        return mSupplicantStaIfaceHal.reconnect();
+    }
+
+    /**
+     * Trigger a reassociation even if the iface is currently connected.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean reassociate() {
+        return mSupplicantStaIfaceHal.reassociate();
+    }
+
+    /**
+     * Trigger a disconnection from the currently connected network.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean disconnect() {
+        return mSupplicantStaIfaceHal.disconnect();
+    }
+
+    /**
+     * Makes a callback to HIDL to getMacAddress from supplicant
+     *
+     * @return string containing the MAC address, or null on a failed call
+     */
+    public String getMacAddress() {
+        return mSupplicantStaIfaceHal.getMacAddress();
+    }
+
+    public static final int RX_FILTER_TYPE_V4_MULTICAST = 0;
+    public static final int RX_FILTER_TYPE_V6_MULTICAST = 1;
+    /**
      * Start filtering out Multicast V4 packets
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      *
@@ -808,9 +338,10 @@
      * The  SETSUSPENDOPT driver command overrides the filtering rules
      */
     public boolean startFilteringMulticastV4Packets() {
-        return doBooleanCommand("DRIVER RXFILTER-STOP")
-            && doBooleanCommand("DRIVER RXFILTER-REMOVE 2")
-            && doBooleanCommand("DRIVER RXFILTER-START");
+        return mSupplicantStaIfaceHal.stopRxFilter()
+                && mSupplicantStaIfaceHal.removeRxFilter(
+                RX_FILTER_TYPE_V4_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter();
     }
 
     /**
@@ -818,9 +349,10 @@
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      */
     public boolean stopFilteringMulticastV4Packets() {
-        return doBooleanCommand("DRIVER RXFILTER-STOP")
-            && doBooleanCommand("DRIVER RXFILTER-ADD 2")
-            && doBooleanCommand("DRIVER RXFILTER-START");
+        return mSupplicantStaIfaceHal.stopRxFilter()
+                && mSupplicantStaIfaceHal.addRxFilter(
+                RX_FILTER_TYPE_V4_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter();
     }
 
     /**
@@ -828,9 +360,10 @@
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      */
     public boolean startFilteringMulticastV6Packets() {
-        return doBooleanCommand("DRIVER RXFILTER-STOP")
-            && doBooleanCommand("DRIVER RXFILTER-REMOVE 3")
-            && doBooleanCommand("DRIVER RXFILTER-START");
+        return mSupplicantStaIfaceHal.stopRxFilter()
+                && mSupplicantStaIfaceHal.removeRxFilter(
+                RX_FILTER_TYPE_V6_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter();
     }
 
     /**
@@ -838,34 +371,15 @@
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      */
     public boolean stopFilteringMulticastV6Packets() {
-        return doBooleanCommand("DRIVER RXFILTER-STOP")
-            && doBooleanCommand("DRIVER RXFILTER-ADD 3")
-            && doBooleanCommand("DRIVER RXFILTER-START");
+        return mSupplicantStaIfaceHal.stopRxFilter()
+                && mSupplicantStaIfaceHal.addRxFilter(
+                RX_FILTER_TYPE_V6_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter();
     }
 
-    /**
-     * Set the operational frequency band
-     * @param band One of
-     *     {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
-     *     {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
-     *     {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
-     * @return {@code true} if the operation succeeded, {@code false} otherwise
-     */
-    public boolean setBand(int band) {
-        String bandstr;
-
-        if (band == WifiManager.WIFI_FREQUENCY_BAND_5GHZ)
-            bandstr = "5G";
-        else if (band == WifiManager.WIFI_FREQUENCY_BAND_2GHZ)
-            bandstr = "2G";
-        else
-            bandstr = "AUTO";
-        return doBooleanCommand("SET SETBAND " + bandstr);
-    }
-
-    public static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED     = 0;
-    public static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED    = 1;
-    public static final int BLUETOOTH_COEXISTENCE_MODE_SENSE       = 2;
+    public static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED  = 0;
+    public static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1;
+    public static final int BLUETOOTH_COEXISTENCE_MODE_SENSE    = 2;
     /**
       * Sets the bluetooth coexistence mode.
       *
@@ -875,7 +389,7 @@
       * @return Whether the mode was successfully set.
       */
     public boolean setBluetoothCoexistenceMode(int mode) {
-        return doBooleanCommand("DRIVER BTCOEXMODE " + mode);
+        return mSupplicantStaIfaceHal.setBtCoexistenceMode(mode);
     }
 
     /**
@@ -883,758 +397,456 @@
      * 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) {
-        if (setCoexScanMode) {
-            return doBooleanCommand("DRIVER BTCOEXSCAN-START");
-        } else {
-            return doBooleanCommand("DRIVER BTCOEXSCAN-STOP");
-        }
-    }
-
-    public void enableSaveConfig() {
-        doBooleanCommand("SET update_config 1");
-    }
-
-    public boolean saveConfig() {
-        return doBooleanCommand("SAVE_CONFIG");
-    }
-
-    public boolean addToBlacklist(String bssid) {
-        if (TextUtils.isEmpty(bssid)) return false;
-        return doBooleanCommand("BLACKLIST " + bssid);
-    }
-
-    public boolean clearBlacklist() {
-        return doBooleanCommand("BLACKLIST clear");
-    }
-
-    public boolean setSuspendOptimizations(boolean enabled) {
-        if (enabled) {
-            return doBooleanCommand("DRIVER SETSUSPENDMODE 1");
-        } else {
-            return doBooleanCommand("DRIVER SETSUSPENDMODE 0");
-        }
-    }
-
-    public boolean setCountryCode(String countryCode) {
-        if (countryCode != null)
-            return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT));
-        else
-            return doBooleanCommand("DRIVER COUNTRY");
+        return mSupplicantStaIfaceHal.setBtCoexistenceScanModeEnabled(setCoexScanMode);
     }
 
     /**
-     * Start/Stop PNO scan.
-     * @param enable boolean indicating whether PNO is being enabled or disabled.
+     * Enable or disable suspend mode optimizations.
+     *
+     * @param enabled true to enable, false otherwise.
+     * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setPnoScan(boolean enable) {
-        String cmd = enable ? "SET pno 1" : "SET pno 0";
-        return doBooleanCommand(cmd);
+    public boolean setSuspendOptimizations(boolean enabled) {
+        return mSupplicantStaIfaceHal.setSuspendModeEnabled(enabled);
     }
 
-    public void enableAutoConnect(boolean enable) {
-        if (enable) {
-            doBooleanCommand("STA_AUTOCONNECT 1");
-        } else {
-            doBooleanCommand("STA_AUTOCONNECT 0");
-        }
+    /**
+     * Set country code.
+     *
+     * @param countryCode 2 byte ASCII string. For ex: US, CA.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setCountryCode(String countryCode) {
+        return mSupplicantStaIfaceHal.setCountryCode(countryCode);
     }
 
-    public void setScanInterval(int scanInterval) {
-        doBooleanCommand("SCAN_INTERVAL " + scanInterval);
-    }
-
-    public void setHs20(boolean hs20) {
-        if (hs20) {
-            doBooleanCommand("SET HS20 1");
-        } else {
-            doBooleanCommand("SET HS20 0");
-        }
-    }
-
+    /**
+     * Initiate TDLS discover and setup or teardown with the specified peer.
+     *
+     * @param macAddr MAC Address of the peer.
+     * @param enable true to start discovery and setup, false to teardown.
+     */
     public void startTdls(String macAddr, boolean enable) {
         if (enable) {
-            synchronized (sLock) {
-                doBooleanCommand("TDLS_DISCOVER " + macAddr);
-                doBooleanCommand("TDLS_SETUP " + macAddr);
-            }
+            mSupplicantStaIfaceHal.initiateTdlsDiscover(macAddr);
+            mSupplicantStaIfaceHal.initiateTdlsSetup(macAddr);
         } else {
-            doBooleanCommand("TDLS_TEARDOWN " + macAddr);
+            mSupplicantStaIfaceHal.initiateTdlsTeardown(macAddr);
         }
     }
 
-    /** Example output:
-     * RSSI=-65
-     * LINKSPEED=48
-     * NOISE=9999
-     * FREQUENCY=0
-     */
-    public String signalPoll() {
-        return doStringCommandWithoutLogging("SIGNAL_POLL");
-    }
-
-    /** Example outout:
-     * TXGOOD=396
-     * TXBAD=1
-     */
-    public String pktcntPoll() {
-        return doStringCommand("PKTCNT_POLL");
-    }
-
-    public void bssFlush() {
-        doBooleanCommand("BSS_FLUSH 0");
-    }
-
-    public boolean startWpsPbc(String bssid) {
-        if (TextUtils.isEmpty(bssid)) {
-            return doBooleanCommand("WPS_PBC");
-        } else {
-            return doBooleanCommand("WPS_PBC " + bssid);
-        }
-    }
-
-    public boolean startWpsPbc(String iface, String bssid) {
-        synchronized (sLock) {
-            if (TextUtils.isEmpty(bssid)) {
-                return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC");
-            } else {
-                return doBooleanCommandNative("IFNAME=" + iface + " WPS_PBC " + bssid);
-            }
-        }
-    }
-
-    public boolean startWpsPinKeypad(String pin) {
-        if (TextUtils.isEmpty(pin)) return false;
-        return doBooleanCommand("WPS_PIN any " + pin);
-    }
-
-    public boolean startWpsPinKeypad(String iface, String pin) {
-        if (TextUtils.isEmpty(pin)) return false;
-        synchronized (sLock) {
-            return doBooleanCommandNative("IFNAME=" + iface + " WPS_PIN any " + pin);
-        }
-    }
-
-
-    public String startWpsPinDisplay(String bssid) {
-        if (TextUtils.isEmpty(bssid)) {
-            return doStringCommand("WPS_PIN any");
-        } else {
-            return doStringCommand("WPS_PIN " + bssid);
-        }
-    }
-
-    public String startWpsPinDisplay(String iface, String bssid) {
-        synchronized (sLock) {
-            if (TextUtils.isEmpty(bssid)) {
-                return doStringCommandNative("IFNAME=" + iface + " WPS_PIN any");
-            } else {
-                return doStringCommandNative("IFNAME=" + iface + " WPS_PIN " + bssid);
-            }
-        }
-    }
-
-    public boolean setExternalSim(boolean external) {
-        String value = external ? "1" : "0";
-        Log.d(TAG, "Setting external_sim to " + value);
-        return doBooleanCommand("SET external_sim " + value);
-    }
-
-    public boolean simAuthResponse(int id, String type, String response) {
-        // with type = GSM-AUTH, UMTS-AUTH or UMTS-AUTS
-        return doBooleanCommand("CTRL-RSP-SIM-" + id + ":" + type + response);
-    }
-
-    public boolean simAuthFailedResponse(int id) {
-        // should be used with type GSM-AUTH
-        return doBooleanCommand("CTRL-RSP-SIM-" + id + ":GSM-FAIL");
-    }
-
-    public boolean umtsAuthFailedResponse(int id) {
-        // should be used with type UMTS-AUTH
-        return doBooleanCommand("CTRL-RSP-SIM-" + id + ":UMTS-FAIL");
-    }
-
-    public boolean simIdentityResponse(int id, String response) {
-        return doBooleanCommand("CTRL-RSP-IDENTITY-" + id + ":" + response);
-    }
-
-    /* Configures an access point connection */
-    public boolean startWpsRegistrar(String bssid, String pin) {
-        if (TextUtils.isEmpty(bssid) || TextUtils.isEmpty(pin)) return false;
-        return doBooleanCommand("WPS_REG " + bssid + " " + pin);
-    }
-
-    public boolean cancelWps() {
-        return doBooleanCommand("WPS_CANCEL");
-    }
-
-    public boolean setPersistentReconnect(boolean enabled) {
-        int value = (enabled == true) ? 1 : 0;
-        return doBooleanCommand("SET persistent_reconnect " + value);
-    }
-
-    public boolean setDeviceName(String name) {
-        return doBooleanCommand("SET device_name " + name);
-    }
-
-    public boolean setDeviceType(String type) {
-        return doBooleanCommand("SET device_type " + type);
-    }
-
-    public boolean setConfigMethods(String cfg) {
-        return doBooleanCommand("SET config_methods " + cfg);
-    }
-
-    public boolean setManufacturer(String value) {
-        return doBooleanCommand("SET manufacturer " + value);
-    }
-
-    public boolean setModelName(String value) {
-        return doBooleanCommand("SET model_name " + value);
-    }
-
-    public boolean setModelNumber(String value) {
-        return doBooleanCommand("SET model_number " + value);
-    }
-
-    public boolean setSerialNumber(String value) {
-        return doBooleanCommand("SET serial_number " + value);
-    }
-
-    public boolean setP2pSsidPostfix(String postfix) {
-        return doBooleanCommand("SET p2p_ssid_postfix " + postfix);
-    }
-
-    public boolean setP2pGroupIdle(String iface, int time) {
-        synchronized (sLock) {
-            return doBooleanCommandNative("IFNAME=" + iface + " SET p2p_group_idle " + time);
-        }
-    }
-
-    public void setPowerSave(boolean enabled) {
-        if (enabled) {
-            doBooleanCommand("SET ps 1");
-        } else {
-            doBooleanCommand("SET ps 0");
-        }
-    }
-
-    public boolean setP2pPowerSave(String iface, boolean enabled) {
-        synchronized (sLock) {
-            if (enabled) {
-                return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 1");
-            } else {
-                return doBooleanCommandNative("IFNAME=" + iface + " P2P_SET ps 0");
-            }
-        }
-    }
-
-    public boolean setWfdEnable(boolean enable) {
-        return doBooleanCommand("SET wifi_display " + (enable ? "1" : "0"));
-    }
-
-    public boolean setWfdDeviceInfo(String hex) {
-        return doBooleanCommand("WFD_SUBELEM_SET 0 " + hex);
-    }
-
     /**
-     * "sta" prioritizes STA connection over P2P and "p2p" prioritizes
-     * P2P connection over STA
+     * Start WPS pin display operation with the specified peer.
+     *
+     * @param bssid BSSID of the peer.
+     * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setConcurrencyPriority(String s) {
-        return doBooleanCommand("P2P_SET conc_pref " + s);
+    public boolean startWpsPbc(String bssid) {
+        return mSupplicantStaIfaceHal.startWpsPbc(bssid);
     }
 
-    public boolean p2pFind() {
-        return doBooleanCommand("P2P_FIND");
+    /**
+     * Start WPS pin keypad operation with the specified pin.
+     *
+     * @param pin Pin to be used.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean startWpsPinKeypad(String pin) {
+        return mSupplicantStaIfaceHal.startWpsPinKeypad(pin);
     }
 
-    public boolean p2pFind(int timeout) {
-        if (timeout <= 0) {
-            return p2pFind();
-        }
-        return doBooleanCommand("P2P_FIND " + timeout);
+    /**
+     * Start WPS pin display operation with the specified peer.
+     *
+     * @param bssid BSSID of the peer.
+     * @return new pin generated on success, null otherwise.
+     */
+    public String startWpsPinDisplay(String bssid) {
+        return mSupplicantStaIfaceHal.startWpsPinDisplay(bssid);
     }
 
-    public boolean p2pStopFind() {
-       return doBooleanCommand("P2P_STOP_FIND");
+    /**
+     * Sets whether to use external sim for SIM/USIM processing.
+     *
+     * @param external true to enable, false otherwise.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setExternalSim(boolean external) {
+        return mSupplicantStaIfaceHal.setExternalSim(external);
     }
 
-    public boolean p2pListen() {
-        return doBooleanCommand("P2P_LISTEN");
-    }
+    /**
+     * Sim auth response types.
+     */
+    public static final String SIM_AUTH_RESP_TYPE_GSM_AUTH = "GSM-AUTH";
+    public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTH = "UMTS-AUTH";
+    public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTS = "UMTS-AUTS";
 
-    public boolean p2pListen(int timeout) {
-        if (timeout <= 0) {
-            return p2pListen();
-        }
-        return doBooleanCommand("P2P_LISTEN " + timeout);
-    }
-
-    public boolean p2pExtListen(boolean enable, int period, int interval) {
-        if (enable && interval < period) {
+    /**
+     * Send the sim auth response for the currently configured network.
+     *
+     * @param type |GSM-AUTH|, |UMTS-AUTH| or |UMTS-AUTS|.
+     * @param response Response params.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean simAuthResponse(int id, String type, String response) {
+        if (SIM_AUTH_RESP_TYPE_GSM_AUTH.equals(type)) {
+            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthResponse(response);
+        } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTH.equals(type)) {
+            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthResponse(response);
+        } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTS.equals(type)) {
+            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAutsResponse(response);
+        } else {
             return false;
         }
-        return doBooleanCommand("P2P_EXT_LISTEN"
-                    + (enable ? (" " + period + " " + interval) : ""));
     }
 
-    public boolean p2pSetChannel(int lc, int oc) {
-        if (DBG) Log.d(mTAG, "p2pSetChannel: lc="+lc+", oc="+oc);
-
-        synchronized (sLock) {
-            if (lc >=1 && lc <= 11) {
-                if (!doBooleanCommand("P2P_SET listen_channel " + lc)) {
-                    return false;
-                }
-            } else if (lc != 0) {
-                return false;
-            }
-
-            if (oc >= 1 && oc <= 165 ) {
-                int freq = (oc <= 14 ? 2407 : 5000) + oc * 5;
-                return doBooleanCommand("P2P_SET disallow_freq 1000-"
-                        + (freq - 5) + "," + (freq + 5) + "-6000");
-            } else if (oc == 0) {
-                /* oc==0 disables "P2P_SET disallow_freq" (enables all freqs) */
-                return doBooleanCommand("P2P_SET disallow_freq \"\"");
-            }
-        }
-        return false;
-    }
-
-    public boolean p2pFlush() {
-        return doBooleanCommand("P2P_FLUSH");
-    }
-
-    private static final int DEFAULT_GROUP_OWNER_INTENT     = 6;
-    /* p2p_connect <peer device address> <pbc|pin|PIN#> [label|display|keypad]
-        [persistent] [join|auth] [go_intent=<0..15>] [freq=<in MHz>] */
-    public String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) {
-        if (config == null) return null;
-        List<String> args = new ArrayList<String>();
-        WpsInfo wps = config.wps;
-        args.add(config.deviceAddress);
-
-        switch (wps.setup) {
-            case WpsInfo.PBC:
-                args.add("pbc");
-                break;
-            case WpsInfo.DISPLAY:
-                if (TextUtils.isEmpty(wps.pin)) {
-                    args.add("pin");
-                } else {
-                    args.add(wps.pin);
-                }
-                args.add("display");
-                break;
-            case WpsInfo.KEYPAD:
-                args.add(wps.pin);
-                args.add("keypad");
-                break;
-            case WpsInfo.LABEL:
-                args.add(wps.pin);
-                args.add("label");
-            default:
-                break;
-        }
-
-        if (config.netId == WifiP2pGroup.PERSISTENT_NET_ID) {
-            args.add("persistent");
-        }
-
-        if (joinExistingGroup) {
-            args.add("join");
-        } else {
-            //TODO: This can be adapted based on device plugged in state and
-            //device battery state
-            int groupOwnerIntent = config.groupOwnerIntent;
-            if (groupOwnerIntent < 0 || groupOwnerIntent > 15) {
-                groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT;
-            }
-            args.add("go_intent=" + groupOwnerIntent);
-        }
-
-        String command = "P2P_CONNECT ";
-        for (String s : args) command += s + " ";
-
-        return doStringCommand(command);
-    }
-
-    public boolean p2pCancelConnect() {
-        return doBooleanCommand("P2P_CANCEL");
-    }
-
-    public boolean p2pProvisionDiscovery(WifiP2pConfig config) {
-        if (config == null) return false;
-
-        switch (config.wps.setup) {
-            case WpsInfo.PBC:
-                return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " pbc");
-            case WpsInfo.DISPLAY:
-                //We are doing display, so provision discovery is keypad
-                return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " keypad");
-            case WpsInfo.KEYPAD:
-                //We are doing keypad, so provision discovery is display
-                return doBooleanCommand("P2P_PROV_DISC " + config.deviceAddress + " display");
-            default:
-                break;
-        }
-        return false;
-    }
-
-    public boolean p2pGroupAdd(boolean persistent) {
-        if (persistent) {
-            return doBooleanCommand("P2P_GROUP_ADD persistent");
-        }
-        return doBooleanCommand("P2P_GROUP_ADD");
-    }
-
-    public boolean p2pGroupAdd(int netId) {
-        return doBooleanCommand("P2P_GROUP_ADD persistent=" + netId);
-    }
-
-    public boolean p2pGroupRemove(String iface) {
-        if (TextUtils.isEmpty(iface)) return false;
-        synchronized (sLock) {
-            return doBooleanCommandNative("IFNAME=" + iface + " P2P_GROUP_REMOVE " + iface);
-        }
-    }
-
-    public boolean p2pReject(String deviceAddress) {
-        return doBooleanCommand("P2P_REJECT " + deviceAddress);
-    }
-
-    /* Invite a peer to a group */
-    public boolean p2pInvite(WifiP2pGroup group, String deviceAddress) {
-        if (TextUtils.isEmpty(deviceAddress)) return false;
-
-        if (group == null) {
-            return doBooleanCommand("P2P_INVITE peer=" + deviceAddress);
-        } else {
-            return doBooleanCommand("P2P_INVITE group=" + group.getInterface()
-                    + " peer=" + deviceAddress + " go_dev_addr=" + group.getOwner().deviceAddress);
-        }
-    }
-
-    /* Reinvoke a persistent connection */
-    public boolean p2pReinvoke(int netId, String deviceAddress) {
-        if (TextUtils.isEmpty(deviceAddress) || netId < 0) return false;
-
-        return doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + deviceAddress);
-    }
-
-    public String p2pGetSsid(String deviceAddress) {
-        return p2pGetParam(deviceAddress, "oper_ssid");
-    }
-
-    public String p2pGetDeviceAddress() {
-        Log.d(TAG, "p2pGetDeviceAddress");
-
-        String status = null;
-
-        /* Explicitly calling the API without IFNAME= prefix to take care of the devices that
-        don't have p2p0 interface. Supplicant seems to be returning the correct address anyway. */
-
-        synchronized (sLock) {
-            status = doStringCommandNative("STATUS");
-        }
-
-        String result = "";
-        if (status != null) {
-            String[] tokens = status.split("\n");
-            for (String token : tokens) {
-                if (token.startsWith("p2p_device_address=")) {
-                    String[] nameValue = token.split("=");
-                    if (nameValue.length != 2)
-                        break;
-                    result = nameValue[1];
-                }
-            }
-        }
-
-        Log.d(TAG, "p2pGetDeviceAddress returning " + result);
-        return result;
-    }
-
-    public int getGroupCapability(String deviceAddress) {
-        int gc = 0;
-        if (TextUtils.isEmpty(deviceAddress)) return gc;
-        String peerInfo = p2pPeer(deviceAddress);
-        if (TextUtils.isEmpty(peerInfo)) return gc;
-
-        String[] tokens = peerInfo.split("\n");
-        for (String token : tokens) {
-            if (token.startsWith("group_capab=")) {
-                String[] nameValue = token.split("=");
-                if (nameValue.length != 2) break;
-                try {
-                    return Integer.decode(nameValue[1]);
-                } catch(NumberFormatException e) {
-                    return gc;
-                }
-            }
-        }
-        return gc;
-    }
-
-    public String p2pPeer(String deviceAddress) {
-        return doStringCommand("P2P_PEER " + deviceAddress);
-    }
-
-    private String p2pGetParam(String deviceAddress, String key) {
-        if (deviceAddress == null) return null;
-
-        String peerInfo = p2pPeer(deviceAddress);
-        if (peerInfo == null) return null;
-        String[] tokens= peerInfo.split("\n");
-
-        key += "=";
-        for (String token : tokens) {
-            if (token.startsWith(key)) {
-                String[] nameValue = token.split("=");
-                if (nameValue.length != 2) break;
-                return nameValue[1];
-            }
-        }
-        return null;
-    }
-
-    public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
-        /*
-         * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
-         * P2P_SERVICE_ADD upnp <version hex> <service>
-         *
-         * e.g)
-         * [Bonjour]
-         * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
-         * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027
-         * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
-         * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001
-         *  09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074
-         *
-         * [UPnP]
-         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012
-         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice
-         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
-         * -org:device:InternetGatewayDevice:1
-         * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
-         * -org:service:ContentDirectory:2
-         */
-        synchronized (sLock) {
-            for (String s : servInfo.getSupplicantQueryList()) {
-                String command = "P2P_SERVICE_ADD";
-                command += (" " + s);
-                if (!doBooleanCommand(command)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
-        /*
-         * P2P_SERVICE_DEL bonjour <query hexdump>
-         * P2P_SERVICE_DEL upnp <version hex> <service>
-         */
-        synchronized (sLock) {
-            for (String s : servInfo.getSupplicantQueryList()) {
-                String command = "P2P_SERVICE_DEL ";
-
-                String[] data = s.split(" ");
-                if (data.length < 2) {
-                    return false;
-                }
-                if ("upnp".equals(data[0])) {
-                    command += s;
-                } else if ("bonjour".equals(data[0])) {
-                    command += data[0];
-                    command += (" " + data[1]);
-                } else {
-                    return false;
-                }
-                if (!doBooleanCommand(command)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    public boolean p2pServiceFlush() {
-        return doBooleanCommand("P2P_SERVICE_FLUSH");
-    }
-
-    public String p2pServDiscReq(String addr, String query) {
-        String command = "P2P_SERV_DISC_REQ";
-        command += (" " + addr);
-        command += (" " + query);
-
-        return doStringCommand(command);
-    }
-
-    public boolean p2pServDiscCancelReq(String id) {
-        return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id);
-    }
-
-    /* Set the current mode of miracast operation.
-     *  0 = disabled
-     *  1 = operating as source
-     *  2 = operating as sink
+    /**
+     * Send the eap sim gsm auth failure for the currently configured network.
+     *
+     * @return true if succeeds, false otherwise.
      */
-    public void setMiracastMode(int mode) {
-        // Note: optional feature on the driver. It is ok for this to fail.
-        doBooleanCommand("DRIVER MIRACAST " + mode);
+    public boolean simAuthFailedResponse(int id) {
+        return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthFailure();
     }
 
-    public boolean fetchAnqp(String bssid, String subtypes) {
-        return doBooleanCommand("ANQP_GET " + bssid + " " + subtypes);
-    }
-
-    /*
-     * NFC-related calls
+    /**
+     * Send the eap sim umts auth failure for the currently configured network.
+     *
+     * @return true if succeeds, false otherwise.
      */
-    public String getNfcWpsConfigurationToken(int netId) {
-        return doStringCommand("WPS_NFC_CONFIG_TOKEN WPS " + netId);
+    public boolean umtsAuthFailedResponse(int id) {
+        return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthFailure();
     }
 
-    public String getNfcHandoverRequest() {
-        return doStringCommand("NFC_GET_HANDOVER_REQ NDEF P2P-CR");
+    /**
+     * Send the eap identity response for the currently configured network.
+     *
+     * @param response String to send.
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean simIdentityResponse(int id, String response) {
+        return mSupplicantStaIfaceHal.sendCurrentNetworkEapIdentityResponse(response);
     }
 
-    public String getNfcHandoverSelect() {
-        return doStringCommand("NFC_GET_HANDOVER_SEL NDEF P2P-CR");
+    /**
+     * This get anonymous identity from supplicant and returns it as a string.
+     *
+     * @return anonymous identity string if succeeds, null otherwise.
+     */
+    public String getEapAnonymousIdentity() {
+        return mSupplicantStaIfaceHal.getCurrentNetworkEapAnonymousIdentity();
     }
 
-    public boolean initiatorReportNfcHandover(String selectMessage) {
-        return doBooleanCommand("NFC_REPORT_HANDOVER INIT P2P 00 " + selectMessage);
+    /**
+     * Start WPS pin registrar operation with the specified peer and pin.
+     *
+     * @param bssid BSSID of the peer.
+     * @param pin Pin to be used.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean startWpsRegistrar(String bssid, String pin) {
+        return mSupplicantStaIfaceHal.startWpsRegistrar(bssid, pin);
     }
 
-    public boolean responderReportNfcHandover(String requestMessage) {
-        return doBooleanCommand("NFC_REPORT_HANDOVER RESP P2P " + requestMessage + " 00");
+    /**
+     * Cancels any ongoing WPS requests.
+     *
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean cancelWps() {
+        return mSupplicantStaIfaceHal.cancelWps();
     }
 
+    /**
+     * Set WPS device name.
+     *
+     * @param name String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setDeviceName(String name) {
+        return mSupplicantStaIfaceHal.setWpsDeviceName(name);
+    }
 
-    /* kernel logging support */
-    private static native byte[] readKernelLogNative();
+    /**
+     * Set WPS device type.
+     *
+     * @param type Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setDeviceType(String type) {
+        return mSupplicantStaIfaceHal.setWpsDeviceType(type);
+    }
 
-    synchronized public String readKernelLog() {
-        byte[] bytes = readKernelLogNative();
-        if (bytes != null) {
-            CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
-            try {
-                CharBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes));
-                return decoded.toString();
-            } catch (CharacterCodingException cce) {
-                return new String(bytes, StandardCharsets.ISO_8859_1);
-            }
-        } else {
-            return "*** failed to read kernel log ***";
+    /**
+     * Set WPS config methods
+     *
+     * @param cfg List of config methods.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setConfigMethods(String cfg) {
+        return mSupplicantStaIfaceHal.setWpsConfigMethods(cfg);
+    }
+
+    /**
+     * Set WPS manufacturer.
+     *
+     * @param value String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setManufacturer(String value) {
+        return mSupplicantStaIfaceHal.setWpsManufacturer(value);
+    }
+
+    /**
+     * Set WPS model name.
+     *
+     * @param value String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setModelName(String value) {
+        return mSupplicantStaIfaceHal.setWpsModelName(value);
+    }
+
+    /**
+     * Set WPS model number.
+     *
+     * @param value String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setModelNumber(String value) {
+        return mSupplicantStaIfaceHal.setWpsModelNumber(value);
+    }
+
+    /**
+     * Set WPS serial number.
+     *
+     * @param value String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setSerialNumber(String value) {
+        return mSupplicantStaIfaceHal.setWpsSerialNumber(value);
+    }
+
+    /**
+     * Enable or disable power save mode.
+     *
+     * @param enabled true to enable, false to disable.
+     */
+    public void setPowerSave(boolean enabled) {
+        mSupplicantStaIfaceHal.setPowerSave(enabled);
+    }
+
+    /**
+     * Set concurrency priority between P2P & STA operations.
+     *
+     * @param isStaHigherPriority Set to true to prefer STA over P2P during concurrency operations,
+     *                            false otherwise.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setConcurrencyPriority(boolean isStaHigherPriority) {
+        return mSupplicantStaIfaceHal.setConcurrencyPriority(isStaHigherPriority);
+    }
+
+    /**
+     * Enable/Disable auto reconnect functionality in wpa_supplicant.
+     *
+     * @param enable true to enable auto reconnecting, false to disable.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean enableStaAutoReconnect(boolean enable) {
+        return mSupplicantStaIfaceHal.enableAutoReconnect(enable);
+    }
+
+    /**
+     * Migrate all the configured networks from wpa_supplicant.
+     *
+     * @param configs       Map of configuration key to configuration objects corresponding to all
+     *                      the networks.
+     * @param networkExtras Map of extra configuration parameters stored in wpa_supplicant.conf
+     * @return Max priority of all the configs.
+     */
+    public boolean migrateNetworksFromSupplicant(Map<String, WifiConfiguration> configs,
+                                                 SparseArray<Map<String, String>> networkExtras) {
+        return mSupplicantStaIfaceHal.loadNetworks(configs, networkExtras);
+    }
+
+    /**
+     * Add the provided network configuration to wpa_supplicant and initiate connection to it.
+     * This method does the following:
+     * 1. Abort any ongoing scan to unblock the connection request.
+     * 2. Remove any existing network in wpa_supplicant(This implicitly triggers disconnect).
+     * 3. Add a new network to wpa_supplicant.
+     * 4. Save the provided configuration to wpa_supplicant.
+     * 5. Select the new network in wpa_supplicant.
+     * 6. Triggers reconnect command to wpa_supplicant.
+     *
+     * @param configuration WifiConfiguration parameters for the provided network.
+     * @return {@code true} if it succeeds, {@code false} otherwise
+     */
+    public boolean connectToNetwork(WifiConfiguration configuration) {
+        // Abort ongoing scan before connect() to unblock connection request.
+        mWificondControl.abortScan();
+        return mSupplicantStaIfaceHal.connectToNetwork(configuration);
+    }
+
+    /**
+     * Initiates roaming to the already configured network in wpa_supplicant. If the network
+     * configuration provided does not match the already configured network, then this triggers
+     * a new connection attempt (instead of roam).
+     * 1. Abort any ongoing scan to unblock the roam request.
+     * 2. First check if we're attempting to connect to the same network as we currently have
+     * configured.
+     * 3. Set the new bssid for the network in wpa_supplicant.
+     * 4. Triggers reassociate command to wpa_supplicant.
+     *
+     * @param configuration WifiConfiguration parameters for the provided network.
+     * @return {@code true} if it succeeds, {@code false} otherwise
+     */
+    public boolean roamToNetwork(WifiConfiguration configuration) {
+        // Abort ongoing scan before connect() to unblock roaming request.
+        mWificondControl.abortScan();
+        return mSupplicantStaIfaceHal.roamToNetwork(configuration);
+    }
+
+    /**
+     * Get the framework network ID corresponding to the provided supplicant network ID for the
+     * network configured in wpa_supplicant.
+     *
+     * @param supplicantNetworkId network ID in wpa_supplicant for the network.
+     * @return Corresponding framework network ID if found, -1 if network not found.
+     */
+    public int getFrameworkNetworkId(int supplicantNetworkId) {
+        return supplicantNetworkId;
+    }
+
+    /**
+     * Remove all the networks.
+     *
+     * @return {@code true} if it succeeds, {@code false} otherwise
+     */
+    public boolean removeAllNetworks() {
+        return mSupplicantStaIfaceHal.removeAllNetworks();
+    }
+
+    /**
+     * Set the BSSID for the currently configured network in wpa_supplicant.
+     *
+     * @return true if successful, false otherwise.
+     */
+    public boolean setConfiguredNetworkBSSID(String bssid) {
+        return mSupplicantStaIfaceHal.setCurrentNetworkBssid(bssid);
+    }
+
+    /**
+     * Initiate ANQP query.
+     *
+     * @param bssid BSSID of the AP to be queried
+     * @param anqpIds Set of anqp IDs.
+     * @param hs20Subtypes Set of HS20 subtypes.
+     * @return true on success, false otherwise.
+     */
+    public boolean requestAnqp(String bssid, Set<Integer> anqpIds, Set<Integer> hs20Subtypes) {
+        if (bssid == null || ((anqpIds == null || anqpIds.isEmpty())
+                && (hs20Subtypes == null || hs20Subtypes.isEmpty()))) {
+            Log.e(mTAG, "Invalid arguments for ANQP request.");
+            return false;
         }
-    }
-
-    /* WIFI HAL support */
-
-    // HAL command ids
-    private static int sCmdId = 1;
-    private static int getNewCmdIdLocked() {
-        return sCmdId++;
-    }
-
-    private static final String TAG = "WifiNative-HAL";
-    private static long sWifiHalHandle = 0;             /* used by JNI to save wifi_handle */
-    private static long[] sWifiIfaceHandles = null;     /* used by JNI to save interface handles */
-    public static int sWlan0Index = -1;
-    private static MonitorThread sThread;
-    private static final int STOP_HAL_TIMEOUT_MS = 1000;
-
-    private static native boolean startHalNative();
-    private static native void stopHalNative();
-    private static native void waitForHalEventNative();
-
-    private static class MonitorThread extends Thread {
-        public void run() {
-            Log.i(TAG, "Waiting for HAL events mWifiHalHandle=" + Long.toString(sWifiHalHandle));
-            waitForHalEventNative();
+        ArrayList<Short> anqpIdList = new ArrayList<>();
+        for (Integer anqpId : anqpIds) {
+            anqpIdList.add(anqpId.shortValue());
         }
+        ArrayList<Integer> hs20SubtypeList = new ArrayList<>();
+        hs20SubtypeList.addAll(hs20Subtypes);
+        return mSupplicantStaIfaceHal.initiateAnqpQuery(bssid, anqpIdList, hs20SubtypeList);
     }
 
-    public boolean startHal() {
-        String debugLog = "startHal stack: ";
-        java.lang.StackTraceElement[] elements = Thread.currentThread().getStackTrace();
-        for (int i = 2; i < elements.length && i <= 7; i++ ) {
-            debugLog = debugLog + " - " + elements[i].getMethodName();
+    /**
+     * Request a passpoint icon file |filename| from the specified AP |bssid|.
+     * @param bssid BSSID of the AP
+     * @param fileName name of the icon file
+     * @return true if request is sent successfully, false otherwise
+     */
+    public boolean requestIcon(String  bssid, String fileName) {
+        if (bssid == null || fileName == null) {
+            Log.e(mTAG, "Invalid arguments for Icon request.");
+            return false;
         }
-
-        sLocalLog.log(debugLog);
-
-        synchronized (sLock) {
-            if (startHalNative()) {
-                int wlan0Index = queryInterfaceIndex(mInterfaceName);
-                if (wlan0Index == -1) {
-                    if (DBG) sLocalLog.log("Could not find interface with name: " + mInterfaceName);
-                    return false;
-                }
-                sWlan0Index = wlan0Index;
-                sThread = new MonitorThread();
-                sThread.start();
-                return true;
-            } else {
-                if (DBG) sLocalLog.log("Could not start hal");
-                Log.e(TAG, "Could not start hal");
-                return false;
-            }
-        }
+        return mSupplicantStaIfaceHal.initiateHs20IconQuery(bssid, fileName);
     }
 
-    public void stopHal() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                stopHalNative();
-                try {
-                    sThread.join(STOP_HAL_TIMEOUT_MS);
-                    Log.d(TAG, "HAL event thread stopped successfully");
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "Could not stop HAL cleanly");
-                }
-                sThread = null;
-                sWifiHalHandle = 0;
-                sWifiIfaceHandles = null;
-                sWlan0Index = -1;
-            }
-        }
+    /**
+     * Get the currently configured network's WPS NFC token.
+     *
+     * @return Hex string corresponding to the WPS NFC token.
+     */
+    public String getCurrentNetworkWpsNfcConfigurationToken() {
+        return mSupplicantStaIfaceHal.getCurrentNetworkWpsNfcConfigurationToken();
     }
 
+    /** Remove the request |networkId| from supplicant if it's the current network,
+     * if the current configured network matches |networkId|.
+     *
+     * @param networkId network id of the network to be removed from supplicant.
+     */
+    public void removeNetworkIfCurrent(int networkId) {
+        mSupplicantStaIfaceHal.removeNetworkIfCurrent(networkId);
+    }
+
+    /********************************************************
+     * Vendor HAL operations
+     ********************************************************/
+    /**
+     * Callback to notify vendor HAL death.
+     */
+    public interface VendorHalDeathEventHandler {
+        /**
+         * Invoked when the vendor HAL dies.
+         */
+        void onDeath();
+    }
+
+    /**
+     * Initializes the vendor HAL. This is just used to initialize the {@link HalDeviceManager}.
+     */
+    public boolean initializeVendorHal(VendorHalDeathEventHandler handler) {
+        return mWifiVendorHal.initialize(handler);
+    }
+
+    /**
+     * Bring up the Vendor HAL and configure for STA mode or AP mode, if vendor HAL is supported.
+     *
+     * @param isStaMode true to start HAL in STA mode, false to start in AP mode.
+     * @return false if the HAL start fails, true if successful or if vendor HAL not supported.
+     */
+    private boolean startHalIfNecessary(boolean isStaMode) {
+        if (!mWifiVendorHal.isVendorHalSupported()) {
+            Log.i(mTAG, "Vendor HAL not supported, Ignore start...");
+            return true;
+        }
+        return mWifiVendorHal.startVendorHal(isStaMode);
+    }
+
+    /**
+     * Stops the HAL, if vendor HAL is supported.
+     */
+    private void stopHalIfNecessary() {
+        if (!mWifiVendorHal.isVendorHalSupported()) {
+            Log.i(mTAG, "Vendor HAL not supported, Ignore stop...");
+            return;
+        }
+        mWifiVendorHal.stopVendorHal();
+    }
+
+    /**
+     * Tests whether the HAL is running or not
+     */
     public boolean isHalStarted() {
-        return (sWifiHalHandle != 0);
-    }
-    private static native int getInterfacesNative();
-
-    public int queryInterfaceIndex(String interfaceName) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                int num = getInterfacesNative();
-                for (int i = 0; i < num; i++) {
-                    String name = getInterfaceNameNative(i);
-                    if (name.equals(interfaceName)) {
-                        return i;
-                    }
-                }
-            }
-        }
-        return -1;
-    }
-
-    private static native String getInterfaceNameNative(int index);
-    public String getInterfaceName(int index) {
-        synchronized (sLock) {
-            return getInterfaceNameNative(index);
-        }
+        return mWifiVendorHal.isHalStarted();
     }
 
     // TODO: Change variable names to camel style.
@@ -1644,29 +856,18 @@
         public int  max_ap_cache_per_scan;
         public int  max_rssi_sample_size;
         public int  max_scan_reporting_threshold;
-        public int  max_hotlist_bssids;
-        public int  max_significant_wifi_change_aps;
-        public int  max_bssid_history_entries;
-        public int  max_number_epno_networks;
-        public int  max_number_epno_networks_by_ssid;
-        public int  max_number_of_white_listed_ssid;
     }
 
-    public boolean getScanCapabilities(ScanCapabilities capabilities) {
-        synchronized (sLock) {
-            return isHalStarted() && getScanCapabilitiesNative(sWlan0Index, capabilities);
-        }
+    /**
+     * Gets the scan capabilities
+     *
+     * @param capabilities object to be filled in
+     * @return true for success. false for failure
+     */
+    public boolean getBgScanCapabilities(ScanCapabilities capabilities) {
+        return mWifiVendorHal.getBgScanCapabilities(capabilities);
     }
 
-    private static native boolean getScanCapabilitiesNative(
-            int iface, ScanCapabilities capabilities);
-
-    private static native boolean startScanNative(int iface, int id, ScanSettings settings);
-    private static native boolean stopScanNative(int iface, int id);
-    private static native WifiScanner.ScanData[] getScanResultsNative(int iface, boolean flush);
-    private static native WifiLinkLayerStats getWifiLinkLayerStatsNative(int iface);
-    private static native void setWifiLinkLayerStatsNative(int iface, int enable);
-
     public static class ChannelSettings {
         public int frequency;
         public int dwell_time_ms;
@@ -1684,14 +885,37 @@
         public ChannelSettings[] channels;
     }
 
+    /**
+     * Network parameters for hidden networks to be scanned for.
+     */
+    public static class HiddenNetwork {
+        public String ssid;
+
+        @Override
+        public boolean equals(Object otherObj) {
+            if (this == otherObj) {
+                return true;
+            } else if (otherObj == null || getClass() != otherObj.getClass()) {
+                return false;
+            }
+            HiddenNetwork other = (HiddenNetwork) otherObj;
+            return Objects.equals(ssid, other.ssid);
+        }
+
+        @Override
+        public int hashCode() {
+            return (ssid == null ? 0 : ssid.hashCode());
+        }
+    }
+
     public static class ScanSettings {
         public int base_period_ms;
         public int max_ap_per_scan;
         public int report_threshold_percent;
         public int report_threshold_num_scans;
         public int num_buckets;
-        /* Not part of gscan HAL API. Used only for wpa_supplicant scanning */
-        public int[] hiddenNetworkIds;
+        /* Not used for bg scans. Only works for single scans. */
+        public HiddenNetwork[] hiddenNetworks;
         public BucketSettings[] buckets;
     }
 
@@ -1700,8 +924,6 @@
      */
     public static class PnoNetwork {
         public String ssid;
-        public int networkId;
-        public int priority;
         public byte flags;
         public byte auth_bit_field;
 
@@ -1713,10 +935,16 @@
                 return false;
             }
             PnoNetwork other = (PnoNetwork) otherObj;
-            return ((Objects.equals(ssid, other.ssid)) && (networkId == other.networkId)
-                    && (priority == other.priority) && (flags == other.flags)
+            return ((Objects.equals(ssid, other.ssid)) && (flags == other.flags)
                     && (auth_bit_field == other.auth_bit_field));
         }
+
+        @Override
+        public int hashCode() {
+            int result = (ssid == null ? 0 : ssid.hashCode());
+            result ^= ((int) flags * 31) + ((int) auth_bit_field << 8);
+            return result;
+        }
     }
 
     /**
@@ -1731,21 +959,11 @@
         public int sameNetworkBonus;
         public int secureBonus;
         public int band5GHzBonus;
+        public int periodInMs;
         public boolean isConnected;
         public PnoNetwork[] networkList;
     }
 
-    /**
-     * Wi-Fi channel information.
-     */
-    public static class WifiChannelInfo {
-        int mPrimaryFrequency;
-        int mCenterFrequency0;
-        int mCenterFrequency1;
-        int mChannelWidth;
-        // TODO: add preamble once available in HAL.
-    }
-
     public static interface ScanEventHandler {
         /**
          * Called for each AP as it is found with the entire contents of the beacon/probe response.
@@ -1783,775 +1001,245 @@
         void onPnoScanFailed();
     }
 
-    /* scan status, keep these values in sync with gscan.h */
     public static final int WIFI_SCAN_RESULTS_AVAILABLE = 0;
     public static final int WIFI_SCAN_THRESHOLD_NUM_SCANS = 1;
     public static final int WIFI_SCAN_THRESHOLD_PERCENT = 2;
     public static final int WIFI_SCAN_FAILED = 3;
 
-    // Callback from native
-    private static void onScanStatus(int id, int event) {
-        ScanEventHandler handler = sScanEventHandler;
-        if (handler != null) {
-            handler.onScanStatus(event);
-        }
+    /**
+     * Starts a background scan.
+     * Any ongoing scan will be stopped first
+     *
+     * @param settings     to control the scan
+     * @param eventHandler to call with the results
+     * @return true for success
+     */
+    public boolean startBgScan(ScanSettings settings, ScanEventHandler eventHandler) {
+        return mWifiVendorHal.startBgScan(settings, eventHandler);
     }
 
-    public static  WifiSsid createWifiSsid(byte[] rawSsid) {
-        String ssidHexString = String.valueOf(HexEncoding.encode(rawSsid));
-
-        if (ssidHexString == null) {
-            return null;
-        }
-
-        WifiSsid wifiSsid = WifiSsid.createFromHex(ssidHexString);
-
-        return wifiSsid;
+    /**
+     * Stops any ongoing backgound scan
+     */
+    public void stopBgScan() {
+        mWifiVendorHal.stopBgScan();
     }
 
-    public static String ssidConvert(byte[] rawSsid) {
-        String ssid;
-
-        CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
-        try {
-            CharBuffer decoded = decoder.decode(ByteBuffer.wrap(rawSsid));
-            ssid = decoded.toString();
-        } catch (CharacterCodingException cce) {
-            ssid = null;
-        }
-
-        if (ssid == null) {
-            ssid = new String(rawSsid, StandardCharsets.ISO_8859_1);
-        }
-
-        return ssid;
+    /**
+     * Pauses an ongoing backgound scan
+     */
+    public void pauseBgScan() {
+        mWifiVendorHal.pauseBgScan();
     }
 
-    // Called from native
-    public static boolean setSsid(byte[] rawSsid, ScanResult result) {
-        if (rawSsid == null || rawSsid.length == 0 || result == null) {
-            return false;
-        }
-
-        result.SSID = ssidConvert(rawSsid);
-        result.wifiSsid = createWifiSsid(rawSsid);
-        return true;
+    /**
+     * Restarts a paused scan
+     */
+    public void restartBgScan() {
+        mWifiVendorHal.restartBgScan();
     }
 
-    private static void populateScanResult(ScanResult result, int beaconCap, String dbg) {
-        if (dbg == null) dbg = "";
-
-        InformationElementUtil.HtOperation htOperation = new InformationElementUtil.HtOperation();
-        InformationElementUtil.VhtOperation vhtOperation =
-                new InformationElementUtil.VhtOperation();
-        InformationElementUtil.ExtendedCapabilities extendedCaps =
-                new InformationElementUtil.ExtendedCapabilities();
-
-        ScanResult.InformationElement elements[] =
-                InformationElementUtil.parseInformationElements(result.bytes);
-        for (ScanResult.InformationElement ie : elements) {
-            if(ie.id == ScanResult.InformationElement.EID_HT_OPERATION) {
-                htOperation.from(ie);
-            } else if(ie.id == ScanResult.InformationElement.EID_VHT_OPERATION) {
-                vhtOperation.from(ie);
-            } else if (ie.id == ScanResult.InformationElement.EID_EXTENDED_CAPS) {
-                extendedCaps.from(ie);
-            }
-        }
-
-        if (extendedCaps.is80211McRTTResponder) {
-            result.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
-        } else {
-            result.clearFlag(ScanResult.FLAG_80211mc_RESPONDER);
-        }
-
-        //handle RTT related information
-        if (vhtOperation.isValid()) {
-            result.channelWidth = vhtOperation.getChannelWidth();
-            result.centerFreq0 = vhtOperation.getCenterFreq0();
-            result.centerFreq1 = vhtOperation.getCenterFreq1();
-        } else {
-            result.channelWidth = htOperation.getChannelWidth();
-            result.centerFreq0 = htOperation.getCenterFreq0(result.frequency);
-            result.centerFreq1  = 0;
-        }
-
-        // build capabilities string
-        BitSet beaconCapBits = new BitSet(16);
-        for (int i = 0; i < 16; i++) {
-            if ((beaconCap & (1 << i)) != 0) {
-                beaconCapBits.set(i);
-            }
-        }
-        result.capabilities = InformationElementUtil.Capabilities.buildCapabilities(elements,
-                                               beaconCapBits);
-
-        if(DBG) {
-            Log.d(TAG, dbg + "SSID: " + result.SSID + " ChannelWidth is: " + result.channelWidth
-                    + " PrimaryFreq: " + result.frequency + " mCenterfreq0: " + result.centerFreq0
-                    + " mCenterfreq1: " + result.centerFreq1 + (extendedCaps.is80211McRTTResponder
-                    ? "Support RTT reponder: " : "Do not support RTT responder")
-                    + " Capabilities: " + result.capabilities);
-        }
-
-        result.informationElements = elements;
-    }
-
-    // Callback from native
-    private static void onFullScanResult(int id, ScanResult result,
-            int bucketsScanned, int beaconCap) {
-        if (DBG) Log.i(TAG, "Got a full scan results event, ssid = " + result.SSID);
-
-        ScanEventHandler handler = sScanEventHandler;
-        if (handler != null) {
-            populateScanResult(result, beaconCap, " onFullScanResult ");
-            handler.onFullScanResult(result, bucketsScanned);
-        }
-    }
-
-    private static int sScanCmdId = 0;
-    private static ScanEventHandler sScanEventHandler;
-    private static ScanSettings sScanSettings;
-
-    public boolean startScan(ScanSettings settings, ScanEventHandler eventHandler) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sScanCmdId != 0) {
-                    stopScan();
-                } else if (sScanSettings != null || sScanEventHandler != null) {
-                /* current scan is paused; no need to stop it */
-                }
-
-                sScanCmdId = getNewCmdIdLocked();
-
-                sScanSettings = settings;
-                sScanEventHandler = eventHandler;
-
-                if (startScanNative(sWlan0Index, sScanCmdId, settings) == false) {
-                    sScanEventHandler = null;
-                    sScanSettings = null;
-                    sScanCmdId = 0;
-                    return false;
-                }
-
-                return true;
-            } else {
-                return false;
-            }
-        }
-    }
-
-    public void stopScan() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sScanCmdId != 0) {
-                    stopScanNative(sWlan0Index, sScanCmdId);
-                }
-                sScanSettings = null;
-                sScanEventHandler = null;
-                sScanCmdId = 0;
-            }
-        }
-    }
-
-    public void pauseScan() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sScanCmdId != 0 && sScanSettings != null && sScanEventHandler != null) {
-                    Log.d(TAG, "Pausing scan");
-                    WifiScanner.ScanData scanData[] = getScanResultsNative(sWlan0Index, true);
-                    stopScanNative(sWlan0Index, sScanCmdId);
-                    sScanCmdId = 0;
-                    sScanEventHandler.onScanPaused(scanData);
-                }
-            }
-        }
-    }
-
-    public void restartScan() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sScanCmdId == 0 && sScanSettings != null && sScanEventHandler != null) {
-                    Log.d(TAG, "Restarting scan");
-                    ScanEventHandler handler = sScanEventHandler;
-                    ScanSettings settings = sScanSettings;
-                    if (startScan(sScanSettings, sScanEventHandler)) {
-                        sScanEventHandler.onScanRestarted();
-                    } else {
-                    /* we are still paused; don't change state */
-                        sScanEventHandler = handler;
-                        sScanSettings = settings;
-                    }
-                }
-            }
-        }
-    }
-
-    public WifiScanner.ScanData[] getScanResults(boolean flush) {
-        synchronized (sLock) {
-            WifiScanner.ScanData[] sd = null;
-            if (isHalStarted()) {
-                sd = getScanResultsNative(sWlan0Index, flush);
-            }
-
-            if (sd != null) {
-                return sd;
-            } else {
-                return new WifiScanner.ScanData[0];
-            }
-        }
-    }
-
-    public static interface HotlistEventHandler {
-        void onHotlistApFound (ScanResult[] result);
-        void onHotlistApLost  (ScanResult[] result);
-    }
-
-    private static int sHotlistCmdId = 0;
-    private static HotlistEventHandler sHotlistEventHandler;
-
-    private native static boolean setHotlistNative(int iface, int id,
-            WifiScanner.HotlistSettings settings);
-    private native static boolean resetHotlistNative(int iface, int id);
-
-    public boolean setHotlist(WifiScanner.HotlistSettings settings,
-            HotlistEventHandler eventHandler) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sHotlistCmdId != 0) {
-                    return false;
-                } else {
-                    sHotlistCmdId = getNewCmdIdLocked();
-                }
-
-                sHotlistEventHandler = eventHandler;
-                if (setHotlistNative(sWlan0Index, sHotlistCmdId, settings) == false) {
-                    sHotlistEventHandler = null;
-                    return false;
-                }
-
-                return true;
-            } else {
-                return false;
-            }
-        }
-    }
-
-    public void resetHotlist() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sHotlistCmdId != 0) {
-                    resetHotlistNative(sWlan0Index, sHotlistCmdId);
-                    sHotlistCmdId = 0;
-                    sHotlistEventHandler = null;
-                }
-            }
-        }
-    }
-
-    // Callback from native
-    private static void onHotlistApFound(int id, ScanResult[] results) {
-        HotlistEventHandler handler = sHotlistEventHandler;
-        if (handler != null) {
-            handler.onHotlistApFound(results);
-        } else {
-            /* this can happen because of race conditions */
-            Log.d(TAG, "Ignoring hotlist AP found event");
-        }
-    }
-
-    // Callback from native
-    private static void onHotlistApLost(int id, ScanResult[] results) {
-        HotlistEventHandler handler = sHotlistEventHandler;
-        if (handler != null) {
-            handler.onHotlistApLost(results);
-        } else {
-            /* this can happen because of race conditions */
-            Log.d(TAG, "Ignoring hotlist AP lost event");
-        }
-    }
-
-    public static interface SignificantWifiChangeEventHandler {
-        void onChangesFound(ScanResult[] result);
-    }
-
-    private static SignificantWifiChangeEventHandler sSignificantWifiChangeHandler;
-    private static int sSignificantWifiChangeCmdId;
-
-    private static native boolean trackSignificantWifiChangeNative(
-            int iface, int id, WifiScanner.WifiChangeSettings settings);
-    private static native boolean untrackSignificantWifiChangeNative(int iface, int id);
-
-    public boolean trackSignificantWifiChange(
-            WifiScanner.WifiChangeSettings settings, SignificantWifiChangeEventHandler handler) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sSignificantWifiChangeCmdId != 0) {
-                    return false;
-                } else {
-                    sSignificantWifiChangeCmdId = getNewCmdIdLocked();
-                }
-
-                sSignificantWifiChangeHandler = handler;
-                if (trackSignificantWifiChangeNative(sWlan0Index, sSignificantWifiChangeCmdId,
-                        settings) == false) {
-                    sSignificantWifiChangeHandler = null;
-                    return false;
-                }
-
-                return true;
-            } else {
-                return false;
-            }
-
-        }
-    }
-
-    public void untrackSignificantWifiChange() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sSignificantWifiChangeCmdId != 0) {
-                    untrackSignificantWifiChangeNative(sWlan0Index, sSignificantWifiChangeCmdId);
-                    sSignificantWifiChangeCmdId = 0;
-                    sSignificantWifiChangeHandler = null;
-                }
-            }
-        }
-    }
-
-    // Callback from native
-    private static void onSignificantWifiChange(int id, ScanResult[] results) {
-        SignificantWifiChangeEventHandler handler = sSignificantWifiChangeHandler;
-        if (handler != null) {
-            handler.onChangesFound(results);
-        } else {
-            /* this can happen because of race conditions */
-            Log.d(TAG, "Ignoring significant wifi change");
-        }
+    /**
+     * Gets the latest scan results received.
+     */
+    public WifiScanner.ScanData[] getBgScanResults() {
+        return mWifiVendorHal.getBgScanResults();
     }
 
     public WifiLinkLayerStats getWifiLinkLayerStats(String iface) {
-        // TODO: use correct iface name to Index translation
-        if (iface == null) return null;
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getWifiLinkLayerStatsNative(sWlan0Index);
-            } else {
-                return null;
-            }
-        }
+        return mWifiVendorHal.getWifiLinkLayerStats();
     }
 
-    public void setWifiLinkLayerStats(String iface, int enable) {
-        if (iface == null) return;
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                setWifiLinkLayerStatsNative(sWlan0Index, enable);
-            }
-        }
-    }
-
-    public static native int getSupportedFeatureSetNative(int iface);
+    /**
+     * Get the supported features
+     *
+     * @return bitmask defined by WifiManager.WIFI_FEATURE_*
+     */
     public int getSupportedFeatureSet() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getSupportedFeatureSetNative(sWlan0Index);
-            } else {
-                Log.d(TAG, "Failing getSupportedFeatureset because HAL isn't started");
-                return 0;
-            }
-        }
+        return mWifiVendorHal.getSupportedFeatureSet();
     }
 
-    /* Rtt related commands/events */
     public static interface RttEventHandler {
         void onRttResults(RttManager.RttResult[] result);
     }
 
-    private static RttEventHandler sRttEventHandler;
-    private static int sRttCmdId;
-
-    // Callback from native
-    private static void onRttResults(int id, RttManager.RttResult[] results) {
-        RttEventHandler handler = sRttEventHandler;
-        if (handler != null && id == sRttCmdId) {
-            Log.d(TAG, "Received " + results.length + " rtt results");
-            handler.onRttResults(results);
-            sRttCmdId = 0;
-        } else {
-            Log.d(TAG, "RTT Received event for unknown cmd = " + id +
-                    ", current id = " + sRttCmdId);
-        }
-    }
-
-    private static native boolean requestRangeNative(
-            int iface, int id, RttManager.RttParams[] params);
-    private static native boolean cancelRangeRequestNative(
-            int iface, int id, RttManager.RttParams[] params);
-
+    /**
+     * Starts a new rtt request
+     *
+     * @param params RTT request params. Refer to {@link RttManager#RttParams}.
+     * @param handler Callback to be invoked to notify any results.
+     * @return true if the request was successful, false otherwise.
+     */
     public boolean requestRtt(
             RttManager.RttParams[] params, RttEventHandler handler) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sRttCmdId != 0) {
-                    Log.w(TAG, "Last one is still under measurement!");
-                    return false;
-                } else {
-                    sRttCmdId = getNewCmdIdLocked();
-                }
-                sRttEventHandler = handler;
-                return requestRangeNative(sWlan0Index, sRttCmdId, params);
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.requestRtt(params, handler);
     }
 
+    /**
+     * Cancels an outstanding rtt request
+     *
+     * @param params RTT request params. Refer to {@link RttManager#RttParams}
+     * @return true if there was an outstanding request and it was successfully cancelled
+     */
     public boolean cancelRtt(RttManager.RttParams[] params) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sRttCmdId == 0) {
-                    return false;
-                }
-
-                sRttCmdId = 0;
-
-                if (cancelRangeRequestNative(sWlan0Index, sRttCmdId, params)) {
-                    sRttEventHandler = null;
-                    return true;
-                } else {
-                    Log.e(TAG, "RTT cancel Request failed");
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.cancelRtt(params);
     }
 
-    private static int sRttResponderCmdId = 0;
-
-    private static native ResponderConfig enableRttResponderNative(int iface, int commandId,
-            int timeoutSeconds, WifiChannelInfo channelHint);
     /**
      * Enable RTT responder role on the device. Returns {@link ResponderConfig} if the responder
      * role is successfully enabled, {@code null} otherwise.
+     *
+     * @param timeoutSeconds timeout to use for the responder.
      */
     @Nullable
     public ResponderConfig enableRttResponder(int timeoutSeconds) {
-        synchronized (sLock) {
-            if (!isHalStarted()) return null;
-            if (sRttResponderCmdId != 0) {
-                if (DBG) Log.e(mTAG, "responder mode already enabled - this shouldn't happen");
-                return null;
-            }
-            int id = getNewCmdIdLocked();
-            ResponderConfig config = enableRttResponderNative(
-                    sWlan0Index, id, timeoutSeconds, null);
-            if (config != null) sRttResponderCmdId = id;
-            if (DBG) Log.d(TAG, "enabling rtt " + (config != null));
-            return config;
-        }
+        return mWifiVendorHal.enableRttResponder(timeoutSeconds);
     }
 
-    private static native boolean disableRttResponderNative(int iface, int commandId);
     /**
      * Disable RTT responder role. Returns {@code true} if responder role is successfully disabled,
      * {@code false} otherwise.
      */
     public boolean disableRttResponder() {
-        synchronized (sLock) {
-            if (!isHalStarted()) return false;
-            if (sRttResponderCmdId == 0) {
-                Log.e(mTAG, "responder role not enabled yet");
-                return true;
-            }
-            sRttResponderCmdId = 0;
-            return disableRttResponderNative(sWlan0Index, sRttResponderCmdId);
-        }
+        return mWifiVendorHal.disableRttResponder();
     }
 
-    private static native boolean setScanningMacOuiNative(int iface, byte[] oui);
-
+    /**
+     * Set the MAC OUI during scanning.
+     * An OUI {Organizationally Unique Identifier} is a 24-bit number that
+     * uniquely identifies a vendor or manufacturer.
+     *
+     * @param oui OUI to set.
+     * @return true for success
+     */
     public boolean setScanningMacOui(byte[] oui) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return setScanningMacOuiNative(sWlan0Index, oui);
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.setScanningMacOui(oui);
     }
 
-    private static native int[] getChannelsForBandNative(
-            int iface, int band);
-
+    /**
+     * Query the list of valid frequencies for the provided band.
+     * The result depends on the on the country code that has been set.
+     *
+     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+     * @return frequencies vector of valid frequencies (MHz), or null for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
     public int [] getChannelsForBand(int band) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getChannelsForBandNative(sWlan0Index, band);
-            } else {
-                return null;
-            }
-        }
+        return mWifiVendorHal.getChannelsForBand(band);
     }
 
-    private static native boolean isGetChannelsForBandSupportedNative();
-    public boolean isGetChannelsForBandSupported(){
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return isGetChannelsForBandSupportedNative();
-            } else {
-                return false;
-            }
-        }
+    /**
+     * Indicates whether getChannelsForBand is supported.
+     *
+     * @return true if it is.
+     */
+    public boolean isGetChannelsForBandSupported() {
+        return mWifiVendorHal.isGetChannelsForBandSupported();
     }
 
-    private static native boolean setDfsFlagNative(int iface, boolean dfsOn);
-    public boolean setDfsFlag(boolean dfsOn) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return setDfsFlagNative(sWlan0Index, dfsOn);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    private static native boolean setInterfaceUpNative(boolean up);
-    public boolean setInterfaceUp(boolean up) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return setInterfaceUpNative(up);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    private static native RttManager.RttCapabilities getRttCapabilitiesNative(int iface);
+    /**
+     * RTT (Round Trip Time) measurement capabilities of the device.
+     */
     public RttManager.RttCapabilities getRttCapabilities() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getRttCapabilitiesNative(sWlan0Index);
-            } else {
-                return null;
-            }
-        }
+        return mWifiVendorHal.getRttCapabilities();
     }
 
-    private static native ApfCapabilities getApfCapabilitiesNative(int iface);
+    /**
+     * Get the APF (Android Packet Filter) capabilities of the device
+     */
     public ApfCapabilities getApfCapabilities() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getApfCapabilitiesNative(sWlan0Index);
-            } else {
-                return null;
-            }
-        }
+        return mWifiVendorHal.getApfCapabilities();
     }
 
-    private static native boolean installPacketFilterNative(int iface, byte[] filter);
+    /**
+     * Installs an APF program on this iface, replacing any existing program.
+     *
+     * @param filter is the android packet filter program
+     * @return true for success
+     */
     public boolean installPacketFilter(byte[] filter) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return installPacketFilterNative(sWlan0Index, filter);
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.installPacketFilter(filter);
     }
 
-    private static native boolean setCountryCodeHalNative(int iface, String CountryCode);
-    public boolean setCountryCodeHal(String CountryCode) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return setCountryCodeHalNative(sWlan0Index, CountryCode);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    /* Rtt related commands/events */
-    public abstract class TdlsEventHandler {
-        abstract public void onTdlsStatus(String macAddr, int status, int reason);
-    }
-
-    private static TdlsEventHandler sTdlsEventHandler;
-
-    private static native boolean enableDisableTdlsNative(int iface, boolean enable,
-            String macAddr);
-    public boolean enableDisableTdls(boolean enable, String macAdd, TdlsEventHandler tdlsCallBack) {
-        synchronized (sLock) {
-            sTdlsEventHandler = tdlsCallBack;
-            return enableDisableTdlsNative(sWlan0Index, enable, macAdd);
-        }
-    }
-
-    // Once TDLS per mac and event feature is implemented, this class definition should be
-    // moved to the right place, like WifiManager etc
-    public static class TdlsStatus {
-        int channel;
-        int global_operating_class;
-        int state;
-        int reason;
-    }
-    private static native TdlsStatus getTdlsStatusNative(int iface, String macAddr);
-    public TdlsStatus getTdlsStatus(String macAdd) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getTdlsStatusNative(sWlan0Index, macAdd);
-            } else {
-                return null;
-            }
-        }
-    }
-
-    //ToFix: Once TDLS per mac and event feature is implemented, this class definition should be
-    // moved to the right place, like WifiStateMachine etc
-    public static class TdlsCapabilities {
-        /* Maximum TDLS session number can be supported by the Firmware and hardware */
-        int maxConcurrentTdlsSessionNumber;
-        boolean isGlobalTdlsSupported;
-        boolean isPerMacTdlsSupported;
-        boolean isOffChannelTdlsSupported;
-    }
-
-
-
-    private static native TdlsCapabilities getTdlsCapabilitiesNative(int iface);
-    public TdlsCapabilities getTdlsCapabilities () {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getTdlsCapabilitiesNative(sWlan0Index);
-            } else {
-                return null;
-            }
-        }
-    }
-
-    private static boolean onTdlsStatus(String macAddr, int status, int reason) {
-        TdlsEventHandler handler = sTdlsEventHandler;
-        if (handler == null) {
-            return false;
-        } else {
-            handler.onTdlsStatus(macAddr, status, reason);
-            return true;
-        }
+    /**
+     * Set country code for this AP iface.
+     *
+     * @param countryCode - two-letter country code (as ISO 3166)
+     * @return true for success
+     */
+    public boolean setCountryCodeHal(String countryCode) {
+        return mWifiVendorHal.setCountryCodeHal(countryCode);
     }
 
     //---------------------------------------------------------------------------------
-
     /* Wifi Logger commands/events */
-
     public static interface WifiLoggerEventHandler {
         void onRingBufferData(RingBufferStatus status, byte[] buffer);
         void onWifiAlert(int errorCode, byte[] buffer);
     }
 
-    private static WifiLoggerEventHandler sWifiLoggerEventHandler = null;
-
-    // Callback from native
-    private static void onRingBufferData(RingBufferStatus status, byte[] buffer) {
-        WifiLoggerEventHandler handler = sWifiLoggerEventHandler;
-        if (handler != null)
-            handler.onRingBufferData(status, buffer);
-    }
-
-    // Callback from native
-    private static void onWifiAlert(byte[] buffer, int errorCode) {
-        WifiLoggerEventHandler handler = sWifiLoggerEventHandler;
-        if (handler != null)
-            handler.onWifiAlert(errorCode, buffer);
-    }
-
-    private static int sLogCmdId = -1;
-    private static native boolean setLoggingEventHandlerNative(int iface, int id);
+    /**
+     * Registers the logger callback and enables alerts.
+     * Ring buffer data collection is only triggered when |startLoggingRingBuffer| is invoked.
+     *
+     * @param handler Callback to be invoked.
+     * @return true on success, false otherwise.
+     */
     public boolean setLoggingEventHandler(WifiLoggerEventHandler handler) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                int oldId =  sLogCmdId;
-                sLogCmdId = getNewCmdIdLocked();
-                if (!setLoggingEventHandlerNative(sWlan0Index, sLogCmdId)) {
-                    sLogCmdId = oldId;
-                    return false;
-                }
-                sWifiLoggerEventHandler = handler;
-                return true;
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.setLoggingEventHandler(handler);
     }
 
-    private static native boolean startLoggingRingBufferNative(int iface, int verboseLevel,
-            int flags, int minIntervalSec ,int minDataSize, String ringName);
+    /**
+     * Control debug data collection
+     *
+     * @param verboseLevel 0 to 3, inclusive. 0 stops logging.
+     * @param flags        Ignored.
+     * @param maxInterval  Maximum interval between reports; ignore if 0.
+     * @param minDataSize  Minimum data size in buffer for report; ignore if 0.
+     * @param ringName     Name of the ring for which data collection is to start.
+     * @return true for success, false otherwise.
+     */
     public boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxInterval,
             int minDataSize, String ringName){
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return startLoggingRingBufferNative(sWlan0Index, verboseLevel, flags, maxInterval,
-                        minDataSize, ringName);
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.startLoggingRingBuffer(
+                verboseLevel, flags, maxInterval, minDataSize, ringName);
     }
 
-    private static native int getSupportedLoggerFeatureSetNative(int iface);
+    /**
+     * Logger features exposed.
+     * This is a no-op now, will always return -1.
+     *
+     * @return true on success, false otherwise.
+     */
     public int getSupportedLoggerFeatureSet() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getSupportedLoggerFeatureSetNative(sWlan0Index);
-            } else {
-                return 0;
-            }
-        }
+        return mWifiVendorHal.getSupportedLoggerFeatureSet();
     }
 
-    private static native boolean resetLogHandlerNative(int iface, int id);
+    /**
+     * Stops all logging and resets the logger callback.
+     * This stops both the alerts and ring buffer data collection.
+     * @return true on success, false otherwise.
+     */
     public boolean resetLogHandler() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if (sLogCmdId == -1) {
-                    Log.e(TAG,"Can not reset handler Before set any handler");
-                    return false;
-                }
-                sWifiLoggerEventHandler = null;
-                if (resetLogHandlerNative(sWlan0Index, sLogCmdId)) {
-                    sLogCmdId = -1;
-                    return true;
-                } else {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.resetLogHandler();
     }
 
-    private static native String getDriverVersionNative(int iface);
+    /**
+     * Vendor-provided wifi driver version string
+     *
+     * @return String returned from the HAL.
+     */
     public String getDriverVersion() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getDriverVersionNative(sWlan0Index);
-            } else {
-                return "";
-            }
-        }
+        return mWifiVendorHal.getDriverVersion();
     }
 
-
-    private static native String getFirmwareVersionNative(int iface);
+    /**
+     * Vendor-provided wifi firmware version string
+     *
+     * @return String returned from the HAL.
+     */
     public String getFirmwareVersion() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getFirmwareVersionNative(sWlan0Index);
-            } else {
-                return "";
-            }
-        }
+        return mWifiVendorHal.getFirmwareVersion();
     }
 
     public static class RingBufferStatus{
@@ -2564,6 +1252,11 @@
         int readBytes;
         int writtenRecords;
 
+        // Bit masks for interpreting |flag|
+        public static final int HAS_BINARY_ENTRIES = (1 << 0);
+        public static final int HAS_ASCII_ENTRIES = (1 << 1);
+        public static final int HAS_PER_PACKET_ENTRIES = (1 << 2);
+
         @Override
         public String toString() {
             return "name: " + name + " flag: " + flag + " ringBufferId: " + ringBufferId +
@@ -2573,64 +1266,39 @@
         }
     }
 
-    private static native RingBufferStatus[] getRingBufferStatusNative(int iface);
+    /**
+     * API to get the status of all ring buffers supported by driver
+     */
     public RingBufferStatus[] getRingBufferStatus() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getRingBufferStatusNative(sWlan0Index);
-            } else {
-                return null;
-            }
-        }
+        return mWifiVendorHal.getRingBufferStatus();
     }
 
-    private static native boolean getRingBufferDataNative(int iface, String ringName);
+    /**
+     * Indicates to driver that all the data has to be uploaded urgently
+     *
+     * @param ringName Name of the ring buffer requested.
+     * @return true on success, false otherwise.
+     */
     public boolean getRingBufferData(String ringName) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getRingBufferDataNative(sWlan0Index, ringName);
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.getRingBufferData(ringName);
     }
 
-    private static byte[] mFwMemoryDump;
-    // Callback from native
-    private static void onWifiFwMemoryAvailable(byte[] buffer) {
-        mFwMemoryDump = buffer;
-        if (DBG) {
-            Log.d(TAG, "onWifiFwMemoryAvailable is called and buffer length is: " +
-                    (buffer == null ? 0 :  buffer.length));
-        }
-    }
-
-    private static native boolean getFwMemoryDumpNative(int iface);
+    /**
+     * Request vendor debug info from the firmware
+     *
+     * @return Raw data obtained from the HAL.
+     */
     public byte[] getFwMemoryDump() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                if(getFwMemoryDumpNative(sWlan0Index)) {
-                    byte[] fwMemoryDump = mFwMemoryDump;
-                    mFwMemoryDump = null;
-                    return fwMemoryDump;
-                } else {
-                    return null;
-                }
-            }
-            return null;
-        }
+        return mWifiVendorHal.getFwMemoryDump();
     }
 
-    private static native byte[] getDriverStateDumpNative(int iface);
-    /** Fetch the driver state, for driver debugging. */
+    /**
+     * Request vendor debug info from the driver
+     *
+     * @return Raw data obtained from the HAL.
+     */
     public byte[] getDriverStateDump() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getDriverStateDumpNative(sWlan0Index);
-            } else {
-                return null;
-            }
-        }
+        return mWifiVendorHal.getDriverStateDump();
     }
 
     //---------------------------------------------------------------------------------
@@ -2834,67 +1502,31 @@
         }
     }
 
-    private static native int startPktFateMonitoringNative(int iface);
     /**
      * Ask the HAL to enable packet fate monitoring. Fails unless HAL is started.
+     *
+     * @return true for success, false otherwise.
      */
     public boolean startPktFateMonitoring() {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return startPktFateMonitoringNative(sWlan0Index) == WIFI_SUCCESS;
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.startPktFateMonitoring();
     }
 
-    private static native int getTxPktFatesNative(int iface, TxFateReport[] reportBufs);
     /**
      * Fetch the most recent TX packet fates from the HAL. Fails unless HAL is started.
+     *
+     * @return true for success, false otherwise.
      */
     public boolean getTxPktFates(TxFateReport[] reportBufs) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                int res = getTxPktFatesNative(sWlan0Index, reportBufs);
-                if (res != WIFI_SUCCESS) {
-                    Log.e(TAG, "getTxPktFatesNative returned " + res);
-                    return false;
-                } else {
-                    return true;
-                }
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.getTxPktFates(reportBufs);
     }
 
-    private static native int getRxPktFatesNative(int iface, RxFateReport[] reportBufs);
     /**
      * Fetch the most recent RX packet fates from the HAL. Fails unless HAL is started.
      */
     public boolean getRxPktFates(RxFateReport[] reportBufs) {
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                int res = getRxPktFatesNative(sWlan0Index, reportBufs);
-                if (res != WIFI_SUCCESS) {
-                    Log.e(TAG, "getRxPktFatesNative returned " + res);
-                    return false;
-                } else {
-                    return true;
-                }
-            } else {
-                return false;
-            }
-        }
+        return mWifiVendorHal.getRxPktFates(reportBufs);
     }
 
-    //---------------------------------------------------------------------------------
-    /* Configure ePNO/PNO */
-    private static PnoEventHandler sPnoEventHandler;
-    private static int sPnoCmdId = 0;
-
-    private static native boolean setPnoListNative(int iface, int id, PnoSettings settings);
-
     /**
      * Set the PNO settings & the network list in HAL to start PNO.
      * @param settings PNO settings and network list.
@@ -2902,229 +1534,176 @@
      * @return true if success, false otherwise
      */
     public boolean setPnoList(PnoSettings settings, PnoEventHandler eventHandler) {
-        Log.e(TAG, "setPnoList cmd " + sPnoCmdId);
-
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                sPnoCmdId = getNewCmdIdLocked();
-                sPnoEventHandler = eventHandler;
-                if (setPnoListNative(sWlan0Index, sPnoCmdId, settings)) {
-                    return true;
-                }
-            }
-            sPnoEventHandler = null;
-            return false;
-        }
+        Log.e(mTAG, "setPnoList not supported");
+        return false;
     }
 
     /**
-     * Set the PNO network list in HAL to start PNO.
-     * @param list PNO network list.
-     * @param eventHandler Handler to receive notifications back during PNO scan.
-     * @return true if success, false otherwise
-     */
-    public boolean setPnoList(PnoNetwork[] list, PnoEventHandler eventHandler) {
-        PnoSettings settings = new PnoSettings();
-        settings.networkList = list;
-        return setPnoList(settings, eventHandler);
-    }
-
-    private static native boolean resetPnoListNative(int iface, int id);
-
-    /**
      * Reset the PNO settings in HAL to stop PNO.
      * @return true if success, false otherwise
      */
     public boolean resetPnoList() {
-        Log.e(TAG, "resetPnoList cmd " + sPnoCmdId);
-
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                sPnoCmdId = getNewCmdIdLocked();
-                sPnoEventHandler = null;
-                if (resetPnoListNative(sWlan0Index, sPnoCmdId)) {
-                    return true;
-                }
-            }
-            return false;
-        }
+        Log.e(mTAG, "resetPnoList not supported");
+        return false;
     }
 
-    // Callback from native
-    private static void onPnoNetworkFound(int id, ScanResult[] results, int[] beaconCaps) {
-        if (results == null) {
-            Log.e(TAG, "onPnoNetworkFound null results");
-            return;
-
-        }
-        Log.d(TAG, "WifiNative.onPnoNetworkFound result " + results.length);
-
-        PnoEventHandler handler = sPnoEventHandler;
-        if (sPnoCmdId != 0 && handler != null) {
-            for (int i=0; i<results.length; i++) {
-                Log.e(TAG, "onPnoNetworkFound SSID " + results[i].SSID
-                        + " " + results[i].level + " " + results[i].frequency);
-
-                populateScanResult(results[i], beaconCaps[i], "onPnoNetworkFound ");
-                results[i].wifiSsid = WifiSsid.createFromAsciiEncoded(results[i].SSID);
-            }
-
-            handler.onPnoNetworkFound(results);
-        } else {
-            /* this can happen because of race conditions */
-            Log.d(TAG, "Ignoring Pno Network found event");
-        }
-    }
-
-    private native static boolean setBssidBlacklistNative(int iface, int id,
-                                              String list[]);
-
-    public boolean setBssidBlacklist(String list[]) {
-        int size = 0;
-        if (list != null) {
-            size = list.length;
-        }
-        Log.e(TAG, "setBssidBlacklist cmd " + sPnoCmdId + " size " + size);
-
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                sPnoCmdId = getNewCmdIdLocked();
-                return setBssidBlacklistNative(sWlan0Index, sPnoCmdId, list);
-            } else {
-                return false;
-            }
-        }
-    }
-
-    private native static int startSendingOffloadedPacketNative(int iface, int idx,
-                                    byte[] srcMac, byte[] dstMac, byte[] pktData, int period);
-
-    public int
-    startSendingOffloadedPacket(int slot, KeepalivePacketData keepAlivePacket, int period) {
-        Log.d(TAG, "startSendingOffloadedPacket slot=" + slot + " period=" + period);
-
+    /**
+     * Start sending the specified keep alive packets periodically.
+     *
+     * @param slot Integer used to identify each request.
+     * @param keepAlivePacket Raw packet contents to send.
+     * @param period Period to use for sending these packets.
+     * @return 0 for success, -1 for error
+     */
+    public int startSendingOffloadedPacket(int slot, KeepalivePacketData keepAlivePacket,
+                                           int period) {
         String[] macAddrStr = getMacAddress().split(":");
         byte[] srcMac = new byte[6];
-        for(int i = 0; i < 6; i++) {
+        for (int i = 0; i < 6; i++) {
             Integer hexVal = Integer.parseInt(macAddrStr[i], 16);
             srcMac[i] = hexVal.byteValue();
         }
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return startSendingOffloadedPacketNative(sWlan0Index, slot, srcMac,
-                        keepAlivePacket.dstMac, keepAlivePacket.data, period);
-            } else {
-                return -1;
-            }
-        }
+        return mWifiVendorHal.startSendingOffloadedPacket(
+                slot, srcMac, keepAlivePacket, period);
     }
 
-    private native static int stopSendingOffloadedPacketNative(int iface, int idx);
-
-    public int
-    stopSendingOffloadedPacket(int slot) {
-        Log.d(TAG, "stopSendingOffloadedPacket " + slot);
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return stopSendingOffloadedPacketNative(sWlan0Index, slot);
-            } else {
-                return -1;
-            }
-        }
+    /**
+     * Stop sending the specified keep alive packets.
+     *
+     * @param slot id - same as startSendingOffloadedPacket call.
+     * @return 0 for success, -1 for error
+     */
+    public int stopSendingOffloadedPacket(int slot) {
+        return mWifiVendorHal.stopSendingOffloadedPacket(slot);
     }
 
     public static interface WifiRssiEventHandler {
         void onRssiThresholdBreached(byte curRssi);
     }
 
-    private static WifiRssiEventHandler sWifiRssiEventHandler;
-
-    // Callback from native
-    private static void onRssiThresholdBreached(int id, byte curRssi) {
-        WifiRssiEventHandler handler = sWifiRssiEventHandler;
-        if (handler != null) {
-            handler.onRssiThresholdBreached(curRssi);
-        }
-    }
-
-    private native static int startRssiMonitoringNative(int iface, int id,
-                                        byte maxRssi, byte minRssi);
-
-    private static int sRssiMonitorCmdId = 0;
-
+    /**
+     * Start RSSI monitoring on the currently connected access point.
+     *
+     * @param maxRssi          Maximum RSSI threshold.
+     * @param minRssi          Minimum RSSI threshold.
+     * @param rssiEventHandler Called when RSSI goes above maxRssi or below minRssi
+     * @return 0 for success, -1 for failure
+     */
     public int startRssiMonitoring(byte maxRssi, byte minRssi,
-                                                WifiRssiEventHandler rssiEventHandler) {
-        Log.d(TAG, "startRssiMonitoring: maxRssi=" + maxRssi + " minRssi=" + minRssi);
-        synchronized (sLock) {
-            sWifiRssiEventHandler = rssiEventHandler;
-            if (isHalStarted()) {
-                if (sRssiMonitorCmdId != 0) {
-                    stopRssiMonitoring();
-                }
-
-                sRssiMonitorCmdId = getNewCmdIdLocked();
-                Log.d(TAG, "sRssiMonitorCmdId = " + sRssiMonitorCmdId);
-                int ret = startRssiMonitoringNative(sWlan0Index, sRssiMonitorCmdId,
-                        maxRssi, minRssi);
-                if (ret != 0) { // if not success
-                    sRssiMonitorCmdId = 0;
-                }
-                return ret;
-            } else {
-                return -1;
-            }
-        }
+                                   WifiRssiEventHandler rssiEventHandler) {
+        return mWifiVendorHal.startRssiMonitoring(maxRssi, minRssi, rssiEventHandler);
     }
 
-    private native static int stopRssiMonitoringNative(int iface, int idx);
-
     public int stopRssiMonitoring() {
-        Log.d(TAG, "stopRssiMonitoring, cmdId " + sRssiMonitorCmdId);
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                int ret = 0;
-                if (sRssiMonitorCmdId != 0) {
-                    ret = stopRssiMonitoringNative(sWlan0Index, sRssiMonitorCmdId);
-                }
-                sRssiMonitorCmdId = 0;
-                return ret;
-            } else {
-                return -1;
-            }
-        }
+        return mWifiVendorHal.stopRssiMonitoring();
     }
 
-    private static native WifiWakeReasonAndCounts getWlanWakeReasonCountNative(int iface);
-
     /**
      * Fetch the host wakeup reasons stats from wlan driver.
+     *
      * @return the |WifiWakeReasonAndCounts| object retrieved from the wlan driver.
      */
     public WifiWakeReasonAndCounts getWlanWakeReasonCount() {
-        Log.d(TAG, "getWlanWakeReasonCount " + sWlan0Index);
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                return getWlanWakeReasonCountNative(sWlan0Index);
-            } else {
-                return null;
-            }
-        }
+        return mWifiVendorHal.getWlanWakeReasonCount();
     }
 
-    private static native int configureNeighborDiscoveryOffload(int iface, boolean enabled);
-
+    /**
+     * Enable/Disable Neighbour discovery offload functionality in the firmware.
+     *
+     * @param enabled true to enable, false to disable.
+     * @return true for success, false otherwise.
+     */
     public boolean configureNeighborDiscoveryOffload(boolean enabled) {
-        final String logMsg =  "configureNeighborDiscoveryOffload(" + enabled + ")";
-        Log.d(mTAG, logMsg);
-        synchronized (sLock) {
-            if (isHalStarted()) {
-                final int ret = configureNeighborDiscoveryOffload(sWlan0Index, enabled);
-                if (ret != 0) {
-                    Log.d(mTAG, logMsg + " returned: " + ret);
-                }
-                return (ret == 0);
+        return mWifiVendorHal.configureNeighborDiscoveryOffload(enabled);
+    }
+
+    // Firmware roaming control.
+
+    /**
+     * Class to retrieve firmware roaming capability parameters.
+     */
+    public static class RoamingCapabilities {
+        public int  maxBlacklistSize;
+        public int  maxWhitelistSize;
+    }
+
+    /**
+     * Query the firmware roaming capabilities.
+     * @return true for success, false otherwise.
+     */
+    public boolean getRoamingCapabilities(RoamingCapabilities capabilities) {
+        return mWifiVendorHal.getRoamingCapabilities(capabilities);
+    }
+
+    /**
+     * Macros for controlling firmware roaming.
+     */
+    public static final int DISABLE_FIRMWARE_ROAMING = 0;
+    public static final int ENABLE_FIRMWARE_ROAMING = 1;
+
+    /**
+     * Enable/disable firmware roaming.
+     *
+     * @return error code returned from HAL.
+     */
+    public int enableFirmwareRoaming(int state) {
+        return mWifiVendorHal.enableFirmwareRoaming(state);
+    }
+
+    /**
+     * Class for specifying the roaming configurations.
+     */
+    public static class RoamingConfig {
+        public ArrayList<String> blacklistBssids;
+        public ArrayList<String> whitelistSsids;
+    }
+
+    /**
+     * Set firmware roaming configurations.
+     */
+    public boolean configureRoaming(RoamingConfig config) {
+        Log.d(mTAG, "configureRoaming ");
+        return mWifiVendorHal.configureRoaming(config);
+    }
+
+    /**
+     * Reset firmware roaming configuration.
+     */
+    public boolean resetRoamingConfiguration() {
+        // Pass in an empty RoamingConfig object which translates to zero size
+        // blacklist and whitelist to reset the firmware roaming configuration.
+        return mWifiVendorHal.configureRoaming(new RoamingConfig());
+    }
+
+    /********************************************************
+     * JNI operations
+     ********************************************************/
+    /* Register native functions */
+    static {
+        /* Native functions are defined in libwifi-service.so */
+        System.loadLibrary("wifi-service");
+        registerNatives();
+    }
+
+    private static native int registerNatives();
+    /* kernel logging support */
+    private static native byte[] readKernelLogNative();
+
+    /**
+     * Fetches the latest kernel logs.
+     */
+    public synchronized String readKernelLog() {
+        byte[] bytes = readKernelLogNative();
+        if (bytes != null) {
+            CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+            try {
+                CharBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes));
+                return decoded.toString();
+            } catch (CharacterCodingException cce) {
+                return new String(bytes, StandardCharsets.ISO_8859_1);
             }
+        } else {
+            return "*** failed to read kernel log ***";
         }
-        return false;
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiNetworkHistory.java b/service/java/com/android/server/wifi/WifiNetworkHistory.java
index edbc516..f8457cd 100644
--- a/service/java/com/android/server/wifi/WifiNetworkHistory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkHistory.java
@@ -27,7 +27,6 @@
 import android.os.Process;
 import android.text.TextUtils;
 
-import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.server.net.DelayedDiskWrite;
@@ -110,17 +109,15 @@
 
     protected final DelayedDiskWrite mWriter;
     Context mContext;
-    private final LocalLog mLocalLog;
     /*
      * Lost config list, whenever we read a config from networkHistory.txt that was not in
      * wpa_supplicant.conf
      */
     HashSet<String> mLostConfigsDbg = new HashSet<String>();
 
-    public WifiNetworkHistory(Context c, LocalLog localLog, DelayedDiskWrite writer) {
+    public WifiNetworkHistory(Context c, DelayedDiskWrite writer) {
         mContext = c;
         mWriter = writer;
-        mLocalLog = localLog;
     }
 
     /**
@@ -323,9 +320,8 @@
      *         information read from wpa_supplicant.conf
      */
     public void readNetworkHistory(Map<String, WifiConfiguration> configs,
-            ConcurrentHashMap<Integer, ScanDetailCache> scanDetailCaches,
+            Map<Integer, ScanDetailCache> scanDetailCaches,
             Set<String> deletedEphemeralSSIDs) {
-        localLog("readNetworkHistory() path:" + NETWORK_HISTORY_CONFIG_FILE);
 
         try (DataInputStream in =
                      new DataInputStream(new BufferedInputStream(
@@ -360,7 +356,7 @@
                     // skip reading that configuration data
                     // since we don't have a corresponding network ID
                     if (config == null) {
-                        localLog("readNetworkHistory didnt find netid for hash="
+                        Log.e(TAG, "readNetworkHistory didnt find netid for hash="
                                 + Integer.toString(value.hashCode())
                                 + " key: " + value);
                         mLostConfigsDbg.add(value);
@@ -622,18 +618,15 @@
         }
     }
 
-    private void localLog(String s) {
-        if (mLocalLog != null) {
-            mLocalLog.log(s);
-        }
-    }
-
     private ScanDetailCache getScanDetailCache(WifiConfiguration config,
-            ConcurrentHashMap<Integer, ScanDetailCache> scanDetailCaches) {
+            Map<Integer, ScanDetailCache> scanDetailCaches) {
         if (config == null || scanDetailCaches == null) return null;
         ScanDetailCache cache = scanDetailCaches.get(config.networkId);
         if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            cache = new ScanDetailCache(config);
+            cache =
+                    new ScanDetailCache(
+                            config, WifiConfigManager.SCAN_CACHE_ENTRIES_MAX_SIZE,
+                            WifiConfigManager.SCAN_CACHE_ENTRIES_TRIM_SIZE);
             scanDetailCaches.put(config.networkId, cache);
         }
         return cache;
diff --git a/service/java/com/android/server/wifi/WifiNetworkScoreCache.java b/service/java/com/android/server/wifi/WifiNetworkScoreCache.java
deleted file mode 100644
index ed37935..0000000
--- a/service/java/com/android/server/wifi/WifiNetworkScoreCache.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2014 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.Manifest.permission;
-import android.content.Context;
-import android.net.INetworkScoreCache;
-import android.net.NetworkKey;
-import android.net.ScoredNetwork;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class WifiNetworkScoreCache extends INetworkScoreCache.Stub {
-    private static final String TAG = "WifiNetworkScoreCache";
-    private static final boolean DBG = false;
-
-    // A Network scorer returns a score in the range [-128, +127]
-    // We treat the lowest possible score as though there were no score, effectively allowing the
-    // scorer to provide an RSSI threshold below which a network should not be used.
-    public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE;
-    private final Context mContext;
-
-    // The key is of the form "<ssid>"<bssid>
-    // TODO: What about SSIDs that can't be encoded as UTF-8?
-    private final Map<String, ScoredNetwork> mNetworkCache;
-
-    public WifiNetworkScoreCache(Context context) {
-        mContext = context;
-        mNetworkCache = new HashMap<String, ScoredNetwork>();
-    }
-
-     @Override public final void updateScores(List<android.net.ScoredNetwork> networks) {
-        if (networks == null) {
-            return;
-        }
-        Log.e(TAG, "updateScores list size=" + networks.size());
-
-        synchronized(mNetworkCache) {
-            for (ScoredNetwork network : networks) {
-                String networkKey = buildNetworkKey(network);
-                if (networkKey == null) continue;
-                mNetworkCache.put(networkKey, network);
-            }
-        }
-     }
-
-     @Override public final void clearScores() {
-         synchronized (mNetworkCache) {
-             mNetworkCache.clear();
-         }
-     }
-
-    /**
-     * Returns whether there is any score info for the given ScanResult.
-     *
-     * This includes null-score info, so it should only be used when determining whether to request
-     * scores from the network scorer.
-     */
-    public boolean isScoredNetwork(ScanResult result) {
-        return getScoredNetwork(result) != null;
-    }
-
-    /**
-     * Returns whether there is a non-null score curve for the given ScanResult.
-     *
-     * A null score curve has special meaning - we should never connect to an ephemeral network if
-     * the score curve is null.
-     */
-    public boolean hasScoreCurve(ScanResult result) {
-        ScoredNetwork network = getScoredNetwork(result);
-        return network != null && network.rssiCurve != null;
-    }
-
-    public int getNetworkScore(ScanResult result) {
-
-        int score = INVALID_NETWORK_SCORE;
-
-        ScoredNetwork network = getScoredNetwork(result);
-        if (network != null && network.rssiCurve != null) {
-            score = network.rssiCurve.lookupScore(result.level);
-            if (DBG) {
-                Log.e(TAG, "getNetworkScore found scored network " + network.networkKey
-                        + " score " + Integer.toString(score)
-                        + " RSSI " + result.level);
-            }
-        }
-        return score;
-    }
-
-    /**
-     * Returns the ScoredNetwork metered hint for a given ScanResult.
-     *
-     * If there is no ScoredNetwork associated with the ScanResult then false will be returned.
-     */
-    public boolean getMeteredHint(ScanResult result) {
-        ScoredNetwork network = getScoredNetwork(result);
-        return network != null && network.meteredHint;
-    }
-
-    public int getNetworkScore(ScanResult result, boolean isActiveNetwork) {
-
-        int score = INVALID_NETWORK_SCORE;
-
-        ScoredNetwork network = getScoredNetwork(result);
-        if (network != null && network.rssiCurve != null) {
-            score = network.rssiCurve.lookupScore(result.level, isActiveNetwork);
-            if (DBG) {
-                Log.e(TAG, "getNetworkScore found scored network " + network.networkKey
-                        + " score " + Integer.toString(score)
-                        + " RSSI " + result.level
-                        + " isActiveNetwork " + isActiveNetwork);
-            }
-        }
-        return score;
-    }
-
-    private ScoredNetwork getScoredNetwork(ScanResult result) {
-        String key = buildNetworkKey(result);
-        if (key == null) return null;
-
-        //find it
-        synchronized(mNetworkCache) {
-            ScoredNetwork network = mNetworkCache.get(key);
-            return network;
-        }
-    }
-
-     private String buildNetworkKey(ScoredNetwork network) {
-        if (network == null || network.networkKey == null) return null;
-        if (network.networkKey.wifiKey == null) return null;
-        if (network.networkKey.type == NetworkKey.TYPE_WIFI) {
-            String key = network.networkKey.wifiKey.ssid;
-            if (key == null) return null;
-            if (network.networkKey.wifiKey.bssid != null) {
-                key = key + network.networkKey.wifiKey.bssid;
-            }
-            return key;
-        }
-        return null;
-    }
-
-    private String buildNetworkKey(ScanResult result) {
-        if (result == null || result.SSID == null) {
-            return null;
-        }
-        StringBuilder key = new StringBuilder("\"");
-        key.append(result.SSID);
-        key.append("\"");
-        if (result.BSSID != null) {
-            key.append(result.BSSID);
-        }
-        return key.toString();
-    }
-
-    @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
-        writer.println("WifiNetworkScoreCache");
-        writer.println("  All score curves:");
-        for (Map.Entry<String, ScoredNetwork> entry : mNetworkCache.entrySet()) {
-            ScoredNetwork scoredNetwork = entry.getValue();
-            writer.println("    " + entry.getKey() + ": " + scoredNetwork.rssiCurve
-                    + ", meteredHint=" + scoredNetwork.meteredHint);
-        }
-        writer.println("  Current network scores:");
-        WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-        for (ScanResult scanResult : wifiManager.getScanResults()) {
-            writer.println("    " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
-        }
-    }
-
-}
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
new file mode 100644
index 0000000..ce04bd2
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -0,0 +1,531 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * This class looks at all the connectivity scan results then
+ * selects a network for the phone to connect or roam to.
+ */
+public class WifiNetworkSelector {
+    private static final long INVALID_TIME_STAMP = Long.MIN_VALUE;
+    // Minimum time gap between last successful network selection and a new selection
+    // attempt.
+    @VisibleForTesting
+    public static final int MINIMUM_NETWORK_SELECTION_INTERVAL_MS = 10 * 1000;
+
+    private final WifiConfigManager mWifiConfigManager;
+    private final Clock mClock;
+    private final LocalLog mLocalLog;
+    private long mLastNetworkSelectionTimeStamp = INVALID_TIME_STAMP;
+    // Buffer of filtered scan results (Scan results considered by network selection) & associated
+    // WifiConfiguration (if any).
+    private volatile List<Pair<ScanDetail, WifiConfiguration>> mConnectableNetworks =
+            new ArrayList<>();
+    private final int mThresholdQualifiedRssi24;
+    private final int mThresholdQualifiedRssi5;
+    private final int mThresholdMinimumRssi24;
+    private final int mThresholdMinimumRssi5;
+    private final boolean mEnableAutoJoinWhenAssociated;
+
+    /**
+     * WiFi Network Selector supports various types of networks. Each type can
+     * have its evaluator to choose the best WiFi network for the device to connect
+     * to. When registering a WiFi network evaluator with the WiFi Network Selector,
+     * the priority of the network must be specified, and it must be a value between
+     * 0 and (EVALUATOR_MIN_PIRORITY - 1) with 0 being the highest priority. Wifi
+     * Network Selector iterates through the registered scorers from the highest priority
+     * to the lowest till a network is selected.
+     */
+    public static final int EVALUATOR_MIN_PRIORITY = 6;
+
+    /**
+     * Maximum number of evaluators can be registered with Wifi Network Selector.
+     */
+    public static final int MAX_NUM_EVALUATORS = EVALUATOR_MIN_PRIORITY;
+
+    /**
+     * Interface for WiFi Network Evaluator
+     *
+     * A network scorer evaulates all the networks from the scan results and
+     * recommends the best network in its category to connect or roam to.
+     */
+    public interface NetworkEvaluator {
+        /**
+         * Get the evaluator name.
+         */
+        String getName();
+
+        /**
+         * Update the evaluator.
+         *
+         * Certain evaluators have to be updated with the new scan results. For example
+         * the ExternalScoreEvalutor needs to refresh its Score Cache.
+         *
+         * @param scanDetails    a list of scan details constructed from the scan results
+         */
+        void update(List<ScanDetail> scanDetails);
+
+        /**
+         * Evaluate all the networks from the scan results.
+         *
+         * @param scanDetails    a list of scan details constructed from the scan results
+         * @param currentNetwork configuration of the current connected network
+         *                       or null if disconnected
+         * @param currentBssid   BSSID of the current connected network or null if
+         *                       disconnected
+         * @param connected      a flag to indicate if WifiStateMachine is in connected
+         *                       state
+         * @param untrustedNetworkAllowed a flag to indidate if untrusted networks like
+         *                                ephemeral networks are allowed
+         * @param connectableNetworks     a list of the ScanDetail and WifiConfiguration
+         *                                pair which is used by the WifiLastResortWatchdog
+         * @return configuration of the chosen network;
+         *         null if no network in this category is available.
+         */
+        @Nullable
+        WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
+                        WifiConfiguration currentNetwork, String currentBssid,
+                        boolean connected, boolean untrustedNetworkAllowed,
+                        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks);
+    }
+
+    private final NetworkEvaluator[] mEvaluators = new NetworkEvaluator[MAX_NUM_EVALUATORS];
+
+    // A helper to log debugging information in the local log buffer, which can
+    // be retrieved in bugreport.
+    private void localLog(String log) {
+        mLocalLog.log(log);
+    }
+
+    private boolean isCurrentNetworkSufficient(WifiInfo wifiInfo) {
+        WifiConfiguration network =
+                            mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
+
+        // Currently connected?
+        if (network == null) {
+            localLog("No current connected network.");
+            return false;
+        } else {
+            localLog("Current connected network: " + network.SSID
+                    + " , ID: " + network.networkId);
+        }
+
+        // Ephemeral network is not qualified.
+        if (network.ephemeral) {
+            localLog("Current network is an ephemeral one.");
+            return false;
+        }
+
+        // Open network is not qualified.
+        if (WifiConfigurationUtil.isConfigForOpenNetwork(network)) {
+            localLog("Current network is a open one.");
+            return false;
+        }
+
+        // 2.4GHz networks is not qualified.
+        if (wifiInfo.is24GHz()) {
+            localLog("Current network is 2.4GHz.");
+            return false;
+        }
+
+        // Is the current network's singnal strength qualified? It can only
+        // be a 5GHz network if we reach here.
+        int currentRssi = wifiInfo.getRssi();
+        if (wifiInfo.is5GHz() && currentRssi < mThresholdQualifiedRssi5) {
+            localLog("Current network band=" + (wifiInfo.is5GHz() ? "5GHz" : "2.4GHz")
+                    + ", RSSI[" + currentRssi + "]-acceptable but not qualified.");
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean isNetworkSelectionNeeded(List<ScanDetail> scanDetails, WifiInfo wifiInfo,
+                        boolean connected, boolean disconnected) {
+        if (scanDetails.size() == 0) {
+            localLog("Empty connectivity scan results. Skip network selection.");
+            return false;
+        }
+
+        if (connected) {
+            // Is roaming allowed?
+            if (!mEnableAutoJoinWhenAssociated) {
+                localLog("Switching networks in connected state is not allowed."
+                        + " Skip network selection.");
+                return false;
+            }
+
+            // Has it been at least the minimum interval since last network selection?
+            if (mLastNetworkSelectionTimeStamp != INVALID_TIME_STAMP) {
+                long gap = mClock.getElapsedSinceBootMillis()
+                            - mLastNetworkSelectionTimeStamp;
+                if (gap < MINIMUM_NETWORK_SELECTION_INTERVAL_MS) {
+                    localLog("Too short since last network selection: " + gap + " ms."
+                            + " Skip network selection.");
+                    return false;
+                }
+            }
+
+            if (isCurrentNetworkSufficient(wifiInfo)) {
+                localLog("Current connected network already sufficient. Skip network selection.");
+                return false;
+            } else {
+                localLog("Current connected network is not sufficient.");
+                return true;
+            }
+        } else if (disconnected) {
+            return true;
+        } else {
+            // No network selection if WifiStateMachine is in a state other than
+            // CONNECTED or DISCONNECTED.
+            localLog("WifiStateMachine is in neither CONNECTED nor DISCONNECTED state."
+                    + " Skip network selection.");
+            return false;
+        }
+    }
+
+    /**
+     * Format the given ScanResult as a scan ID for logging.
+     */
+    public static String toScanId(@Nullable ScanResult scanResult) {
+        return scanResult == null ? "NULL"
+                                  : String.format("%s:%s", scanResult.SSID, scanResult.BSSID);
+    }
+
+    /**
+     * Format the given WifiConfiguration as a SSID:netId string
+     */
+    public static String toNetworkString(WifiConfiguration network) {
+        if (network == null) {
+            return null;
+        }
+
+        return (network.SSID + ":" + network.networkId);
+    }
+
+    private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails,
+                HashSet<String> bssidBlacklist, boolean isConnected, String currentBssid) {
+        ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
+        List<ScanDetail> validScanDetails = new ArrayList<ScanDetail>();
+        StringBuffer noValidSsid = new StringBuffer();
+        StringBuffer blacklistedBssid = new StringBuffer();
+        StringBuffer lowRssi = new StringBuffer();
+        boolean scanResultsHaveCurrentBssid = false;
+
+        for (ScanDetail scanDetail : scanDetails) {
+            ScanResult scanResult = scanDetail.getScanResult();
+
+            if (TextUtils.isEmpty(scanResult.SSID)) {
+                noValidSsid.append(scanResult.BSSID).append(" / ");
+                continue;
+            }
+
+            // Check if the scan results contain the currently connected BSSID
+            if (scanResult.BSSID.equals(currentBssid)) {
+                scanResultsHaveCurrentBssid = true;
+            }
+
+            final String scanId = toScanId(scanResult);
+
+            if (bssidBlacklist.contains(scanResult.BSSID)) {
+                blacklistedBssid.append(scanId).append(" / ");
+                continue;
+            }
+
+            // Skip network with too weak signals.
+            if ((scanResult.is24GHz() && scanResult.level
+                    < mThresholdMinimumRssi24)
+                    || (scanResult.is5GHz() && scanResult.level
+                    < mThresholdMinimumRssi5)) {
+                lowRssi.append(scanId).append("(")
+                    .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz")
+                    .append(")").append(scanResult.level).append(" / ");
+                continue;
+            }
+
+            validScanDetails.add(scanDetail);
+        }
+
+        // WNS listens to all single scan results. Some scan requests may not include
+        // the channel of the currently connected network, so the currently connected
+        // network won't show up in the scan results. We don't act on these scan results
+        // to avoid aggressive network switching which might trigger disconnection.
+        if (isConnected && !scanResultsHaveCurrentBssid) {
+            localLog("Current connected BSSID " + currentBssid + " is not in the scan results."
+                    + " Skip network selection.");
+            validScanDetails.clear();
+            return validScanDetails;
+        }
+
+        if (noValidSsid.length() != 0) {
+            localLog("Networks filtered out due to invalid SSID: " + noValidSsid);
+        }
+
+        if (blacklistedBssid.length() != 0) {
+            localLog("Networks filtered out due to blacklist: " + blacklistedBssid);
+        }
+
+        if (lowRssi.length() != 0) {
+            localLog("Networks filtered out due to low signal strength: " + lowRssi);
+        }
+
+        return validScanDetails;
+    }
+
+    /**
+     * @return the list of ScanDetails scored as potential candidates by the last run of
+     * selectNetwork, this will be empty if Network selector determined no selection was
+     * needed on last run. This includes scan details of sufficient signal strength, and
+     * had an associated WifiConfiguration.
+     */
+    public List<Pair<ScanDetail, WifiConfiguration>> getFilteredScanDetails() {
+        return mConnectableNetworks;
+    }
+
+    /**
+     * This API is called when user explicitly selects a network. Currently, it is used in following
+     * cases:
+     * (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. This network is favored over all the other networks visible in latest network
+     *    selection procedure.
+     *
+     * @param netId  ID for the network chosen by the user
+     * @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 setUserConnectChoice(int netId) {
+        localLog("userSelectNetwork: network ID=" + netId);
+        WifiConfiguration selected = mWifiConfigManager.getConfiguredNetwork(netId);
+
+        if (selected == null || selected.SSID == null) {
+            localLog("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);
+        }
+
+        boolean change = false;
+        String key = selected.configKey();
+        // This is only used for setting the connect choice timestamp for debugging purposes.
+        long currentTime = mClock.getWallClockMillis();
+        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
+
+        for (WifiConfiguration network : savedNetworks) {
+            WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
+            if (network.networkId == selected.networkId) {
+                if (status.getConnectChoice() != null) {
+                    localLog("Remove user selection preference of " + status.getConnectChoice()
+                            + " Set Time: " + status.getConnectChoiceTimestamp() + " from "
+                            + network.SSID + " : " + network.networkId);
+                    mWifiConfigManager.clearNetworkConnectChoice(network.networkId);
+                    change = true;
+                }
+                continue;
+            }
+
+            if (status.getSeenInLastQualifiedNetworkSelection()
+                    && (status.getConnectChoice() == null
+                    || !status.getConnectChoice().equals(key))) {
+                localLog("Add key: " + key + " Set Time: " + currentTime + " to "
+                        + toNetworkString(network));
+                mWifiConfigManager.setNetworkConnectChoice(network.networkId, key, currentTime);
+                change = true;
+            }
+        }
+
+        return change;
+    }
+
+    /**
+     * Overrides the {@code candidate} chosen by the {@link #mEvaluators} with the user chosen
+     * {@link WifiConfiguration} if one exists.
+     *
+     * @return the user chosen {@link WifiConfiguration} if one exists, {@code candidate} otherwise
+     */
+    private WifiConfiguration overrideCandidateWithUserConnectChoice(
+            @NonNull WifiConfiguration candidate) {
+        WifiConfiguration tempConfig = candidate;
+        WifiConfiguration originalCandidate = candidate;
+        ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
+
+        while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
+            String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
+            tempConfig = mWifiConfigManager.getConfiguredNetwork(key);
+
+            if (tempConfig != null) {
+                WifiConfiguration.NetworkSelectionStatus tempStatus =
+                        tempConfig.getNetworkSelectionStatus();
+                if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) {
+                    scanResultCandidate = tempStatus.getCandidate();
+                    candidate = tempConfig;
+                }
+            } else {
+                localLog("Connect choice: " + key + " has no corresponding saved config.");
+                break;
+            }
+        }
+
+        if (candidate != originalCandidate) {
+            localLog("After user selection adjustment, the final candidate is:"
+                    + WifiNetworkSelector.toNetworkString(candidate) + " : "
+                    + scanResultCandidate.BSSID);
+        }
+        return candidate;
+    }
+
+    /**
+     * Select the best network from the ones in range.
+     *
+     * @param scanDetails    List of ScanDetail for all the APs in range
+     * @param bssidBlacklist Blacklisted BSSIDs
+     * @param wifiInfo       Currently connected network
+     * @param connected      True if the device is connected
+     * @param disconnected   True if the device is disconnected
+     * @param untrustedNetworkAllowed True if untrusted networks are allowed for connection
+     * @return Configuration of the selected network, or Null if nothing
+     */
+    @Nullable
+    public WifiConfiguration selectNetwork(List<ScanDetail> scanDetails,
+            HashSet<String> bssidBlacklist, WifiInfo wifiInfo,
+            boolean connected, boolean disconnected, boolean untrustedNetworkAllowed) {
+        mConnectableNetworks.clear();
+        if (scanDetails.size() == 0) {
+            localLog("Empty connectivity scan result");
+            return null;
+        }
+
+        WifiConfiguration currentNetwork =
+                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
+
+        // Always get the current BSSID from WifiInfo in case that firmware initiated
+        // roaming happened.
+        String currentBssid = wifiInfo.getBSSID();
+
+        // Shall we start network selection at all?
+        if (!isNetworkSelectionNeeded(scanDetails, wifiInfo, connected, disconnected)) {
+            return null;
+        }
+
+        // Update the registered network evaluators.
+        for (NetworkEvaluator registeredEvaluator : mEvaluators) {
+            if (registeredEvaluator != null) {
+                registeredEvaluator.update(scanDetails);
+            }
+        }
+
+        // Filter out unwanted networks.
+        List<ScanDetail> filteredScanDetails = filterScanResults(scanDetails, bssidBlacklist,
+                connected, currentBssid);
+        if (filteredScanDetails.size() == 0) {
+            return null;
+        }
+
+        // Go through the registered network evaluators from the highest priority
+        // one to the lowest till a network is selected.
+        WifiConfiguration selectedNetwork = null;
+        for (NetworkEvaluator registeredEvaluator : mEvaluators) {
+            if (registeredEvaluator != null) {
+                localLog("About to run " + registeredEvaluator.getName() + " :");
+                selectedNetwork = registeredEvaluator.evaluateNetworks(filteredScanDetails,
+                            currentNetwork, currentBssid, connected,
+                            untrustedNetworkAllowed, mConnectableNetworks);
+                if (selectedNetwork != null) {
+                    localLog(registeredEvaluator.getName() + " selects "
+                            + WifiNetworkSelector.toNetworkString(selectedNetwork) + " : "
+                            + selectedNetwork.getNetworkSelectionStatus().getCandidate().BSSID);
+                    break;
+                }
+            }
+        }
+
+        if (selectedNetwork != null) {
+            selectedNetwork = overrideCandidateWithUserConnectChoice(selectedNetwork);
+            mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
+        }
+
+        return selectedNetwork;
+    }
+
+    /**
+     * Register a network evaluator
+     *
+     * @param evaluator the network evaluator to be registered
+     * @param priority a value between 0 and (SCORER_MIN_PRIORITY-1)
+     *
+     * @return true if the evaluator is successfully registered with QNS;
+     *         false if failed to register the evaluator
+     */
+    public boolean registerNetworkEvaluator(NetworkEvaluator evaluator, int priority) {
+        if (priority < 0 || priority >= EVALUATOR_MIN_PRIORITY) {
+            localLog("Invalid network evaluator priority: " + priority);
+            return false;
+        }
+
+        if (mEvaluators[priority] != null) {
+            localLog("Priority " + priority + " is already registered by "
+                    + mEvaluators[priority].getName());
+            return false;
+        }
+
+        mEvaluators[priority] = evaluator;
+        return true;
+    }
+
+    WifiNetworkSelector(Context context, WifiConfigManager configManager, Clock clock,
+            LocalLog localLog) {
+        mWifiConfigManager = configManager;
+        mClock = clock;
+        mLocalLog = localLog;
+
+        mThresholdQualifiedRssi24 = context.getResources().getInteger(
+                            R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+        mThresholdQualifiedRssi5 = context.getResources().getInteger(
+                            R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+        mThresholdMinimumRssi24 = context.getResources().getInteger(
+                            R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mThresholdMinimumRssi5 = context.getResources().getInteger(
+                            R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
+                            R.bool.config_wifi_framework_enable_associated_network_selection);
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiNotificationController.java b/service/java/com/android/server/wifi/WifiNotificationController.java
index 6df2eb8..c8e5e90 100644
--- a/service/java/com/android/server/wifi/WifiNotificationController.java
+++ b/service/java/com/android/server/wifi/WifiNotificationController.java
@@ -20,7 +20,6 @@
 import android.app.NotificationManager;
 import android.app.TaskStackBuilder;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -28,18 +27,25 @@
 import android.net.NetworkInfo;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 
+import com.android.internal.notification.SystemNotificationChannels;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
 
-/* Takes care of handling the "open wi-fi network available" notification @hide */
-final class WifiNotificationController {
+/**
+ * Takes care of handling the "open wi-fi network available" notification
+ * @hide
+ */
+public class WifiNotificationController {
     /**
      * The icon to show in the 'available networks' notification. This will also
      * be the ID of the Notification given to the NotificationManager.
@@ -50,6 +56,7 @@
      * When a notification is shown, we wait this amount before possibly showing it again.
      */
     private final long NOTIFICATION_REPEAT_DELAY_MS;
+
     /**
      * Whether the user has set the setting to show the 'available networks' notification.
      */
@@ -58,6 +65,7 @@
      * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
      */
     private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+
     /**
      * The {@link System#currentTimeMillis()} must be at least this value for us
      * to show the notification again.
@@ -89,18 +97,22 @@
     private int mNumScansSinceNetworkStateChange;
 
     private final Context mContext;
-    private final WifiStateMachine mWifiStateMachine;
     private NetworkInfo mNetworkInfo;
     private NetworkInfo.DetailedState mDetailedState;
     private volatile int mWifiState;
     private FrameworkFacade mFrameworkFacade;
+    private WifiInjector mWifiInjector;
+    private WifiScanner mWifiScanner;
 
-    WifiNotificationController(Context context, Looper looper, WifiStateMachine wsm,
-            FrameworkFacade framework, Notification.Builder builder) {
+    WifiNotificationController(Context context,
+                               Looper looper,
+                               FrameworkFacade framework,
+                               Notification.Builder builder,
+                               WifiInjector wifiInjector) {
         mContext = context;
-        mWifiStateMachine = wsm;
         mFrameworkFacade = framework;
         mNotificationBuilder = builder;
+        mWifiInjector = wifiInjector;
         mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
         mDetailedState = NetworkInfo.DetailedState.IDLE;
 
@@ -113,7 +125,8 @@
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
-                        if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                        if (intent.getAction()
+                                .equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
                             mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                                     WifiManager.WIFI_STATE_UNKNOWN);
                             resetNotification();
@@ -133,19 +146,33 @@
                                     case CAPTIVE_PORTAL_CHECK:
                                         resetNotification();
                                         break;
+
+                                    case IDLE:
+                                    case SCANNING:
+                                    case CONNECTING:
+                                    case AUTHENTICATING:
+                                    case OBTAINING_IPADDR:
+                                    case SUSPENDED:
+                                    case FAILED:
+                                    case BLOCKED:
+                                    case VERIFYING_POOR_LINK:
+                                        break;
                                 }
                             }
                         } else if (intent.getAction().equals(
                                 WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                            if (mWifiScanner == null) {
+                                mWifiScanner = mWifiInjector.getWifiScanner();
+                            }
                             checkAndSetNotification(mNetworkInfo,
-                                    mWifiStateMachine.syncGetScanResultsList());
+                                    mWifiScanner.getSingleScanResults());
                         }
                     }
                 }, filter);
 
         // Setting is in seconds
         NOTIFICATION_REPEAT_DELAY_MS = mFrameworkFacade.getIntegerSetting(context,
-                Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000L;
         mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(
                 new Handler(looper));
         mNotificationEnabledSettingObserver.register();
@@ -159,6 +186,10 @@
         // don't bother doing any of the following
         if (!mNotificationEnabled) return;
         if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
+        if (UserManager.get(mContext)
+                .hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT)) {
+            return;
+        }
 
         NetworkInfo.State state = NetworkInfo.State.DISCONNECTED;
         if (networkInfo != null)
@@ -245,7 +276,8 @@
 
             if (mNotificationBuilder == null) {
                 // Cache the Notification builder object.
-                mNotificationBuilder = new Notification.Builder(mContext)
+                mNotificationBuilder = new Notification.Builder(mContext,
+                        SystemNotificationChannels.NETWORK_AVAILABLE)
                         .setWhen(0)
                         .setSmallIcon(ICON_NETWORKS_AVAILABLE)
                         .setAutoCancel(true)
@@ -289,8 +321,7 @@
         }
 
         public void register() {
-            ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(Settings.Global.getUriFor(
+            mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
                     Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
             synchronized (WifiNotificationController.this) {
                 mNotificationEnabled = getValue();
diff --git a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
deleted file mode 100644
index 1e75603..0000000
--- a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
+++ /dev/null
@@ -1,1313 +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 android.annotation.Nullable;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.NetworkKey;
-import android.net.NetworkScoreManager;
-import android.net.WifiKey;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.PersistableBundle;
-import android.telephony.CarrierConfigManager;
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wifi.util.ScanDetailUtil;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.nio.charset.StandardCharsets;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class looks at all the connectivity scan results then
- * select an network for the phone to connect/roam to.
- */
-public class WifiQualifiedNetworkSelector {
-    private WifiConfigManager mWifiConfigManager;
-    private WifiInfo mWifiInfo;
-    private NetworkScoreManager mScoreManager;
-    private WifiNetworkScoreCache mNetworkScoreCache;
-    private Clock mClock;
-    private static final String TAG = "WifiQualifiedNetworkSelector:";
-    // Always enable debugging logs for now since QNS is still a new feature.
-    private static final boolean FORCE_DEBUG = true;
-    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)
-    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;
-
-    private static final int CARRIER_SSID = 0;
-    private static final int CARRIER_KEY = 1;
-    private static final int CARRIER_EAP_METHOD = 2;
-
-    //if current network is on 2.4GHz band and has a RSSI over this, need not new network selection
-    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
-    public static final int QUALIFIED_RSSI_5G_BAND = -70;
-    //any RSSI larger than this will benefit the traffic very limited
-    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
-    public static final int MINIMUM_2G_ACCEPT_RSSI = -85;
-    public static final int MINIMUM_5G_ACCEPT_RSSI = -82;
-
-    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;
-    public static final int BSSID_BLACKLIST_THRESHOLD = 3;
-    public static final int BSSID_BLACKLIST_EXPIRE_TIME = 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;
-
-    private final LocalLog mLocalLog = new LocalLog(512);
-    private int mRssiScoreSlope = RSSI_SCORE_SLOPE;
-    private int mRssiScoreOffset = RSSI_SCORE_OFFSET;
-    private int mSameBssidAward = SAME_BSSID_AWARD;
-    private int mLastSelectionAward = LAST_SELECTION_AWARD;
-    private int mPasspointSecurityAward = PASSPOINT_SECURITY_AWARD;
-    private int mSecurityAward = SECURITY_AWARD;
-    private int mUserPreferedBand = WifiManager.WIFI_FREQUENCY_BAND_AUTO;
-    private Map<String, BssidBlacklistStatus> mBssidBlacklist =
-            new HashMap<String, BssidBlacklistStatus>();
-    private List<WifiConfiguration> mCarrierConfiguredNetworks = new ArrayList<WifiConfiguration>();
-    private Context mContext;
-
-    /**
-     * class save the blacklist status of a given BSSID
-     */
-    private static class BssidBlacklistStatus {
-        //how many times it is requested to be blacklisted (association rejection trigger this)
-        int mCounter;
-        boolean mIsBlacklisted;
-        long mBlacklistedTimeStamp = INVALID_TIME_STAMP;
-    }
-
-    private void localLog(String log) {
-        if (mDbg) {
-            mLocalLog.log(log);
-        }
-    }
-
-    @VisibleForTesting
-    public void setCarrierConfiguredNetworks(List<WifiConfiguration> carrierConfiguredNetworks) {
-        mCarrierConfiguredNetworks = carrierConfiguredNetworks;
-    }
-
-    private void localLoge(String log) {
-        mLocalLog.log(log);
-    }
-
-    @VisibleForTesting
-    void setWifiNetworkScoreCache(WifiNetworkScoreCache cache) {
-        mNetworkScoreCache = cache;
-    }
-
-    /**
-     * @return current target connected network
-     */
-    public WifiConfiguration getConnetionTargetNetwork() {
-        return mCurrentConnectedNetwork;
-    }
-
-    /**
-     * @return the list of ScanDetails scored as potential candidates by the last run of
-     * selectQualifiedNetwork, this will be empty if QNS determined no selection was needed on last
-     * run. This includes scan details of sufficient signal strength, and had an associated
-     * WifiConfiguration.
-     */
-    public List<Pair<ScanDetail, WifiConfiguration>> getFilteredScanDetails() {
-        return mFilteredScanDetails;
-    }
-
-    /**
-     * set the user selected preferred band
-     *
-     * @param band preferred band user selected
-     */
-    public void setUserPreferredBand(int band) {
-        mUserPreferedBand = band;
-    }
-
-    WifiQualifiedNetworkSelector(WifiConfigManager configureStore, Context context,
-            WifiInfo wifiInfo, Clock clock) {
-        mWifiConfigManager = configureStore;
-        mWifiInfo = wifiInfo;
-        mClock = clock;
-        mContext = context;
-        mScoreManager =
-                (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
-        if (mScoreManager != null) {
-            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);
-            mNetworkScoreCache = null;
-        }
-
-        mRssiScoreSlope = context.getResources().getInteger(
-                R.integer.config_wifi_framework_RSSI_SCORE_SLOPE);
-        mRssiScoreOffset = context.getResources().getInteger(
-                R.integer.config_wifi_framework_RSSI_SCORE_OFFSET);
-        mSameBssidAward = context.getResources().getInteger(
-                R.integer.config_wifi_framework_SAME_BSSID_AWARD);
-        mLastSelectionAward = context.getResources().getInteger(
-                R.integer.config_wifi_framework_LAST_SELECTION_AWARD);
-        mPasspointSecurityAward = context.getResources().getInteger(
-                R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD);
-        mSecurityAward = context.getResources().getInteger(
-                R.integer.config_wifi_framework_SECURITY_AWARD);
-        mNoIntnetPenalty = (mWifiConfigManager.mThresholdSaturatedRssi24.get() + mRssiScoreOffset)
-                * mRssiScoreSlope + mWifiConfigManager.mBandAward5Ghz.get()
-                + mWifiConfigManager.mCurrentNetworkBoost.get() + mSameBssidAward + mSecurityAward;
-
-        context.registerReceiver(mBroadcastReceiver, new IntentFilter(
-                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-    }
-
-    @VisibleForTesting
-    public List<WifiConfiguration> parseCarrierSuppliedWifiInfo(String[] wifiArray) {
-        List<WifiConfiguration> carrierConfiguredNetworks = new ArrayList<WifiConfiguration>();
-        for (String config : wifiArray) {
-            String[] wc = config.split("\\|");
-            if (wc.length != 3) {
-                continue;
-            }
-            WifiConfiguration wifiConfig = new WifiConfiguration();
-            try {
-                byte[] decodedBytes = Base64.decode(wc[CARRIER_SSID], Base64.DEFAULT);
-                String ssid = new String(decodedBytes);
-                wifiConfig.SSID = "\"" + ssid + "\"";
-            } catch (IllegalArgumentException ex) {
-                localLog("mBroadcaseReceiver: Could not decode base64 string");
-                continue;
-            }
-            try {
-                int key = Integer.parseInt(wc[CARRIER_KEY]);
-                wifiConfig.allowedKeyManagement.set(key);
-                int eapType = Integer.parseInt(wc[CARRIER_EAP_METHOD]);
-                wifiConfig.enterpriseConfig = new WifiEnterpriseConfig();
-                wifiConfig.enterpriseConfig.setEapMethod(eapType);
-            } catch (NumberFormatException e) {
-                localLog("mBroadcastReceiver: not an integer:" + wc[CARRIER_KEY] + "," +
-                        wc[CARRIER_EAP_METHOD]);
-                continue;
-            } catch (IllegalArgumentException e) {
-                localLog("mBroadcastReceiver: invalid config" + wc[CARRIER_KEY] + "," +
-                        wc[CARRIER_EAP_METHOD]);
-            }
-            carrierConfiguredNetworks.add(wifiConfig);
-            localLog("mBroadcastReceiver: carrier config:" + wifiConfig.SSID);
-        }
-        return carrierConfiguredNetworks;
-    }
-
-    final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            localLog("mBroadcastReceiver: onReceive " + intent.getAction());
-            String[] wifiArray = null;
-            CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
-                    mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            if (carrierConfigManager != null) {
-                PersistableBundle b = carrierConfigManager.getConfig();
-                if (b != null) {
-                    wifiArray = b.getStringArray(
-                            CarrierConfigManager.KEY_CARRIER_WIFI_STRING_ARRAY);
-                }
-            }
-
-            if (wifiArray == null) {
-                return;
-            }
-            mCarrierConfiguredNetworks = parseCarrierSuppliedWifiInfo(wifiArray);
-            boolean hasCarrierNetworks = (mCarrierConfiguredNetworks == null ||
-                    mCarrierConfiguredNetworks.size() == 0) ? false : true;
-            mWifiConfigManager.setHasCarrierNetworks(hasCarrierNetworks);
-        }
-    };
-
-    void enableVerboseLogging(int verbose) {
-        mDbg = verbose > 0 || FORCE_DEBUG;
-    }
-
-    private String getNetworkString(WifiConfiguration network) {
-        if (network == null) {
-            return null;
-        }
-
-        return (network.SSID + ":" + network.networkId);
-
-    }
-
-    /**
-     * check whether current network is good enough we need not consider any potential switch
-     *
-     * @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) {
-
-        if (currentNetwork == null) {
-            localLog("Disconnected");
-            return false;
-        } else {
-            localLog("Current network is: " + currentNetwork.SSID + " ,ID is: "
-                    + currentNetwork.networkId);
-        }
-
-        //if current connected network is an ephemeral network,we will consider
-        // there is no current network
-        if (currentNetwork.ephemeral) {
-            localLog("Current is ephemeral. Start reselect");
-            return false;
-        }
-
-        //if current network is open network, not qualified
-        if (mWifiConfigManager.isOpenNetwork(currentNetwork)) {
-            localLog("Current network is open network");
-            return false;
-        }
-
-        // Current network band must match with user preference selection
-        if (mWifiInfo.is24GHz() && (mUserPreferedBand != WifiManager.WIFI_FREQUENCY_BAND_2GHZ)) {
-            localLog("Current band does not match user preference. Start Qualified Network"
-                    + " Selection Current band = " + (mWifiInfo.is24GHz() ? "2.4GHz band"
-                    : "5GHz band") + "UserPreference band = " + mUserPreferedBand);
-            return false;
-        }
-
-        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);
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * check whether QualifiedNetworkSelection is needed or not
-     *
-     * @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 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
-        if (isLinkDebouncing) {
-            localLog("Need not Qualified Network Selection 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
-            if (!mWifiConfigManager.getEnableAutoJoinWhenAssociated()) {
-                localLog("Switch network under connection is not allowed");
-                return false;
-            }
-
-            //Do not select again if last selection is within
-            //MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL
-            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:"
-                            + gap + " ms!");
-                    return false;
-                }
-            }
-
-            WifiConfiguration currentNetwork =
-                    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
-                return false;
-            }
-
-            if (!isNetworkQualified(mCurrentConnectedNetwork)) {
-                //need not trigger Qualified Network Selection if current network is qualified
-                localLog("Current network is not qualified");
-                return true;
-            } else {
-                return false;
-            }
-        } else if (isDisconnected) {
-            mCurrentConnectedNetwork = null;
-            mCurrentBssid = null;
-            //Do not start Qualified Network Selection if current state is a transient state
-            if (isSupplicantTransientState) {
-                return false;
-            }
-        } else {
-            //Do not allow new network selection in other state
-            localLog("WifiStateMachine is not on connected or disconnected state");
-            return false;
-        }
-
-        return true;
-    }
-
-    int calculateBssidScore(ScanResult scanResult, WifiConfiguration network,
-            WifiConfiguration currentNetwork, boolean sameBssid, boolean sameSelect,
-            StringBuffer sbuf) {
-
-        int score = 0;
-        //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);
-        if (scanResult.is5GHz()) {
-            //5GHz band
-            score += mWifiConfigManager.mBandAward5Ghz.get();
-            sbuf.append(" 5GHz bonus: " + mWifiConfigManager.mBandAward5Ghz.get());
-        }
-
-        //last user selection award
-        if (sameSelect) {
-            long timeDifference = mClock.elapsedRealtime()
-                    - 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);
-            }
-        }
-
-        //same network award
-        if (network == currentNetwork || network.isLinked(currentNetwork)) {
-            score += mWifiConfigManager.mCurrentNetworkBoost.get();
-            sbuf.append(" Same network with current associated. Bonus: "
-                    + mWifiConfigManager.mCurrentNetworkBoost.get());
-        }
-
-        //same BSSID award
-        if (sameBssid) {
-            score += mSameBssidAward;
-            sbuf.append(" Same BSSID with current association. Bonus: " + mSameBssidAward);
-        }
-
-        //security award
-        if (network.isPasspoint()) {
-            score += mPasspointSecurityAward;
-            sbuf.append(" Passpoint Bonus:" + mPasspointSecurityAward);
-        } else if (!mWifiConfigManager.isOpenNetwork(network)) {
-            score += mSecurityAward;
-            sbuf.append(" Secure network Bonus:" + 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
-        if (network.numNoInternetAccessReports > 0 && !network.validatedInternetAccess) {
-            score -= mNoIntnetPenalty;
-            sbuf.append(" No internet Penalty:-" + mNoIntnetPenalty);
-        }
-
-
-        sbuf.append(" Score for scanResult: " + scanResult +  " and Network ID: "
-                + network.networkId + " final score:" + score + "\n\n");
-
-        return score;
-    }
-
-    /**
-     * This API try to update all the saved networks' network selection status
-     */
-    private void updateSavedNetworkSelectionStatus() {
-        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
-        if (savedNetworks.size() == 0) {
-            localLog("no saved network");
-            return;
-        }
-
-        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 (status.isNetworkTemporaryDisabled()) {
-                mWifiConfigManager.tryEnableQualifiedNetwork(network.networkId);
-            }
-
-            //clean 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("Connect Choice:" + status.getConnectChoice() + " set time:"
-                    + status.getConnectChoiceTimestamp());
-            sbuf.append("\n");
-        }
-        localLog(sbuf.toString());
-    }
-
-    /**
-     * This API is called when user explicitly select 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
-     * 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
-     *
-     * @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
-     */
-    public boolean userSelectNetwork(int netId, boolean persist) {
-        WifiConfiguration selected = mWifiConfigManager.getWifiConfiguration(netId);
-        localLog("userSelectNetwork:" + netId + " persist:" + persist);
-        if (selected == null || selected.SSID == null) {
-            localLoge("userSelectNetwork: Bad configuration with nid=" + netId);
-            return false;
-        }
-
-
-        if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
-            mWifiConfigManager.updateNetworkSelectionStatus(netId,
-                    WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-        }
-
-        if (!persist) {
-            localLog("User has no privilege to overwrite the current priority");
-            return false;
-        }
-
-        boolean change = false;
-        String key = selected.configKey();
-        // This is only used for setting the connect choice timestamp for debugging purposes.
-        long currentTime = mClock.currentTimeMillis();
-        List<WifiConfiguration> savedNetworks = mWifiConfigManager.getSavedNetworks();
-
-        for (WifiConfiguration network : savedNetworks) {
-            WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId);
-            WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
-            if (config.networkId == selected.networkId) {
-                if (status.getConnectChoice() != null) {
-                    localLog("Remove user selection preference of " + status.getConnectChoice()
-                            + " Set Time: " + status.getConnectChoiceTimestamp() + " from "
-                            + config.SSID + " : " + config.networkId);
-                    status.setConnectChoice(null);
-                    status.setConnectChoiceTimestamp(WifiConfiguration.NetworkSelectionStatus
-                            .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
-                    change = true;
-                }
-                continue;
-            }
-
-            if (status.getSeenInLastQualifiedNetworkSelection()
-                    && (status.getConnectChoice() == null
-                    || !status.getConnectChoice().equals(key))) {
-                localLog("Add key:" + key + " Set Time: " + currentTime + " to "
-                        + getNetworkString(config));
-                status.setConnectChoice(key);
-                status.setConnectChoiceTimestamp(currentTime);
-                change = true;
-            }
-        }
-        //Write this change to file
-        if (change) {
-            mWifiConfigManager.writeKnownNetworkHistory();
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * 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.
-     *
-     * @param bssid the bssid to be enabled / disabled
-     * @param enable -- true enable a bssid if it has been disabled
-     *               -- false disable a bssid
-     */
-    public boolean enableBssidForQualityNetworkSelection(String bssid, boolean enable) {
-        if (enable) {
-            return (mBssidBlacklist.remove(bssid) != null);
-        } else {
-            if (bssid != null) {
-                BssidBlacklistStatus status = mBssidBlacklist.get(bssid);
-                if (status == null) {
-                    //first time
-                    BssidBlacklistStatus newStatus = new BssidBlacklistStatus();
-                    newStatus.mCounter++;
-                    mBssidBlacklist.put(bssid, newStatus);
-                } else if (!status.mIsBlacklisted) {
-                    status.mCounter++;
-                    if (status.mCounter >= BSSID_BLACKLIST_THRESHOLD) {
-                        status.mIsBlacklisted = true;
-                        status.mBlacklistedTimeStamp = mClock.elapsedRealtime();
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * 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.
-     */
-    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) {
-                    iter.remove();
-                }
-            }
-        }
-    }
-
-    /**
-     * 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);
-        return status == null ? false : status.mIsBlacklisted;
-    }
-
-    private boolean isCarrierNetwork(ScanResult scanResult) {
-        return (getMatchingConfigForEAPNetworks(scanResult,
-                mCarrierConfiguredNetworks) != null ? true : false);
-    }
-
-    /**
-     * 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
-     *
-     * @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
-     */
-    public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork,
-            boolean isUntrustedConnectionsAllowed, List<ScanDetail>  scanDetails,
-            boolean isLinkDebouncing, boolean isConnected, boolean isDisconnected,
-            boolean isSupplicantTransient) {
-        localLog("==========start qualified Network Selection==========");
-        mScanDetails = scanDetails;
-        List<Pair<ScanDetail, WifiConfiguration>>  filteredScanDetails = new ArrayList<>();
-        if (mCurrentConnectedNetwork == null) {
-            mCurrentConnectedNetwork =
-                    mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId());
-        }
-
-        // Always get the current BSSID from WifiInfo in case that firmware initiated roaming
-        // happened.
-        mCurrentBssid = mWifiInfo.getBSSID();
-
-        if (!forceSelectNetwork && !needQualifiedNetworkSelection(isLinkDebouncing, isConnected,
-                isDisconnected, isSupplicantTransient)) {
-            localLog("Quit qualified Network Selection since it is not forced and current network"
-                    + " is qualified already");
-            mFilteredScanDetails = filteredScanDetails;
-            return null;
-        }
-
-        int currentHighestScore = Integer.MIN_VALUE;
-        ScanResult scanResultCandidate = null;
-        WifiConfiguration networkCandidate = null;
-        WifiConfiguration carrierCandidate = null;
-        final ExternalScoreEvaluator externalScoreEvaluator =
-                new ExternalScoreEvaluator(mLocalLog, mDbg);
-        final CarrierScoreEvaluator carrierScoreEvaluator =
-                new CarrierScoreEvaluator(mLocalLog, mDbg);
-        String lastUserSelectedNetWorkKey = mWifiConfigManager.getLastSelectedConfiguration();
-        WifiConfiguration lastUserSelectedNetwork =
-                mWifiConfigManager.getWifiConfiguration(lastUserSelectedNetWorkKey);
-        if (lastUserSelectedNetwork != null) {
-            localLog("Last selection is " + lastUserSelectedNetwork.SSID + " Time to now: "
-                    + ((mClock.elapsedRealtime() - mWifiConfigManager.getLastSelectedTimeStamp())
-                            / 1000 / 60 + " minutes"));
-        }
-
-        updateSavedNetworkSelectionStatus();
-        updateBssidBlacklist();
-
-        StringBuffer lowSignalScan = new StringBuffer();
-        StringBuffer notSavedScan = new StringBuffer();
-        StringBuffer noValidSsid = new StringBuffer();
-        StringBuffer scoreHistory =  new StringBuffer();
-        ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
-        boolean scanResultsHaveCurrentBssid = false;
-
-        localLog("isCarrierNetworkEnabledByUser: " +
-                mWifiConfigManager.getIsCarrierNetworkEnabledByUser());
-
-        //iterate all scan results and find the best candidate with the highest score
-        for (ScanDetail scanDetail : mScanDetails) {
-            ScanResult scanResult = scanDetail.getScanResult();
-            //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 + " / ");
-                }
-                continue;
-            }
-
-            //check if the scan results contain the current connected
-            //BSSID.
-            if (scanResult.BSSID.equals(mCurrentBssid)) {
-                scanResultsHaveCurrentBssid = true;
-            }
-
-            final String scanId = toScanId(scanResult);
-            //check whether this BSSID is blocked or not
-            if (mWifiConfigManager.isBssidBlacklisted(scanResult.BSSID)
-                    || isBssidDisabled(scanResult.BSSID)) {
-                //We should not see this in ePNO
-                Log.e(TAG, scanId + " is in blacklist.");
-                continue;
-            }
-
-            //skip scan result 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 + " / ");
-                }
-                continue;
-            }
-
-            //check if there is already a score for this network
-            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
-                    unscoredNetworks.add(ntwkKey);
-                } catch (IllegalArgumentException e) {
-                    Log.w(TAG, "Invalid SSID=" + scanResult.SSID + " BSSID=" + scanResult.BSSID
-                            + " for network score. Skip.");
-                }
-            }
-
-            //check whether this scan result belong to a saved network
-            boolean potentiallyEphemeral = false;
-            // Stores WifiConfiguration of potential connection candidates for scan result filtering
-            WifiConfiguration potentialEphemeralCandidate = null;
-            List<WifiConfiguration> associatedWifiConfigurations =
-                    mWifiConfigManager.updateSavedNetworkWithNewScanDetail(scanDetail,
-                            isSupplicantTransient || isConnected || isLinkDebouncing);
-            if (associatedWifiConfigurations == null) {
-                potentiallyEphemeral =  true;
-                if (mDbg) {
-                    notSavedScan.append(scanId + " / ");
-                }
-            } else if (associatedWifiConfigurations.size() == 1) {
-                //if there are more than 1 associated network, it must be a passpoint network
-                WifiConfiguration network = associatedWifiConfigurations.get(0);
-                if (network.ephemeral) {
-                    potentialEphemeralCandidate = network;
-                    potentiallyEphemeral =  true;
-                }
-            }
-
-            // Evaluate the potentially ephemeral network as a possible candidate if untrusted
-            // connections are allowed and we have an external score for the scan result.
-            if (potentiallyEphemeral) {
-                if (!mWifiConfigManager.wasEphemeralNetworkDeleted(
-                        ScanDetailUtil.createQuotedSSID(scanResult.SSID))) {
-                    if (isUntrustedConnectionsAllowed) {
-                        Integer netScore = getNetworkScore(scanResult, false);
-                        if (netScore != null) {
-                            externalScoreEvaluator.evalUntrustedCandidate(netScore, scanResult);
-                            // scanDetail is for available ephemeral network
-                            filteredScanDetails.add(Pair.create(scanDetail,
-                                    potentialEphemeralCandidate));
-                        }
-                        // Evaluate the carrier network as a possible candidate.
-                        // todo need to add flag isCarrierConnectionsAllowed, config in settings.
-                    } else if (!mCarrierConfiguredNetworks.isEmpty() &&
-                            isCarrierNetwork(scanResult) &&
-                            mWifiConfigManager.getIsCarrierNetworkEnabledByUser()) {
-                        localLog("Checking the carrierScoreEvaluator for candidates...");
-                        carrierScoreEvaluator.evalCarrierCandidate(scanResult,
-                                getCarrierScore(scanResult, mCurrentConnectedNetwork,
-                                        (mCurrentBssid == null ? false :
-                                                mCurrentBssid.equals(scanResult.BSSID))));
-                        filteredScanDetails.add(Pair.create(scanDetail,
-                                potentialEphemeralCandidate));
-                    }
-                }
-                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.
-            int highestScore = Integer.MIN_VALUE;
-            int score;
-            WifiConfiguration configurationCandidateForThisScan = null;
-            WifiConfiguration potentialCandidate = null;
-            for (WifiConfiguration network : associatedWifiConfigurations) {
-                WifiConfiguration.NetworkSelectionStatus status =
-                        network.getNetworkSelectionStatus();
-                status.setSeenInLastQualifiedNetworkSelection(true);
-                if (potentialCandidate == null) {
-                    potentialCandidate = network;
-                }
-                if (!status.isNetworkEnabled()) {
-                    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:"
-                            + network.BSSID + ". Skip " + scanResult.BSSID);
-                    continue;
-                }
-
-                // If the network is marked to use external scores then attempt to fetch the score.
-                // These networks will not be considered alongside the other saved networks.
-                if (network.useExternalScores) {
-                    Integer netScore = getNetworkScore(scanResult, false);
-                    externalScoreEvaluator.evalSavedCandidate(netScore, network, scanResult);
-                    continue;
-                }
-
-                score = calculateBssidScore(scanResult, network, mCurrentConnectedNetwork,
-                        (mCurrentBssid == null ? false : mCurrentBssid.equals(scanResult.BSSID)),
-                        (lastUserSelectedNetwork == null ? false : lastUserSelectedNetwork.networkId
-                         == network.networkId), scoreHistory);
-                if (score > highestScore) {
-                    highestScore = score;
-                    configurationCandidateForThisScan = network;
-                    potentialCandidate = network;
-                }
-                //update the cached candidate
-                if (score > status.getCandidateScore() || (score == status.getCandidateScore()
-                      && status.getCandidate() != null
-                      && scanResult.level > status.getCandidate().level)) {
-                    status.setCandidate(scanResult);
-                    status.setCandidateScore(score);
-                }
-            }
-            // Create potential filteredScanDetail entry
-            filteredScanDetails.add(Pair.create(scanDetail, potentialCandidate));
-
-            if (highestScore > currentHighestScore || (highestScore == currentHighestScore
-                    && scanResultCandidate != null
-                    && scanResult.level > scanResultCandidate.level)) {
-                currentHighestScore = highestScore;
-                scanResultCandidate = scanResult;
-                networkCandidate = configurationCandidateForThisScan;
-                networkCandidate.getNetworkSelectionStatus().setCandidate(scanResultCandidate);
-            }
-        }
-
-        mFilteredScanDetails = filteredScanDetails;
-
-        //kick the score manager if there is any unscored network
-        if (mScoreManager != null && unscoredNetworks.size() != 0) {
-            NetworkKey[] unscoredNetworkKeys =
-                    unscoredNetworks.toArray(new NetworkKey[unscoredNetworks.size()]);
-            mScoreManager.requestScores(unscoredNetworkKeys);
-        }
-
-        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");
-            localLog(scoreHistory.toString());
-        }
-
-        //QNS listens to all single scan results. Some scan requests may not include
-        //the channel of the currently connected network, so the currently connected network
-        //won't show up in the scan results. We don't act on these scan results to avoid
-        //aggressive network switching which might trigger disconnection.
-        if (isConnected && !scanResultsHaveCurrentBssid) {
-            localLog("Current connected BSSID " + mCurrentBssid + " is not in the scan results."
-                    + " Skip network selection.");
-            return null;
-        }
-
-        //we need traverse the whole user preference to choose the one user like most now
-        if (scanResultCandidate != null) {
-            WifiConfiguration tempConfig = networkCandidate;
-
-            while (tempConfig.getNetworkSelectionStatus().getConnectChoice() != null) {
-                String key = tempConfig.getNetworkSelectionStatus().getConnectChoice();
-                tempConfig = mWifiConfigManager.getWifiConfiguration(key);
-
-                if (tempConfig != null) {
-                    WifiConfiguration.NetworkSelectionStatus tempStatus =
-                            tempConfig.getNetworkSelectionStatus();
-                    if (tempStatus.getCandidate() != null && tempStatus.isNetworkEnabled()) {
-                        scanResultCandidate = tempStatus.getCandidate();
-                        networkCandidate = tempConfig;
-                    }
-                } else {
-                    //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:"
-                    + getNetworkString(networkCandidate) + " : " + scanResultCandidate.BSSID);
-        }
-
-        // At this point none of the saved networks were good candidates so we fall back to
-        // externally scored networks if any are available.
-        if (scanResultCandidate == null) {
-            localLog("Checking the externalScoreEvaluator for candidates...");
-            networkCandidate = getExternalScoreCandidate(externalScoreEvaluator);
-            if (networkCandidate != null) {
-                scanResultCandidate = networkCandidate.getNetworkSelectionStatus().getCandidate();
-            }
-        }
-
-        if (scanResultCandidate == null) {
-            networkCandidate = getCarrierScoreCandidate(carrierScoreEvaluator);
-            localLog("Carrier candidate::" + networkCandidate);
-            if (networkCandidate != null) {
-                scanResultCandidate =
-                        mWifiConfigManager.getScanResultCandidate(networkCandidate);
-            }
-        }
-
-        if (scanResultCandidate == null) {
-            localLog("Can not find any suitable candidates");
-            return null;
-        }
-
-        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.
-        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();
-        return networkCandidate;
-    }
-
-    /**
-     * Returns the best candidate network according to the given ExternalScoreEvaluator.
-     */
-    @Nullable
-    WifiConfiguration getExternalScoreCandidate(ExternalScoreEvaluator scoreEvaluator) {
-        WifiConfiguration networkCandidate = null;
-        switch (scoreEvaluator.getBestCandidateType()) {
-            case ExternalScoreEvaluator.BestCandidateType.UNTRUSTED_NETWORK:
-                ScanResult untrustedScanResultCandidate =
-                        scoreEvaluator.getScanResultCandidate();
-                WifiConfiguration unTrustedNetworkCandidate =
-                        mWifiConfigManager.wifiConfigurationFromScanResult(
-                                untrustedScanResultCandidate);
-
-                // Mark this config as ephemeral so it isn't persisted.
-                unTrustedNetworkCandidate.ephemeral = true;
-                if (mNetworkScoreCache != null) {
-                    unTrustedNetworkCandidate.meteredHint =
-                            mNetworkScoreCache.getMeteredHint(untrustedScanResultCandidate);
-                }
-                mWifiConfigManager.saveNetwork(unTrustedNetworkCandidate,
-                        WifiConfiguration.UNKNOWN_UID);
-
-                localLog(String.format("new ephemeral candidate %s network ID:%d, "
-                                + "meteredHint=%b",
-                        toScanId(untrustedScanResultCandidate), unTrustedNetworkCandidate.networkId,
-                        unTrustedNetworkCandidate.meteredHint));
-
-                unTrustedNetworkCandidate.getNetworkSelectionStatus()
-                        .setCandidate(untrustedScanResultCandidate);
-                networkCandidate = unTrustedNetworkCandidate;
-                break;
-
-            case ExternalScoreEvaluator.BestCandidateType.SAVED_NETWORK:
-                ScanResult scanResultCandidate = scoreEvaluator.getScanResultCandidate();
-                networkCandidate = scoreEvaluator.getSavedConfig();
-                networkCandidate.getNetworkSelectionStatus().setCandidate(scanResultCandidate);
-                localLog(String.format("new scored candidate %s network ID:%d",
-                        toScanId(scanResultCandidate), networkCandidate.networkId));
-                break;
-
-            case ExternalScoreEvaluator.BestCandidateType.NONE:
-                localLog("ExternalScoreEvaluator did not see any good candidates.");
-                break;
-
-            default:
-                localLoge("Unhandled ExternalScoreEvaluator case. No candidate selected.");
-                break;
-        }
-        return networkCandidate;
-    }
-
-    /**
-     * Returns the best candidate network according to the given CarrierScoreEvaluator.
-     */
-    @Nullable
-    WifiConfiguration getCarrierScoreCandidate(CarrierScoreEvaluator scoreEvaluator) {
-
-        ScanResult untrustedCarrierScanResult = scoreEvaluator.getScanResultCandidate();
-        if (untrustedCarrierScanResult == null) {
-            return null;
-        }
-
-        WifiConfiguration untrustedCandidateConfig = getMatchingConfigForEAPNetworks(
-                untrustedCarrierScanResult, mCarrierConfiguredNetworks);
-
-        if (untrustedCandidateConfig == null) {
-            return null;
-        }
-
-        WifiConfiguration newUntrustedCandidateConfig =
-                new WifiConfiguration(untrustedCandidateConfig);
-
-        // Mark this config as ephemeral so it isn't persisted.
-        newUntrustedCandidateConfig.ephemeral = true;
-        // Mark this config as a Carrier Network.
-        newUntrustedCandidateConfig.isCarrierNetwork = true;
-
-        mWifiConfigManager.saveNetworkAndSetCandidate(
-                newUntrustedCandidateConfig, untrustedCarrierScanResult);
-        return newUntrustedCandidateConfig;
-    }
-
-    /**
-     * Returns the available external network score or NULL if no score is available.
-     *
-     * @param scanResult The scan result of the network to score.
-     * @param isActiveNetwork Whether or not the network is currently connected.
-     * @return A valid external score if one is available or NULL.
-     */
-    @Nullable
-    Integer getNetworkScore(ScanResult scanResult, boolean isActiveNetwork) {
-        if (mNetworkScoreCache != null && mNetworkScoreCache.isScoredNetwork(scanResult)) {
-            int networkScore = mNetworkScoreCache.getNetworkScore(scanResult, isActiveNetwork);
-            localLog(toScanId(scanResult) + " has score: " + networkScore);
-            return networkScore;
-        }
-        return null;
-    }
-
-    /**
-     * Returns the available external network score or NULL if no score is available.
-     *
-     * @param scanResult The scan result of the network to score.
-     * @return A valid external score if one is available or NULL.
-     */
-    int getCarrierScore(ScanResult scanResult, WifiConfiguration currentNetwork,
-                        boolean sameBssid) {
-        localLog("Calc Carrier score: w/" + sameBssid);
-        if (currentNetwork != null) {
-            localLog("scoring: compare::" + scanResult.SSID + ", with:" + currentNetwork.SSID);
-        }
-        int score = 0;
-        // Calculate the RSSI score.
-        int rssi = scanResult.level <= mWifiConfigManager.mThresholdSaturatedRssi24.get()
-                ? scanResult.level : mWifiConfigManager.mThresholdSaturatedRssi24.get();
-        score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
-
-        // 5GHz band bonus.
-        if (scanResult.is5GHz()) {
-            score += BAND_AWARD_5GHz;
-        }
-
-        //same network award
-        if ((currentNetwork != null) && currentNetwork.SSID.equals(scanResult.SSID)) {
-            score += mWifiConfigManager.mCurrentNetworkBoost.get();
-        }
-
-        //same BSSID award
-        if (sameBssid) {
-            score += mSameBssidAward;
-        }
-
-        localLog("Calc Carrier score:" + score);
-        return score;
-    }
-
-    /**
-     * Formats the given ScanResult as a scan ID for logging.
-     */
-    private static String toScanId(@Nullable ScanResult scanResult) {
-        return scanResult == null ? "NULL"
-                                  : String.format("%s:%s", scanResult.SSID, scanResult.BSSID);
-    }
-
-    //Dump the logs
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("Dump of WifiQualifiedNetworkSelector");
-        pw.println("WifiQualifiedNetworkSelector - Log Begin ----");
-        mLocalLog.dump(fd, pw, args);
-        pw.println("WifiQualifiedNetworkSelector - Log End ----");
-    }
-
-    /**
-     * Used to track and evaluate networks that are assigned external scores.
-     */
-    static class ExternalScoreEvaluator {
-        @Retention(RetentionPolicy.SOURCE)
-        @interface BestCandidateType {
-            int NONE = 0;
-            int SAVED_NETWORK = 1;
-            int UNTRUSTED_NETWORK = 2;
-        }
-        // Always set to the best known candidate.
-        private @BestCandidateType int mBestCandidateType = BestCandidateType.NONE;
-        private int mHighScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
-        private WifiConfiguration mSavedConfig;
-        private ScanResult mScanResultCandidate;
-        private final LocalLog mLocalLog;
-        private final boolean mDbg;
-
-        ExternalScoreEvaluator(LocalLog localLog, boolean dbg) {
-            mLocalLog = localLog;
-            mDbg = dbg;
-        }
-
-        // Determines whether or not the given scan result is the best one its seen so far.
-        void evalUntrustedCandidate(@Nullable Integer score, ScanResult scanResult) {
-            if (score != null && score > mHighScore) {
-                mHighScore = score;
-                mScanResultCandidate = scanResult;
-                mBestCandidateType = BestCandidateType.UNTRUSTED_NETWORK;
-                localLog(toScanId(scanResult) + " become the new untrusted candidate");
-            }
-        }
-
-        // Determines whether or not the given saved network is the best one its seen so far.
-        void evalSavedCandidate(@Nullable Integer score, WifiConfiguration config,
-                ScanResult scanResult) {
-            // Always take the highest score. If there's a tie and an untrusted network is currently
-            // the best then pick the saved network.
-            if (score != null
-                    && (score > mHighScore
-                        || (mBestCandidateType == BestCandidateType.UNTRUSTED_NETWORK
-                            && score == mHighScore))) {
-                mHighScore = score;
-                mSavedConfig = config;
-                mScanResultCandidate = scanResult;
-                mBestCandidateType = BestCandidateType.SAVED_NETWORK;
-                localLog(toScanId(scanResult) + " become the new externally scored saved network "
-                        + "candidate");
-            }
-        }
-
-        int getBestCandidateType() {
-            return mBestCandidateType;
-        }
-
-        int getHighScore() {
-            return mHighScore;
-        }
-
-        public ScanResult getScanResultCandidate() {
-            return mScanResultCandidate;
-        }
-
-        WifiConfiguration getSavedConfig() {
-            return mSavedConfig;
-        }
-
-        private void localLog(String log) {
-            if (mDbg) {
-                mLocalLog.log(log);
-            }
-        }
-    }
-
-    /**
-     * Used to track and evaluate networks that are assigned by the Carriers.
-     */
-    static class CarrierScoreEvaluator {
-        // Always set to the best known candidate
-        private int mHighScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
-        private ScanResult mScanResultCandidate;
-        private final LocalLog mLocalLog;
-        private final boolean mDbg;
-
-        CarrierScoreEvaluator(LocalLog localLog, boolean dbg) {
-            mLocalLog = localLog;
-            mDbg = dbg;
-        }
-
-        // Determines whether or not the given scan result is the best one its seen so far.
-        void evalCarrierCandidate(ScanResult scanResult, int score) {
-            if (score > mHighScore) {
-                mHighScore = score;
-                mScanResultCandidate = scanResult;
-                localLog(toScanId(scanResult) +
-                        " become the new untrusted carrier network candidate");
-            }
-        }
-
-        int getHighScore() {
-            return mHighScore;
-        }
-
-        public ScanResult getScanResultCandidate() {
-            return mScanResultCandidate;
-        }
-
-        private void localLog(String log) {
-            if (mDbg) {
-                mLocalLog.log(log);
-            }
-        }
-    }
-
-    private WifiConfiguration getMatchingConfigForEAPNetworks(
-            ScanResult scanResult, List<WifiConfiguration> candidateConfigs) {
-        if (scanResult == null || candidateConfigs == null) {
-            return null;
-        }
-        // TODO currently we only support EAP. We'll add to this in OC.
-        if (!scanResult.capabilities.contains("EAP")) {
-            return null;
-        }
-        String ssid = "\"" + scanResult.SSID + "\"";
-        for (WifiConfiguration config : candidateConfigs) {
-            if (config.SSID.equals(ssid)) {
-                // TODO currently we only support EAP. We'll add to this in OC.
-                if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) ||
-                        config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
-                    return config;
-                }
-            }
-        }
-        return null;
-    }
-}
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index d32c722..8bc8bf3 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -16,342 +16,140 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.net.NetworkAgent;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.util.Log;
 
+import com.android.internal.R;
 
 /**
-* Calculate scores for connected wifi networks.
+ * Class used to calculate scores for connected wifi networks and report it to the associated
+ * network agent.
 */
 public class WifiScoreReport {
-    // TODO: switch to WifiScoreReport if it doesn't break any tools
-    private static final String TAG = "WifiStateMachine";
+    private static final String TAG = "WifiScoreReport";
 
-    // TODO: This score was hardcorded to 56.  Need to understand why after finishing code refactor
     private static final int STARTING_SCORE = 56;
 
-    // TODO: Understand why these values are used
-    private static final int MAX_BAD_LINKSPEED_COUNT = 6;
     private static final int SCAN_CACHE_VISIBILITY_MS = 12000;
     private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6;
     private static final int SCAN_CACHE_COUNT_PENALTY = 2;
     private static final int AGGRESSIVE_HANDOVER_PENALTY = 6;
-    private static final int MIN_SUCCESS_COUNT = 5;
-    private static final int MAX_SUCCESS_COUNT_OF_STUCK_LINK = 3;
+    private static final int MAX_SUCCESS_RATE_OF_STUCK_LINK = 3; // proportional to packets per sec
     private static final int MAX_STUCK_LINK_COUNT = 5;
-    private static final int MIN_NUM_TICKS_AT_STATE = 1000;
-    private static final int USER_DISCONNECT_PENALTY = 5;
     private static final int MAX_BAD_RSSI_COUNT = 7;
     private static final int BAD_RSSI_COUNT_PENALTY = 2;
     private static final int MAX_LOW_RSSI_COUNT = 1;
-    private static final double MIN_TX_RATE_FOR_WORKING_LINK = 0.3;
+    private static final double MIN_TX_FAILURE_RATE_FOR_WORKING_LINK = 0.3;
     private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1;
     private static final int LINK_STUCK_PENALTY = 2;
     private static final int BAD_LINKSPEED_PENALTY = 4;
     private static final int GOOD_LINKSPEED_BONUS = 4;
 
+    // Device configs. The values are examples.
+    private final int mThresholdMinimumRssi5;      // -82
+    private final int mThresholdQualifiedRssi5;    // -70
+    private final int mThresholdSaturatedRssi5;    // -57
+    private final int mThresholdMinimumRssi24;     // -85
+    private final int mThresholdQualifiedRssi24;   // -73
+    private final int mThresholdSaturatedRssi24;   // -60
+    private final int mBadLinkSpeed24;             //  6 Mbps
+    private final int mBadLinkSpeed5;              // 12 Mbps
+    private final int mGoodLinkSpeed24;            // 24 Mbps
+    private final int mGoodLinkSpeed5;             // 36 Mbps
 
+    private final WifiConfigManager mWifiConfigManager;
+    private boolean mVerboseLoggingEnabled = false;
+
+    // Cache of the last score report.
     private String mReport;
-    private int mBadLinkspeedcount;
+    private boolean mReportValid = false;
 
-    WifiScoreReport(String report, int badLinkspeedcount) {
-        mReport = report;
-        mBadLinkspeedcount = badLinkspeedcount;
+    // State set by updateScoringState
+    private boolean mMultiBandScanResults;
+    private boolean mIsHomeNetwork;
+
+    WifiScoreReport(Context context, WifiConfigManager wifiConfigManager) {
+        // Fetch all the device configs.
+        mThresholdMinimumRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdQualifiedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+        mThresholdSaturatedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
+        mThresholdMinimumRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mThresholdQualifiedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+        mThresholdSaturatedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
+        mBadLinkSpeed24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_link_speed_24);
+        mBadLinkSpeed5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_link_speed_5);
+        mGoodLinkSpeed24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_link_speed_24);
+        mGoodLinkSpeed5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_link_speed_5);
+
+        mWifiConfigManager = wifiConfigManager;
     }
 
     /**
-     *  Method returning the String representation of the score report.
+     * Method returning the String representation of the last score report.
      *
      *  @return String score report
      */
-    public String getReport() {
+    public String getLastReport() {
         return mReport;
     }
 
     /**
-     *  Method returning the bad link speed count at the time of the current score report.
-     *
-     *  @return int bad linkspeed count
+     * Reset the last calculated score.
      */
-    public int getBadLinkspeedcount() {
-        return mBadLinkspeedcount;
+    public void reset() {
+        mReport = "";
+        mReportValid = false;
     }
 
     /**
-     * Calculate wifi network score based on updated link layer stats and return a new
-     * WifiScoreReport object.
+     * Checks if the last report data is valid or not. This will be cleared when {@link #reset()} is
+     * invoked.
+     *
+     * @return true if valid, false otherwise.
+     */
+    public boolean isLastReportValid() {
+        return mReportValid;
+    }
+
+    /**
+     * Enable/Disable verbose logging in score report generation.
+     */
+    public void enableVerboseLogging(boolean enable) {
+        mVerboseLoggingEnabled = enable;
+    }
+
+    /**
+     * Calculate wifi network score based on updated link layer stats and send the score to
+     * the provided network agent.
      *
      * If the score has changed from the previous value, update the WifiNetworkAgent.
-     * @param wifiInfo WifiInfo information about current network connection
-     * @param currentConfiguration WifiConfiguration current wifi config
-     * @param wifiConfigManager WifiConfigManager Object holding current config state
-     * @param networkAgent NetworkAgent to be notified of new score
-     * @param lastReport String most recent score report
-     * @param aggressiveHandover int current aggressiveHandover setting
-     * @return WifiScoreReport Wifi Score report
+     *
+     * Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds.
+     *
+     * @param wifiInfo WifiInfo instance pointing to the currently connected network.
+     * @param networkAgent NetworkAgent to be notified of new score.
+     * @param aggressiveHandover int current aggressiveHandover setting.
+     * @param wifiMetrics for reporting our scores.
      */
-    public static WifiScoreReport calculateScore(WifiInfo wifiInfo,
-                                                 WifiConfiguration currentConfiguration,
-                                                 WifiConfigManager wifiConfigManager,
-                                                 NetworkAgent networkAgent,
-                                                 WifiScoreReport lastReport,
-                                                 int aggressiveHandover,
-                                                 WifiMetrics wifiMetrics) {
-        boolean debugLogging = false;
-        if (wifiConfigManager.mEnableVerboseLogging.get() > 0) {
-            debugLogging = true;
-        }
+    public void calculateAndReportScore(WifiInfo wifiInfo, NetworkAgent networkAgent,
+                                        int aggressiveHandover, WifiMetrics wifiMetrics) {
+        int score;
 
-        StringBuilder sb = new StringBuilder();
-
-        int score = STARTING_SCORE;
-        boolean isBadLinkspeed = (wifiInfo.is24GHz()
-                && wifiInfo.getLinkSpeed() < wifiConfigManager.mBadLinkSpeed24)
-                || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
-                < wifiConfigManager.mBadLinkSpeed5);
-        boolean isGoodLinkspeed = (wifiInfo.is24GHz()
-                && wifiInfo.getLinkSpeed() >= wifiConfigManager.mGoodLinkSpeed24)
-                || (wifiInfo.is5GHz() && wifiInfo.getLinkSpeed()
-                >= wifiConfigManager.mGoodLinkSpeed5);
-
-        int badLinkspeedcount = 0;
-        if (lastReport != null) {
-            badLinkspeedcount = lastReport.getBadLinkspeedcount();
-        }
-
-        if (isBadLinkspeed) {
-            if (badLinkspeedcount < MAX_BAD_LINKSPEED_COUNT) {
-                badLinkspeedcount++;
-            }
-        } else {
-            if (badLinkspeedcount > 0) {
-                badLinkspeedcount--;
-            }
-        }
-
-        if (isBadLinkspeed) sb.append(" bl(").append(badLinkspeedcount).append(")");
-        if (isGoodLinkspeed) sb.append(" gl");
-
-        /**
-         * We want to make sure that we use the 24GHz RSSI thresholds if
-         * there are 2.4GHz scan results
-         * otherwise we end up lowering the score based on 5GHz values
-         * which may cause a switch to LTE before roaming has a chance to try 2.4GHz
-         * We also might unblacklist the configuation based on 2.4GHz
-         * thresholds but joining 5GHz anyhow, and failing over to 2.4GHz because 5GHz is not good
-         */
-        boolean use24Thresholds = false;
-        boolean homeNetworkBoost = false;
-        ScanDetailCache scanDetailCache =
-                wifiConfigManager.getScanDetailCache(currentConfiguration);
-        if (currentConfiguration != null && scanDetailCache != null) {
-            currentConfiguration.setVisibility(
-                    scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
-            if (currentConfiguration.visibility != null) {
-                if (currentConfiguration.visibility.rssi24 != WifiConfiguration.INVALID_RSSI
-                        && currentConfiguration.visibility.rssi24
-                        >= (currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY)) {
-                    use24Thresholds = true;
-                }
-            }
-            if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT
-                    && currentConfiguration.allowedKeyManagement.cardinality() == 1
-                    && currentConfiguration.allowedKeyManagement
-                            .get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
-                // A PSK network with less than 6 known BSSIDs
-                // This is most likely a home network and thus we want to stick to wifi more
-                homeNetworkBoost = true;
-            }
-        }
-        if (homeNetworkBoost) sb.append(" hn");
-        if (use24Thresholds) sb.append(" u24");
-
-        int rssi = wifiInfo.getRssi() - AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover
-                + (homeNetworkBoost ? WifiConfiguration.HOME_NETWORK_RSSI_BOOST : 0);
-        sb.append(String.format(" rssi=%d ag=%d", rssi, aggressiveHandover));
-
-        boolean is24GHz = use24Thresholds || wifiInfo.is24GHz();
-
-        boolean isBadRSSI = (is24GHz && rssi < wifiConfigManager.mThresholdMinimumRssi24.get())
-                || (!is24GHz && rssi < wifiConfigManager.mThresholdMinimumRssi5.get());
-        boolean isLowRSSI = (is24GHz && rssi < wifiConfigManager.mThresholdQualifiedRssi24.get())
-                || (!is24GHz
-                        && wifiInfo.getRssi() < wifiConfigManager.mThresholdMinimumRssi5.get());
-        boolean isHighRSSI = (is24GHz && rssi >= wifiConfigManager.mThresholdSaturatedRssi24.get())
-                || (!is24GHz
-                        && wifiInfo.getRssi() >= wifiConfigManager.mThresholdSaturatedRssi5.get());
-
-        if (isBadRSSI) sb.append(" br");
-        if (isLowRSSI) sb.append(" lr");
-        if (isHighRSSI) sb.append(" hr");
-
-        int penalizedDueToUserTriggeredDisconnect = 0;        // Not a user triggered disconnect
-        if (currentConfiguration != null
-                && (wifiInfo.txSuccessRate > MIN_SUCCESS_COUNT
-                        || wifiInfo.rxSuccessRate > MIN_SUCCESS_COUNT)) {
-            if (isBadRSSI) {
-                currentConfiguration.numTicksAtBadRSSI++;
-                if (currentConfiguration.numTicksAtBadRSSI > MIN_NUM_TICKS_AT_STATE) {
-                    // We remained associated for a compound amount of time while passing
-                    // traffic, hence loose the corresponding user triggered disabled stats
-                    if (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0) {
-                        currentConfiguration.numUserTriggeredWifiDisableBadRSSI--;
-                    }
-                    if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
-                        currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
-                    }
-                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
-                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
-                    }
-                    currentConfiguration.numTicksAtBadRSSI = 0;
-                }
-                if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
-                        && (currentConfiguration.numUserTriggeredWifiDisableBadRSSI > 0
-                                || currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
-                                || currentConfiguration
-                                        .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
-                    score = score - USER_DISCONNECT_PENALTY;
-                    penalizedDueToUserTriggeredDisconnect = 1;
-                    sb.append(" p1");
-                }
-            } else if (isLowRSSI) {
-                currentConfiguration.numTicksAtLowRSSI++;
-                if (currentConfiguration.numTicksAtLowRSSI > MIN_NUM_TICKS_AT_STATE) {
-                    // We remained associated for a compound amount of time while passing
-                    // traffic, hence loose the corresponding user triggered disabled stats
-                    if (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0) {
-                        currentConfiguration.numUserTriggeredWifiDisableLowRSSI--;
-                    }
-                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
-                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
-                    }
-                    currentConfiguration.numTicksAtLowRSSI = 0;
-                }
-                if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
-                        && (currentConfiguration.numUserTriggeredWifiDisableLowRSSI > 0
-                                || currentConfiguration
-                                        .numUserTriggeredWifiDisableNotHighRSSI > 0)) {
-                    score = score - USER_DISCONNECT_PENALTY;
-                    penalizedDueToUserTriggeredDisconnect = 2;
-                    sb.append(" p2");
-                }
-            } else if (!isHighRSSI) {
-                currentConfiguration.numTicksAtNotHighRSSI++;
-                if (currentConfiguration.numTicksAtNotHighRSSI > MIN_NUM_TICKS_AT_STATE) {
-                    // We remained associated for a compound amount of time while passing
-                    // traffic, hence loose the corresponding user triggered disabled stats
-                    if (currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
-                        currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI--;
-                    }
-                    currentConfiguration.numTicksAtNotHighRSSI = 0;
-                }
-                if (wifiConfigManager.mEnableWifiCellularHandoverUserTriggeredAdjustment
-                        && currentConfiguration.numUserTriggeredWifiDisableNotHighRSSI > 0) {
-                    score = score - USER_DISCONNECT_PENALTY;
-                    penalizedDueToUserTriggeredDisconnect = 3;
-                    sb.append(" p3");
-                }
-            }
-            sb.append(String.format(" ticks %d,%d,%d", currentConfiguration.numTicksAtBadRSSI,
-                    currentConfiguration.numTicksAtLowRSSI,
-                    currentConfiguration.numTicksAtNotHighRSSI));
-        }
-
-        if (debugLogging) {
-            String rssiStatus = "";
-            if (isBadRSSI) {
-                rssiStatus += " badRSSI ";
-            } else if (isHighRSSI) {
-                rssiStatus += " highRSSI ";
-            } else if (isLowRSSI) {
-                rssiStatus += " lowRSSI ";
-            }
-            if (isBadLinkspeed) rssiStatus += " lowSpeed ";
-            Log.d(TAG, "calculateWifiScore freq=" + Integer.toString(wifiInfo.getFrequency())
-                    + " speed=" + Integer.toString(wifiInfo.getLinkSpeed())
-                    + " score=" + Integer.toString(wifiInfo.score)
-                    + rssiStatus
-                    + " -> txbadrate=" + String.format("%.2f", wifiInfo.txBadRate)
-                    + " txgoodrate=" + String.format("%.2f", wifiInfo.txSuccessRate)
-                    + " txretriesrate=" + String.format("%.2f", wifiInfo.txRetriesRate)
-                    + " rxrate=" + String.format("%.2f", wifiInfo.rxSuccessRate)
-                    + " userTriggerdPenalty" + penalizedDueToUserTriggeredDisconnect);
-        }
-
-        if ((wifiInfo.txBadRate >= 1) && (wifiInfo.txSuccessRate < MAX_SUCCESS_COUNT_OF_STUCK_LINK)
-                && (isBadRSSI || isLowRSSI)) {
-            // Link is stuck
-            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
-                wifiInfo.linkStuckCount += 1;
-            }
-            sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount));
-            if (debugLogging) {
-                Log.d(TAG, " bad link -> stuck count ="
-                        + Integer.toString(wifiInfo.linkStuckCount));
-            }
-        } else if (wifiInfo.txBadRate < MIN_TX_RATE_FOR_WORKING_LINK) {
-            if (wifiInfo.linkStuckCount > 0) {
-                wifiInfo.linkStuckCount -= 1;
-            }
-            sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount));
-            if (debugLogging) {
-                Log.d(TAG, " good link -> stuck count ="
-                        + Integer.toString(wifiInfo.linkStuckCount));
-            }
-        }
-
-        sb.append(String.format(" [%d", score));
-
-        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
-            // Once link gets stuck for more than 3 seconds, start reducing the score
-            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
-        }
-        sb.append(String.format(",%d", score));
-
-        if (isBadLinkspeed) {
-            score -= BAD_LINKSPEED_PENALTY;
-            if (debugLogging) {
-                Log.d(TAG, " isBadLinkspeed   ---> count=" + badLinkspeedcount
-                        + " score=" + Integer.toString(score));
-            }
-        } else if ((isGoodLinkspeed) && (wifiInfo.txSuccessRate > 5)) {
-            score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone dont kill us
-        }
-        sb.append(String.format(",%d", score));
-
-        if (isBadRSSI) {
-            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
-                wifiInfo.badRssiCount += 1;
-            }
-        } else if (isLowRSSI) {
-            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
-            if (wifiInfo.badRssiCount > 0) {
-                // Decrement bad Rssi count
-                wifiInfo.badRssiCount -= 1;
-            }
-        } else {
-            wifiInfo.badRssiCount = 0;
-            wifiInfo.lowRssiCount = 0;
-        }
-
-        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
-        sb.append(String.format(",%d", score));
-
-        if (debugLogging) {
-            Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount)
-                    + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount)
-                    + " --> score " + Integer.toString(score));
-        }
-
-        if (isHighRSSI) {
-            score += 5;
-            if (debugLogging) Log.d(TAG, " isHighRSSI       ---> score=" + Integer.toString(score));
-        }
-        sb.append(String.format(",%d]", score));
-
-        sb.append(String.format(" brc=%d lrc=%d", wifiInfo.badRssiCount, wifiInfo.lowRssiCount));
+        updateScoringState(wifiInfo, aggressiveHandover);
+        score = calculateScore(wifiInfo, aggressiveHandover);
 
         //sanitize boundaries
         if (score > NetworkAgent.WIFI_BASE_SCORE) {
@@ -363,15 +161,163 @@
 
         //report score
         if (score != wifiInfo.score) {
-            if (debugLogging) {
-                Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score));
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, " report new wifi score " + score);
             }
             wifiInfo.score = score;
             if (networkAgent != null) {
                 networkAgent.sendNetworkScore(score);
             }
         }
+
+        mReport = String.format(" score=%d", score);
+        mReportValid = true;
         wifiMetrics.incrementWifiScoreCount(score);
-        return new WifiScoreReport(sb.toString(), badLinkspeedcount);
+    }
+
+    /**
+     * Updates the state.
+     */
+    private void updateScoringState(WifiInfo wifiInfo, int aggressiveHandover) {
+        mMultiBandScanResults = multiBandScanResults(wifiInfo);
+        mIsHomeNetwork = isHomeNetwork(wifiInfo);
+
+        int rssiThreshBad = mThresholdMinimumRssi24;
+        int rssiThreshLow = mThresholdQualifiedRssi24;
+
+        if (wifiInfo.is5GHz() && !mMultiBandScanResults) {
+            rssiThreshBad = mThresholdMinimumRssi5;
+            rssiThreshLow = mThresholdQualifiedRssi5;
+        }
+
+        int rssi =  wifiInfo.getRssi();
+        if (aggressiveHandover != 0) {
+            rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
+        }
+        if (mIsHomeNetwork) {
+            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
+        }
+
+        if ((wifiInfo.txBadRate >= 1)
+                && (wifiInfo.txSuccessRate < MAX_SUCCESS_RATE_OF_STUCK_LINK)
+                && rssi < rssiThreshLow) {
+            // Link is stuck
+            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
+                wifiInfo.linkStuckCount += 1;
+            }
+        } else if (wifiInfo.txBadRate < MIN_TX_FAILURE_RATE_FOR_WORKING_LINK) {
+            if (wifiInfo.linkStuckCount > 0) {
+                wifiInfo.linkStuckCount -= 1;
+            }
+        }
+
+        if (rssi < rssiThreshBad) {
+            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
+                wifiInfo.badRssiCount += 1;
+            }
+        } else if (rssi < rssiThreshLow) {
+            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
+            if (wifiInfo.badRssiCount > 0) {
+                // Decrement bad Rssi count
+                wifiInfo.badRssiCount -= 1;
+            }
+        } else {
+            wifiInfo.badRssiCount = 0;
+            wifiInfo.lowRssiCount = 0;
+        }
+
+    }
+
+    /**
+     * Calculates the score, without all the cruft.
+     */
+    private int calculateScore(WifiInfo wifiInfo, int aggressiveHandover) {
+        int score = STARTING_SCORE;
+
+        int rssiThreshSaturated = mThresholdSaturatedRssi24;
+        int linkspeedThreshBad = mBadLinkSpeed24;
+        int linkspeedThreshGood = mGoodLinkSpeed24;
+
+        if (wifiInfo.is5GHz()) {
+            if (!mMultiBandScanResults) {
+                rssiThreshSaturated = mThresholdSaturatedRssi5;
+            }
+            linkspeedThreshBad = mBadLinkSpeed5;
+            linkspeedThreshGood = mGoodLinkSpeed5;
+        }
+
+        int rssi =  wifiInfo.getRssi();
+        if (aggressiveHandover != 0) {
+            rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover;
+        }
+        if (mIsHomeNetwork) {
+            rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
+        }
+
+        int linkSpeed = wifiInfo.getLinkSpeed();
+
+        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
+            // Once link gets stuck for more than 3 seconds, start reducing the score
+            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
+        }
+
+        if (linkSpeed < linkspeedThreshBad) {
+            score -= BAD_LINKSPEED_PENALTY;
+        } else if ((linkSpeed >= linkspeedThreshGood) && (wifiInfo.txSuccessRate > 5)) {
+            score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone doesn't kill us
+        }
+
+        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
+
+        if (rssi >= rssiThreshSaturated) score += 5;
+
+        if (score > NetworkAgent.WIFI_BASE_SCORE) score = NetworkAgent.WIFI_BASE_SCORE;
+        if (score < 0) score = 0;
+
+        return score;
+    }
+
+    /**
+     * Determines if we can see both 2.4GHz and 5GHz for current config
+     */
+    private boolean multiBandScanResults(WifiInfo wifiInfo) {
+        WifiConfiguration currentConfiguration =
+                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
+        if (currentConfiguration == null) return false;
+        ScanDetailCache scanDetailCache =
+                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
+        if (scanDetailCache == null) return false;
+        // Nasty that we change state here...
+        currentConfiguration.setVisibility(scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS));
+        if (currentConfiguration.visibility == null) return false;
+        if (currentConfiguration.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) return false;
+        if (currentConfiguration.visibility.rssi5 == WifiConfiguration.INVALID_RSSI) return false;
+        // N.B. this does not do exactly what is claimed!
+        if (currentConfiguration.visibility.rssi24
+                >= currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Decides whether the current network is a "home" network
+     */
+    private boolean isHomeNetwork(WifiInfo wifiInfo) {
+        WifiConfiguration currentConfiguration =
+                mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId());
+        if (currentConfiguration == null) return false;
+        // This seems like it will only return true for really old routers!
+        if (currentConfiguration.allowedKeyManagement.cardinality() != 1) return false;
+        if (!currentConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            return false;
+        }
+        ScanDetailCache scanDetailCache =
+                mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId());
+        if (scanDetailCache == null) return false;
+        if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT) {
+            return true;
+        }
+        return false;
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiService.java b/service/java/com/android/server/wifi/WifiService.java
index 28e5b18..d298fbb 100644
--- a/service/java/com/android/server/wifi/WifiService.java
+++ b/service/java/com/android/server/wifi/WifiService.java
@@ -20,6 +20,7 @@
 import android.util.Log;
 
 import com.android.server.SystemService;
+import com.android.server.wifi.util.WifiAsyncChannel;
 
 public final class WifiService extends SystemService {
 
@@ -28,7 +29,7 @@
 
     public WifiService(Context context) {
         super(context);
-        mImpl = new WifiServiceImpl(context);
+        mImpl = new WifiServiceImpl(context, new WifiInjector(context), new WifiAsyncChannel(TAG));
     }
 
     @Override
@@ -48,4 +49,14 @@
     public void onSwitchUser(int userId) {
         mImpl.handleUserSwitch(userId);
     }
+
+    @Override
+    public void onUnlockUser(int userId) {
+        mImpl.handleUserUnlock(userId);
+    }
+
+    @Override
+    public void onStopUser(int userId) {
+        mImpl.handleUserStop(userId);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index ae42db0..5c9548a 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -16,6 +16,19 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC;
+import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHANNEL;
+import static android.net.wifi.WifiManager.SAP_START_FAILURE_NO_CHANNEL;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+
+import static com.android.server.wifi.LocalOnlyHotspotRequestInfo.HOTSPOT_NO_ERROR;
 import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
 import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
 import static com.android.server.wifi.WifiController.CMD_EMERGENCY_CALL_STATE_CHANGED;
@@ -30,6 +43,7 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.AppOpsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -38,33 +52,33 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
+import android.content.pm.ParceledListSlice;
 import android.database.ContentObserver;
-import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
 import android.net.DhcpResults;
+import android.net.IpConfiguration;
 import android.net.Network;
-import android.net.NetworkScorerAppManager;
 import android.net.NetworkUtils;
+import android.net.StaticIpConfiguration;
 import android.net.Uri;
 import android.net.ip.IpManager;
 import android.net.wifi.IWifiManager;
-import android.net.wifi.PasspointManagementObjectDefinition;
 import android.net.wifi.ScanResult;
 import android.net.wifi.ScanSettings;
 import android.net.wifi.WifiActivityEnergyInfo;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConnectionStatistics;
-import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiLinkLayerStats;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.AsyncTask;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
@@ -74,26 +88,24 @@
 import android.os.Process;
 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;
 import android.provider.Settings;
-import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.R;
-import com.android.internal.app.IBatteryStats;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 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;
+import com.android.server.wifi.hotspot2.PasspointProvider;
+import com.android.server.wifi.util.WifiHandler;
+import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import java.io.BufferedReader;
 import java.io.FileDescriptor;
@@ -107,13 +119,14 @@
 import java.security.KeyStore;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathValidator;
-import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.PKIXParameters;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * WifiService handles remote WiFi operation requests by implementing
@@ -125,23 +138,32 @@
     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";
+
+    // Dumpsys argument to enable/disable disconnect on IP reachability failures.
+    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT = "set-ipreach-disconnect";
+    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED = "enabled";
+    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED = "disabled";
+
+    // Default scan background throttling interval if not overriden in settings
+    private static final long DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
+
+    // Apps with importance higher than this value is considered as background app.
+    private static final int BACKGROUND_IMPORTANCE_CUTOFF =
+            RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 
     final WifiStateMachine mWifiStateMachine;
 
     private final Context mContext;
     private final FrameworkFacade mFacade;
+    private final Clock mClock;
+    private final ArraySet<String> mBackgroundThrottlePackageWhitelist = new ArraySet<>();
 
-    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;
+    private final ActivityManager mActivityManager;
     private final WifiCountryCode mCountryCode;
+    private long mBackgroundThrottleInterval;
     // Debug counter tracking scan requests sent by WifiManager
     private int scanRequestCounter = 0;
 
@@ -157,24 +179,60 @@
     private final WifiCertManager mCertManager;
 
     private final WifiInjector mWifiInjector;
+    /* Backup/Restore Module */
+    private final WifiBackupRestore mWifiBackupRestore;
+
+    // Map of package name of background scan apps and last scan timestamp.
+    private final ArrayMap<String, Long> mLastScanTimestamps;
+
+    private WifiScanner mWifiScanner;
+    private WifiLog mLog;
+
     /**
      * Asynchronous channel to WifiStateMachine
      */
     private AsyncChannel mWifiStateMachineChannel;
 
     private final boolean mPermissionReviewRequired;
+    private final FrameworkFacade mFrameworkFacade;
+
+    private WifiPermissionsUtil mWifiPermissionsUtil;
+
+    @GuardedBy("mLocalOnlyHotspotRequests")
+    private final HashMap<Integer, LocalOnlyHotspotRequestInfo> mLocalOnlyHotspotRequests;
+    @GuardedBy("mLocalOnlyHotspotRequests")
+    private WifiConfiguration mLocalOnlyHotspotConfig = null;
+    @GuardedBy("mLocalOnlyHotspotRequests")
+    private final ConcurrentHashMap<String, Integer> mIfaceIpModes;
+
+    /**
+     * Callback for use with LocalOnlyHotspot to unregister requesting applications upon death.
+     *
+     * @hide
+     */
+    public final class LocalOnlyRequestorCallback
+            implements LocalOnlyHotspotRequestInfo.RequestingApplicationDeathCallback {
+        /**
+         * Called with requesting app has died.
+         */
+        @Override
+        public void onLocalOnlyHotspotRequestorDeath(LocalOnlyHotspotRequestInfo requestor) {
+            unregisterCallingAppAndStopLocalOnlyHotspot(requestor);
+        };
+    }
 
     /**
      * Handles client connections
      */
-    private class ClientHandler extends Handler {
+    private class ClientHandler extends WifiHandler {
 
-        ClientHandler(Looper looper) {
-            super(looper);
+        ClientHandler(String tag, Looper looper) {
+            super(tag, looper);
         }
 
         @Override
         public void handleMessage(Message msg) {
+            super.handleMessage(msg);
             switch (msg.what) {
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
@@ -197,46 +255,49 @@
                     break;
                 }
                 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
-                    AsyncChannel ac = new AsyncChannel();
+                    AsyncChannel ac = mFrameworkFacade.makeWifiAsyncChannel(TAG);
                     ac.connect(mContext, this, msg.replyTo);
                     break;
                 }
-                /* Client commands are forwarded to state machine */
-                case WifiManager.CONNECT_NETWORK:
-                case WifiManager.SAVE_NETWORK: {
+                case WifiManager.CONNECT_NETWORK: {
                     WifiConfiguration config = (WifiConfiguration) msg.obj;
                     int networkId = msg.arg1;
-                    if (msg.what == WifiManager.SAVE_NETWORK) {
-                        Slog.d("WiFiServiceImpl ", "SAVE"
-                                + " nid=" + Integer.toString(networkId)
-                                + " uid=" + msg.sendingUid
-                                + " name="
-                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
-                    }
-                    if (msg.what == WifiManager.CONNECT_NETWORK) {
-                        Slog.d("WiFiServiceImpl ", "CONNECT "
-                                + " nid=" + Integer.toString(networkId)
-                                + " uid=" + msg.sendingUid
-                                + " name="
-                                + mContext.getPackageManager().getNameForUid(msg.sendingUid));
-                    }
-
+                    Slog.d("WiFiServiceImpl ", "CONNECT "
+                            + " nid=" + Integer.toString(networkId)
+                            + " uid=" + msg.sendingUid
+                            + " name="
+                            + mContext.getPackageManager().getNameForUid(msg.sendingUid));
                     if (config != null && isValid(config)) {
-                        if (DBG) Slog.d(TAG, "Connect with config" + config);
+                        if (DBG) Slog.d(TAG, "Connect with config " + config);
+                        /* Command is forwarded to state machine */
                         mWifiStateMachine.sendMessage(Message.obtain(msg));
                     } else if (config == null
                             && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-                        if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
+                        if (DBG) Slog.d(TAG, "Connect with networkId " + networkId);
                         mWifiStateMachine.sendMessage(Message.obtain(msg));
                     } else {
                         Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
-                        if (msg.what == WifiManager.CONNECT_NETWORK) {
-                            replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
-                                    WifiManager.INVALID_ARGS);
-                        } else {
-                            replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,
-                                    WifiManager.INVALID_ARGS);
-                        }
+                        replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
+                                WifiManager.INVALID_ARGS);
+                    }
+                    break;
+                }
+                case WifiManager.SAVE_NETWORK: {
+                    WifiConfiguration config = (WifiConfiguration) msg.obj;
+                    int networkId = msg.arg1;
+                    Slog.d("WiFiServiceImpl ", "SAVE"
+                            + " nid=" + Integer.toString(networkId)
+                            + " uid=" + msg.sendingUid
+                            + " name="
+                            + mContext.getPackageManager().getNameForUid(msg.sendingUid));
+                    if (config != null && isValid(config)) {
+                        if (DBG) Slog.d(TAG, "Save network with config " + config);
+                        /* Command is forwarded to state machine */
+                        mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    } else {
+                        Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
+                        replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,
+                                WifiManager.INVALID_ARGS);
                     }
                     break;
                 }
@@ -258,7 +319,8 @@
         }
 
         private void replyFailed(Message msg, int what, int why) {
-            Message reply = msg.obtain();
+            if (msg.replyTo == null) return;
+            Message reply = Message.obtain();
             reply.what = what;
             reply.arg1 = why;
             try {
@@ -273,17 +335,18 @@
     /**
      * Handles interaction with WifiStateMachine
      */
-    private class WifiStateMachineHandler extends Handler {
+    private class WifiStateMachineHandler extends WifiHandler {
         private AsyncChannel mWsmChannel;
 
-        WifiStateMachineHandler(Looper looper) {
-            super(looper);
-            mWsmChannel = new AsyncChannel();
+        WifiStateMachineHandler(String tag, Looper looper, AsyncChannel asyncChannel) {
+            super(tag, looper);
+            mWsmChannel = asyncChannel;
             mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
         }
 
         @Override
         public void handleMessage(Message msg) {
+            super.handleMessage(msg);
             switch (msg.what) {
                 case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
                     if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
@@ -310,68 +373,81 @@
     }
 
     WifiStateMachineHandler mWifiStateMachineHandler;
-
     private WifiController mWifiController;
     private final WifiLockManager mWifiLockManager;
+    private final WifiMulticastLockManager mWifiMulticastLockManager;
 
-    public WifiServiceImpl(Context context) {
+    public WifiServiceImpl(Context context, WifiInjector wifiInjector, AsyncChannel asyncChannel) {
         mContext = context;
-        mWifiInjector = WifiInjector.getInstance();
-        mFacade = new FrameworkFacade();
-        HandlerThread wifiThread = new HandlerThread("WifiService");
-        wifiThread.start();
+        mWifiInjector = wifiInjector;
+        mClock = wifiInjector.getClock();
+
+        mFacade = mWifiInjector.getFrameworkFacade();
         mWifiMetrics = mWifiInjector.getWifiMetrics();
-        mTrafficPoller = new WifiTrafficPoller(mContext, wifiThread.getLooper(),
-                WifiNative.getWlanNativeInterface().getInterfaceName());
-        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);
+        mTrafficPoller = mWifiInjector.getWifiTrafficPoller();
+        mUserManager = mWifiInjector.getUserManager();
+        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);
-
-        mNotificationController = new WifiNotificationController(mContext,
-                wifiThread.getLooper(), mWifiStateMachine, mFacade, null);
-
-        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);
-
+        mSettingsStore = mWifiInjector.getWifiSettingsStore();
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mCertManager = mWifiInjector.getWifiCertManager();
+        mNotificationController = mWifiInjector.getWifiNotificationController();
+        mWifiLockManager = mWifiInjector.getWifiLockManager();
+        mWifiMulticastLockManager = mWifiInjector.getWifiMulticastLockManager();
+        HandlerThread wifiServiceHandlerThread = mWifiInjector.getWifiServiceHandlerThread();
+        mClientHandler = new ClientHandler(TAG, wifiServiceHandlerThread.getLooper());
+        mWifiStateMachineHandler = new WifiStateMachineHandler(TAG,
+                wifiServiceHandlerThread.getLooper(), asyncChannel);
+        mWifiController = mWifiInjector.getWifiController();
+        mWifiBackupRestore = mWifiInjector.getWifiBackupRestore();
         mPermissionReviewRequired = Build.PERMISSIONS_REVIEW_REQUIRED
                 || context.getResources().getBoolean(
                 com.android.internal.R.bool.config_permissionReviewRequired);
+        mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
+        mLog = mWifiInjector.makeLog(TAG);
+        mFrameworkFacade = wifiInjector.getFrameworkFacade();
+        mLastScanTimestamps = new ArrayMap<>();
+        updateBackgroundThrottleInterval();
+        updateBackgroundThrottlingWhitelist();
+        mIfaceIpModes = new ConcurrentHashMap<>();
+        mLocalOnlyHotspotRequests = new HashMap<>();
+        enableVerboseLoggingInternal(getVerboseLoggingLevel());
     }
 
+    /**
+     * Provide a way for unit tests to set valid log object in the WifiHandler
+     * @param log WifiLog object to assign to the clientHandler
+     */
+    @VisibleForTesting
+    public void setWifiHandlerLogForTest(WifiLog log) {
+        mClientHandler.setWifiLog(log);
+    }
 
     /**
-     * Check if Wi-Fi needs to be enabled and start
-     * if needed
+     * Check if we are ready to start wifi.
      *
-     * This function is used only at boot time
+     * First check if we will be restarting system services to decrypt the device. If the device is
+     * not encrypted, check if Wi-Fi needs to be enabled and start if needed
+     *
+     * This function is used only at boot time.
      */
     public void checkAndStartWifi() {
-        /* Check if wi-fi needs to be enabled */
+        // First check if we will end up restarting WifiService
+        if (mFrameworkFacade.inStorageManagerCryptKeeperBounce()) {
+            Log.d(TAG, "Device still encrypted. Need to restart SystemServer.  Do not start wifi.");
+            return;
+        }
+
+        // Check if wi-fi needs to be enabled
         boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
         Slog.i(TAG, "WifiService starting up with Wi-Fi " +
                 (wifiEnabled ? "enabled" : "disabled"));
 
         registerForScanModeChange();
+        registerForBackgroundThrottleChanges();
         mContext.registerReceiver(
                 new BroadcastReceiver() {
                     @Override
@@ -405,6 +481,25 @@
                 },
                 new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED));
 
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        final int currState = intent.getIntExtra(EXTRA_WIFI_AP_STATE,
+                                                                    WIFI_AP_STATE_DISABLED);
+                        final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE,
+                                                                 WIFI_AP_STATE_DISABLED);
+                        final int errorCode = intent.getIntExtra(EXTRA_WIFI_AP_FAILURE_REASON,
+                                                                 HOTSPOT_NO_ERROR);
+                        final String ifaceName =
+                                intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
+                        final int mode = intent.getIntExtra(EXTRA_WIFI_AP_MODE,
+                                                            WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+                        handleWifiApStateChange(currState, prevState, errorCode, ifaceName, mode);
+                    }
+                },
+                new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION));
+
         // Adding optimizations of only receiving broadcasts when wifi is enabled
         // can result in race conditions when apps toggle wifi in the background
         // without active user involvement. Always receive broadcasts.
@@ -412,6 +507,9 @@
         registerForPackageOrUserRemoval();
         mInIdleMode = mPowerManager.isDeviceIdleMode();
 
+        if (!mWifiStateMachine.syncInitialize(mWifiStateMachineChannel)) {
+            Log.wtf(TAG, "Failed to initialize WifiStateMachine");
+        }
         mWifiController.start();
 
         // If we are already disabled (could be due to airplane mode), avoid changing persist
@@ -429,18 +527,12 @@
         mWifiStateMachine.handleUserSwitch(userId);
     }
 
-    /**
-     * see {@link android.net.wifi.WifiManager#pingSupplicant()}
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public boolean pingSupplicant() {
-        enforceAccessPermission();
-        if (mWifiStateMachineChannel != null) {
-            return mWifiStateMachine.syncPingSupplicant(mWifiStateMachineChannel);
-        } else {
-            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
-            return false;
-        }
+    public void handleUserUnlock(int userId) {
+        mWifiStateMachine.handleUserUnlock(userId);
+    }
+
+    public void handleUserStop(int userId) {
+        mWifiStateMachine.handleUserStop(userId);
     }
 
     /**
@@ -449,22 +541,38 @@
      *
      * @param settings If null, use default parameter, i.e. full scan.
      * @param workSource If null, all blame is given to the calling uid.
+     * @param packageName Package name of the app that requests wifi scan.
      */
-    public void startScan(ScanSettings settings, WorkSource workSource) {
+    @Override
+    public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
         enforceChangePermission();
+
+        mLog.trace("startScan uid=%").c(Binder.getCallingUid()).flush();
+        // Check and throttle background apps for wifi scan.
+        if (isRequestFromBackground(packageName)) {
+            long lastScanMs = mLastScanTimestamps.getOrDefault(packageName, 0L);
+            long elapsedRealtime = mClock.getElapsedSinceBootMillis();
+
+            if (lastScanMs != 0 && (elapsedRealtime - lastScanMs) < mBackgroundThrottleInterval) {
+                sendFailedScanBroadcast();
+                return;
+            }
+            // Proceed with the scan request and record the time.
+            mLastScanTimestamps.put(packageName, elapsedRealtime);
+        }
         synchronized (this) {
+            if (mWifiScanner == null) {
+                mWifiScanner = mWifiInjector.getWifiScanner();
+            }
             if (mInIdleMode) {
                 // Need to send an immediate scan result broadcast in case the
                 // caller is waiting for a result ..
 
-                // clear calling identity to send broadcast
-                long callingIdentity = Binder.clearCallingIdentity();
-                try {
-                    mWifiStateMachine.sendScanResultsAvailableBroadcast(/* scanSucceeded = */ false);
-                } finally {
-                    // restore calling identity
-                    Binder.restoreCallingIdentity(callingIdentity);
-                }
+                // TODO: investigate if the logic to cancel scans when idle can move to
+                // WifiScanningServiceImpl.  This will 1 - clean up WifiServiceImpl and 2 -
+                // avoid plumbing an awkward path to report a cancelled/failed scan.  This will
+                // be sent directly until b/31398592 is fixed.
+                sendFailedScanBroadcast();
                 mScanPending = true;
                 return;
             }
@@ -489,9 +597,52 @@
                 settings, workSource);
     }
 
-    public String getWpsNfcConfigurationToken(int netId) {
+    // Send a failed scan broadcast to indicate the current scan request failed.
+    private void sendFailedScanBroadcast() {
+        // clear calling identity to send broadcast
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            // restore calling identity
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+
+    }
+
+    // Check if the request comes from background.
+    private boolean isRequestFromBackground(String packageName) {
+        // Requests from system or wifi are not background.
+        if (Binder.getCallingUid() == Process.SYSTEM_UID
+                || Binder.getCallingUid() == Process.WIFI_UID) {
+            return false;
+        }
+        mAppOps.checkPackage(Binder.getCallingUid(), packageName);
+        if (mBackgroundThrottlePackageWhitelist.contains(packageName)) {
+            return false;
+        }
+
+        // getPackageImportance requires PACKAGE_USAGE_STATS permission, so clearing the incoming
+        // identify so the permission check can be done on system process where wifi runs in.
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return mActivityManager.getPackageImportance(packageName)
+                    > BACKGROUND_IMPORTANCE_CUTOFF;
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    @Override
+    public String getCurrentNetworkWpsNfcConfigurationToken() {
         enforceConnectivityInternalPermission();
-        return mWifiStateMachine.syncGetWpsNfcConfigurationToken(netId);
+        mLog.trace("getCurrentNetworkWpsNfcConfigurationToken uid=%")
+                .c(Binder.getCallingUid()).flush();
+        // TODO Add private logging for netId b/33807876
+        return mWifiStateMachine.syncGetCurrentNetworkWpsNfcConfigurationToken();
     }
 
     boolean mInIdleMode;
@@ -513,10 +664,21 @@
         }
         if (doScan) {
             // Someone requested a scan while we were idle; do a full scan now.
-            startScan(null, null);
+            // The package name doesn't matter as the request comes from System UID.
+            startScan(null, null, "");
         }
     }
 
+    private void enforceNetworkSettingsPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS,
+                "WifiService");
+    }
+
+    private void enforceNetworkStackPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.NETWORK_STACK,
+                "WifiService");
+    }
+
     private void enforceAccessPermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
                 "WifiService");
@@ -555,17 +717,39 @@
                 "ConnectivityService");
     }
 
+    private void enforceLocationPermission(String pkgName, int uid) {
+        mWifiPermissionsUtil.enforceLocationPermission(pkgName, uid);
+    }
+
+    private boolean checkNetworkSettingsPermission() {
+        int result = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.NETWORK_SETTINGS);
+        return result == PackageManager.PERMISSION_GRANTED;
+    }
+
     /**
      * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
      * @param enable {@code true} to enable, {@code false} to disable.
      * @return {@code true} if the enable/disable operation was
      *         started or is already in the queue.
      */
+    @Override
     public synchronized boolean setWifiEnabled(String packageName, boolean enable)
             throws RemoteException {
         enforceChangePermission();
         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid());
+                    + ", uid=" + Binder.getCallingUid() + ", package=" + packageName);
+        mLog.trace("setWifiEnabled package=% uid=% enable=%").c(packageName)
+                .c(Binder.getCallingUid()).c(enable).flush();
+
+        // If SoftAp is enabled, only Settings is allowed to toggle wifi
+        boolean apEnabled =
+                mWifiStateMachine.syncGetWifiApState() != WifiManager.WIFI_AP_STATE_DISABLED;
+        boolean isFromSettings = checkNetworkSettingsPermission();
+        if (apEnabled && !isFromSettings) {
+            mLog.trace("setWifiEnabled SoftAp not disabled: only Settings can enable wifi").flush();
+            return false;
+        }
 
         /*
         * Caller might not have WRITE_SECURE_SETTINGS,
@@ -613,8 +797,10 @@
      *         {@link WifiManager#WIFI_STATE_ENABLING},
      *         {@link WifiManager#WIFI_STATE_UNKNOWN}
      */
+    @Override
     public int getWifiEnabledState() {
         enforceAccessPermission();
+        mLog.trace("getWifiEnabledState uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.syncGetWifiState();
     }
 
@@ -624,15 +810,21 @@
      *        part of WifiConfiguration
      * @param enabled true to enable and false to disable
      */
+    @Override
     public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
-        ConnectivityManager.enforceTetherChangePermission(mContext);
+        mWifiPermissionsUtil.enforceTetherChangePermission(mContext);
+
+        mLog.trace("setWifiApEnabled uid=% enable=%").c(Binder.getCallingUid()).c(enabled).flush();
+
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
             throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
         }
         // null wifiConfig is a meaningful input for CMD_SET_AP
         if (wifiConfig == null || isValid(wifiConfig)) {
-            mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
+            int mode = WifiManager.IFACE_IP_MODE_UNSPECIFIED;
+            SoftApModeConfiguration softApConfig = new SoftApModeConfiguration(mode, wifiConfig);
+            mWifiController.sendMessage(CMD_SET_AP, enabled ? 1 : 0, 0, softApConfig);
         } else {
             Slog.e(TAG, "Invalid WifiConfiguration");
         }
@@ -646,45 +838,501 @@
      *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
      *         {@link WifiManager#WIFI_AP_STATE_FAILED}
      */
+    @Override
     public int getWifiApEnabledState() {
         enforceAccessPermission();
+        mLog.trace("getWifiApEnabledState uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.syncGetWifiApState();
     }
 
     /**
+     * see {@link android.net.wifi.WifiManager#updateInterfaceIpState(String, int)}
+     *
+     * The possible modes include: {@link WifiManager#IFACE_IP_MODE_TETHERED},
+     *                             {@link WifiManager#IFACE_IP_MODE_LOCAL_ONLY},
+     *                             {@link WifiManager#IFACE_IP_MODE_CONFIGURATION_ERROR}
+     *
+     * @param ifaceName String name of the updated interface
+     * @param mode new operating mode of the interface
+     *
+     * @throws SecurityException if the caller does not have permission to call update
+     */
+    @Override
+    public void updateInterfaceIpState(String ifaceName, int mode) {
+        // NETWORK_STACK is a signature only permission.
+        enforceNetworkStackPermission();
+
+        // hand off the work to our handler thread
+        mClientHandler.post(() -> {
+            updateInterfaceIpStateInternal(ifaceName, mode);
+        });
+    }
+
+    private void updateInterfaceIpStateInternal(String ifaceName, int mode) {
+        // update interface IP state related to tethering and hotspot
+        synchronized (mLocalOnlyHotspotRequests) {
+            // update the mode tracker here - we clear out state below
+            Integer previousMode = WifiManager.IFACE_IP_MODE_UNSPECIFIED;
+            if (ifaceName != null) {
+                previousMode = mIfaceIpModes.put(ifaceName, mode);
+            }
+            Slog.d(TAG, "updateInterfaceIpState: ifaceName=" + ifaceName + " mode=" + mode
+                    + " previous mode= " + previousMode);
+
+            switch (mode) {
+                case WifiManager.IFACE_IP_MODE_LOCAL_ONLY:
+                    // first make sure we have registered requests..  otherwise clean up
+                    if (mLocalOnlyHotspotRequests.isEmpty()) {
+                        // we don't have requests...  stop the hotspot
+                        stopSoftAp();
+                        updateInterfaceIpStateInternal(null, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+                        return;
+                    }
+                    // LOHS is ready to go!  Call our registered requestors!
+                    sendHotspotStartedMessageToAllLOHSRequestInfoEntriesLocked();
+                    break;
+                case WifiManager.IFACE_IP_MODE_TETHERED:
+                    // we have tethered an interface. we don't really act on this now other than if
+                    // we have LOHS requests, and this is an issue.  return incompatible mode for
+                    // onFailed for the registered requestors since this can result from a race
+                    // between a tether request and a hotspot request (tethering wins).
+                    sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(
+                            LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE);
+                    break;
+                case WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR:
+                    // there was an error setting up the hotspot...  trigger onFailed for the
+                    // registered LOHS requestors
+                    sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(
+                            LocalOnlyHotspotCallback.ERROR_GENERIC);
+                    updateInterfaceIpStateInternal(null, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+                    break;
+                case WifiManager.IFACE_IP_MODE_UNSPECIFIED:
+                    if (ifaceName == null) {
+                        // interface name is null, this is due to softap teardown.  clear all
+                        // entries for now.
+                        // TODO: Deal with individual interfaces when we receive updates for them
+                        mIfaceIpModes.clear();
+                        return;
+                    }
+                    break;
+                default:
+                    mLog.trace("updateInterfaceIpStateInternal: unknown mode %").c(mode).flush();
+            }
+        }
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#startSoftAp(WifiConfiguration)}
+     * @param wifiConfig SSID, security and channel details as part of WifiConfiguration
+     * @return {@code true} if softap start was triggered
+     * @throws SecurityException if the caller does not have permission to start softap
+     */
+    @Override
+    public boolean startSoftAp(WifiConfiguration wifiConfig) {
+        // NETWORK_STACK is a signature only permission.
+        enforceNetworkStackPermission();
+
+        mLog.trace("startSoftAp uid=%").c(Binder.getCallingUid()).flush();
+
+        synchronized (mLocalOnlyHotspotRequests) {
+            // If a tethering request comes in while we have LOHS running (or requested), call stop
+            // for softap mode and restart softap with the tethering config.
+            if (!mLocalOnlyHotspotRequests.isEmpty()) {
+                stopSoftApInternal();
+            }
+
+            return startSoftApInternal(wifiConfig, WifiManager.IFACE_IP_MODE_TETHERED);
+        }
+    }
+
+    /**
+     * Internal method to start softap mode. Callers of this method should have already checked
+     * proper permissions beyond the NetworkStack permission.
+     */
+    private boolean startSoftApInternal(WifiConfiguration wifiConfig, int mode) {
+        mLog.trace("startSoftApInternal uid=% mode=%")
+                .c(Binder.getCallingUid()).c(mode).flush();
+
+        // null wifiConfig is a meaningful input for CMD_SET_AP
+        if (wifiConfig == null || isValid(wifiConfig)) {
+            SoftApModeConfiguration softApConfig = new SoftApModeConfiguration(mode, wifiConfig);
+            mWifiController.sendMessage(CMD_SET_AP, 1, 0, softApConfig);
+            return true;
+        }
+        Slog.e(TAG, "Invalid WifiConfiguration");
+        return false;
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#stopSoftAp()}
+     * @return {@code true} if softap stop was triggered
+     * @throws SecurityException if the caller does not have permission to stop softap
+     */
+    @Override
+    public boolean stopSoftAp() {
+        // NETWORK_STACK is a signature only permission.
+        enforceNetworkStackPermission();
+
+        // only permitted callers are allowed to this point - they must have gone through
+        // connectivity service since this method is protected with the NETWORK_STACK PERMISSION
+
+        mLog.trace("stopSoftAp uid=%").c(Binder.getCallingUid()).flush();
+
+        synchronized (mLocalOnlyHotspotRequests) {
+            // If a tethering request comes in while we have LOHS running (or requested), call stop
+            // for softap mode and restart softap with the tethering config.
+            if (!mLocalOnlyHotspotRequests.isEmpty()) {
+                mLog.trace("Call to stop Tethering while LOHS is active,"
+                        + " Registered LOHS callers will be updated when softap stopped.");
+            }
+
+            return stopSoftApInternal();
+        }
+    }
+
+    /**
+     * Internal method to stop softap mode.  Callers of this method should have already checked
+     * proper permissions beyond the NetworkStack permission.
+     */
+    private boolean stopSoftApInternal() {
+        mLog.trace("stopSoftApInternal uid=%").c(Binder.getCallingUid()).flush();
+
+        mWifiController.sendMessage(CMD_SET_AP, 0, 0);
+        return true;
+    }
+
+    /**
+     * Private method to handle SoftAp state changes
+     */
+    private void handleWifiApStateChange(
+            int currentState, int previousState, int errorCode, String ifaceName, int mode) {
+        // The AP state update from WifiStateMachine for softap
+        Slog.d(TAG, "handleWifiApStateChange: currentState=" + currentState
+                + " previousState=" + previousState + " errorCode= " + errorCode
+                + " ifaceName=" + ifaceName + " mode=" + mode);
+
+        // check if we have a failure - since it is possible (worst case scenario where
+        // WifiController and WifiStateMachine are out of sync wrt modes) to get two FAILED
+        // notifications in a row, we need to handle this first.
+        if (currentState == WIFI_AP_STATE_FAILED) {
+            // update registered LOHS callbacks if we see a failure
+            synchronized (mLocalOnlyHotspotRequests) {
+                int errorToReport = ERROR_GENERIC;
+                if (errorCode == SAP_START_FAILURE_NO_CHANNEL) {
+                    errorToReport = ERROR_NO_CHANNEL;
+                }
+                // holding the required lock: send message to requestors and clear the list
+                sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(
+                        errorToReport);
+                // also need to clear interface ip state - send null for now since we don't know
+                // what interface (and we have one anyway)
+                updateInterfaceIpStateInternal(null, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+            }
+            return;
+        }
+
+        if (currentState == WIFI_AP_STATE_DISABLING || currentState == WIFI_AP_STATE_DISABLED) {
+            // softap is shutting down or is down...  let requestors know via the onStopped call
+            synchronized (mLocalOnlyHotspotRequests) {
+                // if we are currently in hotspot mode, then trigger onStopped for registered
+                // requestors, otherwise something odd happened and we should clear state
+                if (mIfaceIpModes.contains(WifiManager.IFACE_IP_MODE_LOCAL_ONLY)) {
+                    // holding the required lock: send message to requestors and clear the list
+                    sendHotspotStoppedMessageToAllLOHSRequestInfoEntriesLocked();
+                } else {
+                    // LOHS not active: report an error (still holding the required lock)
+                    sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(ERROR_GENERIC);
+                }
+                // also clear interface ip state - send null for now since we don't know what
+                // interface (and we only have one anyway)
+                updateInterfaceIpState(null, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+            }
+            return;
+        }
+
+        // remaining states are enabling or enabled...  those are not used for the callbacks
+    }
+
+    /**
+     * Helper method to send a HOTSPOT_FAILED message to all registered LocalOnlyHotspotRequest
+     * callers and clear the registrations.
+     *
+     * Callers should already hold the mLocalOnlyHotspotRequests lock.
+     */
+    private void sendHotspotFailedMessageToAllLOHSRequestInfoEntriesLocked(int arg1) {
+        for (LocalOnlyHotspotRequestInfo requestor : mLocalOnlyHotspotRequests.values()) {
+            try {
+                requestor.sendHotspotFailedMessage(arg1);
+                requestor.unlinkDeathRecipient();
+            } catch (RemoteException e) {
+                // This will be cleaned up by binder death handling
+            }
+        }
+
+        // Since all callers were notified, now clear the registrations.
+        mLocalOnlyHotspotRequests.clear();
+    }
+
+    /**
+     * Helper method to send a HOTSPOT_STOPPED message to all registered LocalOnlyHotspotRequest
+     * callers and clear the registrations.
+     *
+     * Callers should already hold the mLocalOnlyHotspotRequests lock.
+     */
+    private void sendHotspotStoppedMessageToAllLOHSRequestInfoEntriesLocked() {
+        for (LocalOnlyHotspotRequestInfo requestor : mLocalOnlyHotspotRequests.values()) {
+            try {
+                requestor.sendHotspotStoppedMessage();
+                requestor.unlinkDeathRecipient();
+            } catch (RemoteException e) {
+                // This will be cleaned up by binder death handling
+            }
+        }
+
+        // Since all callers were notified, now clear the registrations.
+        mLocalOnlyHotspotRequests.clear();
+    }
+
+    /**
+     * Helper method to send a HOTSPOT_STARTED message to all registered LocalOnlyHotspotRequest
+     * callers.
+     *
+     * Callers should already hold the mLocalOnlyHotspotRequests lock.
+     */
+    private void sendHotspotStartedMessageToAllLOHSRequestInfoEntriesLocked() {
+        for (LocalOnlyHotspotRequestInfo requestor : mLocalOnlyHotspotRequests.values()) {
+            try {
+                requestor.sendHotspotStartedMessage(mLocalOnlyHotspotConfig);
+            } catch (RemoteException e) {
+                // This will be cleaned up by binder death handling
+            }
+        }
+    }
+
+    /**
+     * Temporary method used for testing while startLocalOnlyHotspot is not fully implemented.  This
+     * method allows unit tests to register callbacks directly for testing mechanisms triggered by
+     * softap mode changes.
+     */
+    @VisibleForTesting
+    void registerLOHSForTest(int pid, LocalOnlyHotspotRequestInfo request) {
+        mLocalOnlyHotspotRequests.put(pid, request);
+    }
+
+    /**
+     * Method to start LocalOnlyHotspot.  In this method, permissions, settings and modes are
+     * checked to verify that we can enter softapmode.  This method returns
+     * {@link LocalOnlyHotspotCallback#REQUEST_REGISTERED} if we will attempt to start, otherwise,
+     * possible startup erros may include tethering being disallowed failure reason {@link
+     * LocalOnlyHotspotCallback#ERROR_TETHERING_DISALLOWED} or an incompatible mode failure reason
+     * {@link LocalOnlyHotspotCallback#ERROR_INCOMPATIBLE_MODE}.
+     *
+     * see {@link WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}
+     *
+     * @param messenger Messenger to send messages to the corresponding WifiManager.
+     * @param binder IBinder instance to allow cleanup if the app dies
+     * @param packageName String name of the calling package
+     *
+     * @return int return code for attempt to start LocalOnlyHotspot.
+     *
+     * @throws SecurityException if the caller does not have permission to start a Local Only
+     * Hotspot.
+     * @throws IllegalStateException if the caller attempts to start the LocalOnlyHotspot while they
+     * have an outstanding request.
+     */
+    @Override
+    public int startLocalOnlyHotspot(Messenger messenger, IBinder binder, String packageName) {
+        // first check if the caller has permission to start a local only hotspot
+        // need to check for WIFI_STATE_CHANGE and location permission
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
+
+        enforceChangePermission();
+        enforceLocationPermission(packageName, uid);
+        // also need to verify that Locations services are enabled.
+        if (mSettingsStore.getLocationModeSetting(mContext) == Settings.Secure.LOCATION_MODE_OFF) {
+            throw new SecurityException("Location mode is not enabled.");
+        }
+
+        // verify that tethering is not disabled
+        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
+            return LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED;
+        }
+
+        // the app should be in the foreground
+        try {
+            if (!mFrameworkFacade.isAppForeground(uid)) {
+                return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
+            }
+        } catch (RemoteException e) {
+            mLog.trace("RemoteException during isAppForeground when calling startLOHS");
+            return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
+        }
+
+        mLog.trace("startLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush();
+
+        synchronized (mLocalOnlyHotspotRequests) {
+            // check if we are currently tethering
+            if (mIfaceIpModes.contains(WifiManager.IFACE_IP_MODE_TETHERED)) {
+                // Tethering is enabled, cannot start LocalOnlyHotspot
+                mLog.trace("Cannot start localOnlyHotspot when WiFi Tethering is active.");
+                return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
+            }
+
+            // does this caller already have a request?
+            LocalOnlyHotspotRequestInfo request = mLocalOnlyHotspotRequests.get(pid);
+            if (request != null) {
+                mLog.trace("caller already has an active request");
+                throw new IllegalStateException(
+                        "Caller already has an active LocalOnlyHotspot request");
+            }
+
+            // now create the new LOHS request info object
+            request = new LocalOnlyHotspotRequestInfo(binder, messenger,
+                    new LocalOnlyRequestorCallback());
+
+            // check current operating state and take action if needed
+            if (mIfaceIpModes.contains(WifiManager.IFACE_IP_MODE_LOCAL_ONLY)) {
+                // LOHS is already active, send out what is running
+                try {
+                    mLog.trace("LOHS already up, trigger onStarted callback");
+                    request.sendHotspotStartedMessage(mLocalOnlyHotspotConfig);
+                } catch (RemoteException e) {
+                    return LocalOnlyHotspotCallback.ERROR_GENERIC;
+                }
+            } else if (mLocalOnlyHotspotRequests.isEmpty()) {
+                // this is the first request, then set up our config and start LOHS
+                mLocalOnlyHotspotConfig =
+                        WifiApConfigStore.generateLocalOnlyHotspotConfig(mContext);
+                startSoftApInternal(mLocalOnlyHotspotConfig, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+            }
+
+            mLocalOnlyHotspotRequests.put(pid, request);
+            return LocalOnlyHotspotCallback.REQUEST_REGISTERED;
+        }
+    }
+
+    /**
+     * see {@link WifiManager#stopLocalOnlyHotspot()}
+     *
+     * @throws SecurityException if the caller does not have permission to stop a Local Only
+     * Hotspot.
+     */
+    @Override
+    public void stopLocalOnlyHotspot() {
+        // first check if the caller has permission to stop a local only hotspot
+        enforceChangePermission();
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
+
+        mLog.trace("stopLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush();
+
+        synchronized (mLocalOnlyHotspotRequests) {
+            // was the caller already registered?  check request tracker - return false if not
+            LocalOnlyHotspotRequestInfo requestInfo = mLocalOnlyHotspotRequests.get(pid);
+            if (requestInfo == null) {
+                return;
+            }
+            requestInfo.unlinkDeathRecipient();
+            unregisterCallingAppAndStopLocalOnlyHotspot(requestInfo);
+        } // end synchronized
+    }
+
+    /**
+     * Helper method to unregister LocalOnlyHotspot requestors and stop the hotspot if needed.
+     */
+    private void unregisterCallingAppAndStopLocalOnlyHotspot(LocalOnlyHotspotRequestInfo request) {
+        mLog.trace("unregisterCallingAppAndStopLocalOnlyHotspot pid=%").c(request.getPid()).flush();
+
+        synchronized (mLocalOnlyHotspotRequests) {
+            if (mLocalOnlyHotspotRequests.remove(request.getPid()) == null) {
+                mLog.trace("LocalOnlyHotspotRequestInfo not found to remove");
+                return;
+            }
+
+            if (mLocalOnlyHotspotRequests.isEmpty()) {
+                mLocalOnlyHotspotConfig = null;
+                updateInterfaceIpStateInternal(null, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+                // if that was the last caller, then call stopSoftAp as WifiService
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    stopSoftApInternal();
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+    }
+
+    /**
+     * see {@link WifiManager#watchLocalOnlyHotspot(LocalOnlyHotspotObserver)}
+     *
+     * This call requires the android.permission.NETWORK_SETTINGS permission.
+     *
+     * @param messenger Messenger to send messages to the corresponding WifiManager.
+     * @param binder IBinder instance to allow cleanup if the app dies
+     *
+     * @throws SecurityException if the caller does not have permission to watch Local Only Hotspot
+     * status updates.
+     * @throws IllegalStateException if the caller attempts to watch LocalOnlyHotspot updates with
+     * an existing subscription.
+     */
+    @Override
+    public void startWatchLocalOnlyHotspot(Messenger messenger, IBinder binder) {
+        final String packageName = mContext.getOpPackageName();
+
+        // NETWORK_SETTINGS is a signature only permission.
+        enforceNetworkSettingsPermission();
+
+        throw new UnsupportedOperationException("LocalOnlyHotspot is still in development");
+    }
+
+    /**
+     * see {@link WifiManager#unregisterLocalOnlyHotspotObserver()}
+     */
+    @Override
+    public void stopWatchLocalOnlyHotspot() {
+        // NETWORK_STACK is a signature only permission.
+        enforceNetworkSettingsPermission();
+        throw new UnsupportedOperationException("LocalOnlyHotspot is still in development");
+    }
+
+    /**
      * see {@link WifiManager#getWifiApConfiguration()}
      * @return soft access point configuration
+     * @throws SecurityException if the caller does not have permission to retrieve the softap
+     * config
      */
+    @Override
     public WifiConfiguration getWifiApConfiguration() {
         enforceAccessPermission();
+        int uid = Binder.getCallingUid();
+        // only allow Settings UI to get the saved SoftApConfig
+        if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) {
+            // random apps should not be allowed to read the user specified config
+            throw new SecurityException("App not allowed to read or update stored WiFi Ap config "
+                    + "(uid = " + uid + ")");
+        }
+        mLog.trace("getWifiApConfiguration uid=%").c(uid).flush();
         return mWifiStateMachine.syncGetWifiApConfiguration();
     }
 
     /**
-     * see {@link WifiManager#buildWifiConfig()}
-     * @return a WifiConfiguration.
-     */
-    public WifiConfiguration buildWifiConfig(String uriString, String mimeType, byte[] data) {
-        if (mimeType.equals(ConfigBuilder.WifiConfigType)) {
-            try {
-                return ConfigBuilder.buildConfig(uriString, data, mContext);
-            }
-            catch (IOException | GeneralSecurityException | SAXException e) {
-                Log.e(TAG, "Failed to parse wi-fi configuration: " + e);
-            }
-        }
-        else {
-            Log.i(TAG, "Unknown wi-fi config type: " + mimeType);
-        }
-        return null;
-    }
-
-    /**
      * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
      * @param wifiConfig WifiConfiguration details for soft access point
+     * @throws SecurityException if the caller does not have permission to write the sotap config
      */
+    @Override
     public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
         enforceChangePermission();
+        int uid = Binder.getCallingUid();
+        // only allow Settings UI to write the stored SoftApConfig
+        if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) {
+            // random apps should not be allowed to read the user specified config
+            throw new SecurityException("App not allowed to read or update stored WiFi AP config "
+                    + "(uid = " + uid + ")");
+        }
+        mLog.trace("setWifiApConfiguration uid=%").c(uid).flush();
         if (wifiConfig == null)
             return;
         if (isValid(wifiConfig)) {
@@ -695,44 +1343,52 @@
     }
 
     /**
-     * @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();
+        mLog.trace("isScanAlwaysAvailable uid=%").c(Binder.getCallingUid()).flush();
         return mSettingsStore.isScanAlwaysAvailable();
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#disconnect()}
      */
+    @Override
     public void disconnect() {
         enforceChangePermission();
+        mLog.trace("disconnect uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disconnectCommand();
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#reconnect()}
      */
+    @Override
     public void reconnect() {
         enforceChangePermission();
+        mLog.trace("reconnect uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.reconnectCommand();
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#reassociate()}
      */
+    @Override
     public void reassociate() {
         enforceChangePermission();
+        mLog.trace("reassociate uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.reassociateCommand();
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#getSupportedFeatures}
      */
+    @Override
     public int getSupportedFeatures() {
         enforceAccessPermission();
+        mLog.trace("getSupportedFeatures uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncGetSupportedFeatures(mWifiStateMachineChannel);
         } else {
@@ -744,6 +1400,7 @@
     @Override
     public void requestActivityInfo(ResultReceiver result) {
         Bundle bundle = new Bundle();
+        mLog.trace("requestActivityInfo uid=%").c(Binder.getCallingUid()).flush();
         bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, reportActivityInfo());
         result.send(0, bundle);
     }
@@ -751,8 +1408,10 @@
     /**
      * see {@link android.net.wifi.WifiManager#getControllerActivityEnergyInfo(int)}
      */
+    @Override
     public WifiActivityEnergyInfo reportActivityInfo() {
         enforceAccessPermission();
+        mLog.trace("reportActivityInfo uid=%").c(Binder.getCallingUid()).flush();
         if ((getSupportedFeatures() & WifiManager.WIFI_FEATURE_LINK_LAYER_STATS) == 0) {
             return null;
         }
@@ -803,7 +1462,7 @@
                 }
 
                 // Convert the LinkLayerStats into EnergyActivity
-                energyInfo = new WifiActivityEnergyInfo(SystemClock.elapsedRealtime(),
+                energyInfo = new WifiActivityEnergyInfo(mClock.getElapsedSinceBootMillis(),
                         WifiActivityEnergyInfo.STACK_STATE_STATE_IDLE, stats.tx_time,
                         txTimePerLevel, stats.rx_time, rxIdleTime, energyUsed);
             }
@@ -822,54 +1481,57 @@
      * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
      * @return the list of configured networks
      */
-    public List<WifiConfiguration> getConfiguredNetworks() {
+    @Override
+    public ParceledListSlice<WifiConfiguration> getConfiguredNetworks() {
         enforceAccessPermission();
+        mLog.trace("getConfiguredNetworks uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
-            return mWifiStateMachine.syncGetConfiguredNetworks(Binder.getCallingUid(),
-                    mWifiStateMachineChannel);
+            List<WifiConfiguration> configs = mWifiStateMachine.syncGetConfiguredNetworks(
+                    Binder.getCallingUid(), mWifiStateMachineChannel);
+            if (configs != null) {
+                return new ParceledListSlice<WifiConfiguration>(configs);
+            }
         } else {
             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
-            return null;
         }
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#getHasCarrierNetworks()}
-     * @return if Carrier Networks have been configured
-     */
-    public boolean hasCarrierConfiguredNetworks() {
-        enforceAccessPermission();
-        if (mWifiStateMachineChannel != null) {
-            return mWifiStateMachine.syncHasCarrierConfiguredNetworks(Binder.getCallingUid(),
-                    mWifiStateMachineChannel);
-        } else {
-            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
-            return false;
-        }
+        return null;
     }
 
     /**
      * see {@link android.net.wifi.WifiManager#getPrivilegedConfiguredNetworks()}
      * @return the list of configured networks with real preSharedKey
      */
-    public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
+    @Override
+    public ParceledListSlice<WifiConfiguration> getPrivilegedConfiguredNetworks() {
         enforceReadCredentialPermission();
         enforceAccessPermission();
+        mLog.trace("getPrivilegedConfiguredNetworks uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
-            return mWifiStateMachine.syncGetPrivilegedConfiguredNetwork(mWifiStateMachineChannel);
+            List<WifiConfiguration> configs =
+                    mWifiStateMachine.syncGetPrivilegedConfiguredNetwork(mWifiStateMachineChannel);
+            if (configs != null) {
+                return new ParceledListSlice<WifiConfiguration>(configs);
+            }
         } else {
             Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
-            return null;
         }
+        return null;
     }
 
     /**
-     * Returns a WifiConfiguration matching this ScanResult
+     * Returns a WifiConfiguration for a Passpoint network matching this ScanResult.
+     *
      * @param scanResult scanResult that represents the BSSID
      * @return {@link WifiConfiguration} that matches this BSSID or null
      */
+    @Override
     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
         enforceAccessPermission();
+        mLog.trace("getMatchingWifiConfig uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
         return mWifiStateMachine.syncGetMatchingWifiConfig(scanResult, mWifiStateMachineChannel);
     }
 
@@ -878,35 +1540,36 @@
      * @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)) {
+        mLog.trace("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
 
-            WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
-
-            if (config.isPasspoint() &&
-                    (enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS ||
-                            enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS)) {
-                if (config.updateIdentifier != null) {
-                    enforceAccessPermission();
-                }
-                else {
-                    try {
-                        verifyCert(enterpriseConfig.getCaCertificate());
-                    } catch (CertPathValidatorException cpve) {
-                        Slog.e(TAG, "CA Cert " +
-                                enterpriseConfig.getCaCertificate().getSubjectX500Principal() +
-                                " untrusted: " + cpve.getMessage());
-                        return -1;
-                    } catch (GeneralSecurityException | IOException e) {
-                        Slog.e(TAG, "Failed to verify certificate" +
-                                enterpriseConfig.getCaCertificate().getSubjectX500Principal() +
-                                ": " + e);
-                        return -1;
-                    }
-                }
+        // Previously, this API is overloaded for installing Passpoint profiles.  Now
+        // that we have a dedicated API for doing it, redirect the call to the dedicated API.
+        if (config.isPasspoint()) {
+            PasspointConfiguration passpointConfig =
+                    PasspointProvider.convertFromWifiConfig(config);
+            if (passpointConfig.getCredential() == null) {
+                Slog.e(TAG, "Missing credential for Passpoint profile");
+                return -1;
             }
+            // Copy over certificates and keys.
+            passpointConfig.getCredential().setCaCertificate(
+                    config.enterpriseConfig.getCaCertificate());
+            passpointConfig.getCredential().setClientCertificateChain(
+                    config.enterpriseConfig.getClientCertificateChain());
+            passpointConfig.getCredential().setClientPrivateKey(
+                    config.enterpriseConfig.getClientPrivateKey());
+            if (!addOrUpdatePasspointConfiguration(passpointConfig)) {
+                Slog.e(TAG, "Failed to add Passpoint profile");
+                return -1;
+            }
+            // There is no network ID associated with a Passpoint profile.
+            return 0;
+        }
 
+        if (isValid(config)) {
             //TODO: pass the Uid the WifiStateMachine as a message parameter
             Slog.i("addOrUpdateNetwork", " uid = " + Integer.toString(Binder.getCallingUid())
                     + " SSID " + config.SSID
@@ -948,9 +1611,11 @@
      * to the supplicant
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
-
+        mLog.trace("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
+        // TODO Add private logging for netId b/33807876
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId);
         } else {
@@ -966,8 +1631,14 @@
      * @param disableOthers if true, disable all other networks.
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean enableNetwork(int netId, boolean disableOthers) {
         enforceChangePermission();
+        // TODO b/33807876 Log netId
+        mLog.trace("enableNetwork uid=% disableOthers=%")
+                .c(Binder.getCallingUid())
+                .c(disableOthers).flush();
+
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, netId,
                     disableOthers);
@@ -983,8 +1654,12 @@
      * to the supplicant
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
+        // TODO b/33807876 Log netId
+        mLog.trace("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
+
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId);
         } else {
@@ -997,8 +1672,10 @@
      * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
+    @Override
     public WifiInfo getConnectionInfo() {
         enforceAccessPermission();
+        mLog.trace("getConnectionInfo uid=%").c(Binder.getCallingUid()).flush();
         /*
          * Make sure we have the latest information, by sending
          * a status request to the supplicant.
@@ -1011,54 +1688,76 @@
      * a list of {@link ScanResult} objects.
      * @return the list of results
      */
+    @Override
     public List<ScanResult> getScanResults(String callingPackage) {
         enforceAccessPermission();
-        int userId = UserHandle.getCallingUserId();
         int uid = Binder.getCallingUid();
-        boolean canReadPeerMacAddresses = checkPeersMacAddress();
-        boolean isActiveNetworkScorer =
-                NetworkScorerAppManager.isCallerActiveScorer(mContext, uid);
-        boolean hasInteractUsersFull = checkInteractAcrossUsersFull();
         long ident = Binder.clearCallingIdentity();
         try {
-            if (!canReadPeerMacAddresses && !isActiveNetworkScorer
-                    && !isLocationEnabled(callingPackage)) {
+            if (!mWifiPermissionsUtil.canAccessScanResults(callingPackage,
+                      uid, Build.VERSION_CODES.M)) {
                 return new ArrayList<ScanResult>();
             }
-            if (!canReadPeerMacAddresses && !isActiveNetworkScorer
-                    && !checkCallerCanAccessScanResults(callingPackage, uid)) {
-                return new ArrayList<ScanResult>();
+            if (mWifiScanner == null) {
+                mWifiScanner = mWifiInjector.getWifiScanner();
             }
-            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
-                    != AppOpsManager.MODE_ALLOWED) {
-                return new ArrayList<ScanResult>();
-            }
-            if (!isCurrentProfile(userId) && !hasInteractUsersFull) {
-                return new ArrayList<ScanResult>();
-            }
-            return mWifiStateMachine.syncGetScanResultsList();
+            return mWifiScanner.getSingleScanResults();
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
     /**
-     * Add a Hotspot 2.0 release 2 Management Object
-     * @param mo The MO in XML form
-     * @return -1 for failure
+     * Add or update a Passpoint configuration.
+     *
+     * @param config The Passpoint configuration to be added
+     * @return true on success or false on failure
      */
-    public int addPasspointManagementObject(String mo) {
-        return mWifiStateMachine.syncAddPasspointManagementObject(mWifiStateMachineChannel, mo);
+    @Override
+    public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
+        enforceChangePermission();
+        mLog.trace("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        return mWifiStateMachine.syncAddOrUpdatePasspointConfig(mWifiStateMachineChannel, config,
+                Binder.getCallingUid());
     }
 
     /**
-     * Modify a Hotspot 2.0 release 2 Management Object
-     * @param fqdn The FQDN of the service provider
-     * @param mos A List of MO definitions to be updated
-     * @return the number of nodes updated, or -1 for failure
+     * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name).
+     *
+     * @param fqdn The FQDN of the Passpoint configuration to be removed
+     * @return true on success or false on failure
      */
-    public int modifyPasspointManagementObject(String fqdn, List<PasspointManagementObjectDefinition> mos) {
-        return mWifiStateMachine.syncModifyPasspointManagementObject(mWifiStateMachineChannel, fqdn, mos);
+    @Override
+    public boolean removePasspointConfiguration(String fqdn) {
+        enforceChangePermission();
+        mLog.trace("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        return mWifiStateMachine.syncRemovePasspointConfig(mWifiStateMachineChannel, fqdn);
+    }
+
+    /**
+     * Return the list of the installed Passpoint configurations.
+     *
+     * An empty list will be returned when no configuration is installed.
+     *
+     * @return A list of {@link PasspointConfiguration}
+     */
+    @Override
+    public List<PasspointConfiguration> getPasspointConfigurations() {
+        enforceAccessPermission();
+        mLog.trace("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        return mWifiStateMachine.syncGetPasspointConfigs(mWifiStateMachineChannel);
     }
 
     /**
@@ -1066,7 +1765,14 @@
      * @param bssid The BSSID of the AP
      * @param fileName Icon file name
      */
+    @Override
     public void queryPasspointIcon(long bssid, String fileName) {
+        enforceAccessPermission();
+        mLog.trace("queryPasspointIcon uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_wifi_hotspot2_enabled)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
         mWifiStateMachine.syncQueryPasspointIcon(mWifiStateMachineChannel, bssid, fileName);
     }
 
@@ -1075,7 +1781,9 @@
      * @param fqdn FQDN of the SP
      * @return ordinal [HomeProvider, RoamingProvider, Incomplete, None, Declined]
      */
+    @Override
     public int matchProviderWithCurrentNetwork(String fqdn) {
+        mLog.trace("matchProviderWithCurrentNetwork uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.matchProviderWithCurrentNetwork(mWifiStateMachineChannel, fqdn);
     }
 
@@ -1084,62 +1792,22 @@
      * @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) {
+        mLog.trace("deauthenticateNetwork uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.deauthenticateNetwork(mWifiStateMachineChannel, holdoff, ess);
     }
 
-    private boolean isLocationEnabled(String callingPackage) {
-        boolean legacyForegroundApp = !isMApp(mContext, callingPackage)
-                && isForegroundApp(callingPackage);
-        return legacyForegroundApp || Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF)
-                != Settings.Secure.LOCATION_MODE_OFF;
-    }
-
-    /**
-     * Returns true if the caller holds INTERACT_ACROSS_USERS_FULL.
-     */
-    private boolean checkInteractAcrossUsersFull() {
-        return mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    /**
-     * Returns true if the caller holds PEERS_MAC_ADDRESS.
-     */
-    private boolean checkPeersMacAddress() {
-        return mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.PEERS_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
-    }
-
-    /**
-     * Returns true if the calling user is the current one or a profile of the
-     * current user..
-     */
-    private boolean isCurrentProfile(int userId) {
-        int currentUser = ActivityManager.getCurrentUser();
-        if (userId == currentUser) {
-            return true;
-        }
-        List<UserInfo> profiles = mUserManager.getProfiles(currentUser);
-        for (UserInfo user : profiles) {
-            if (userId == user.id) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Tell the supplicant to persist the current list of configured networks.
      * @return {@code true} if the operation succeeded
      *
      * TODO: deprecate this
      */
+    @Override
     public boolean saveConfiguration() {
-        boolean result = true;
         enforceChangePermission();
+        mLog.trace("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
         } else {
@@ -1157,21 +1825,15 @@
      * 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);
         enforceConnectivityInternalPermission();
+        mLog.trace("setCountryCode uid=%").c(Binder.getCallingUid()).flush();
         final long token = Binder.clearCallingIdentity();
-        try {
-            if (mCountryCode.setCountryCode(countryCode, persist) && persist) {
-                // Save this country code to persistent storage
-                mFacade.setStringSetting(mContext,
-                        Settings.Global.WIFI_COUNTRY_CODE,
-                        countryCode);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        mCountryCode.setCountryCode(countryCode);
+        Binder.restoreCallingIdentity(token);
     }
 
      /**
@@ -1180,44 +1842,18 @@
      * not.
      * Returns null when there is no country code available.
      */
+    @Override
     public String getCountryCode() {
         enforceConnectivityInternalPermission();
+        mLog.trace("getCountryCode uid=%").c(Binder.getCallingUid()).flush();
         String country = mCountryCode.getCountryCode();
         return country;
     }
-    /**
-     * Set the operational frequency band
-     * @param band One of
-     *     {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
-     *     {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
-     *     {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
-     * @param persist {@code true} if the setting should be remembered.
-     *
-     */
-    public void setFrequencyBand(int band, boolean persist) {
-        enforceChangePermission();
-        if (!isDualBandSupported()) return;
-        Slog.i(TAG, "WifiService trying to set frequency band to " + band +
-                " with persist set to " + persist);
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mWifiStateMachine.setFrequencyBand(band, persist);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
 
-
-    /**
-     * Get the operational frequency band
-     */
-    public int getFrequencyBand() {
-        enforceAccessPermission();
-        return mWifiStateMachine.getFrequencyBand();
-    }
-
+    @Override
     public boolean isDualBandSupported() {
         //TODO: Should move towards adding a driver API that checks at runtime
+        mLog.trace("isDualBandSupported uid=%").c(Binder.getCallingUid()).flush();
         return mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_wifi_dual_band_support);
     }
@@ -1228,8 +1864,11 @@
      * @return the DHCP information
      * @deprecated
      */
+    @Override
+    @Deprecated
     public DhcpInfo getDhcpInfo() {
         enforceAccessPermission();
+        mLog.trace("getDhcpInfo uid=%").c(Binder.getCallingUid()).flush();
         DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
 
         DhcpInfo info = new DhcpInfo();
@@ -1264,26 +1903,6 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#addToBlacklist}
-     *
-     */
-    public void addToBlacklist(String bssid) {
-        enforceChangePermission();
-
-        mWifiStateMachine.addToBlacklist(bssid);
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#clearBlacklist}
-     *
-     */
-    public void clearBlacklist() {
-        enforceChangePermission();
-
-        mWifiStateMachine.clearBlacklist();
-    }
-
-    /**
      * enable TDLS for the local NIC to remote NIC
      * The APPs don't know the remote MAC address to identify NIC though,
      * so we need to do additional work to find it from remote IP address
@@ -1357,11 +1976,12 @@
         }
     }
 
+    @Override
     public void enableTdls(String remoteAddress, boolean enable) {
         if (remoteAddress == null) {
           throw new IllegalArgumentException("remoteAddress cannot be null");
         }
-
+        mLog.trace("enableTdls uid=% enable=%").c(Binder.getCallingUid()).c(enable).flush();
         TdlsTaskParams params = new TdlsTaskParams();
         params.remoteIpAddress = remoteAddress;
         params.enable = enable;
@@ -1369,7 +1989,12 @@
     }
 
 
+    @Override
     public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
+        mLog.trace("enableTdlsWithMacAddress uid=% enable=%")
+                .c(Binder.getCallingUid())
+                .c(enable)
+                .flush();
         if (remoteMacAddress == null) {
           throw new IllegalArgumentException("remoteMacAddress cannot be null");
         }
@@ -1381,29 +2006,25 @@
      * Get a reference to handler. This is used by a client to establish
      * an AsyncChannel communication with WifiService
      */
+    @Override
     public Messenger getWifiServiceMessenger() {
         enforceAccessPermission();
         enforceChangePermission();
+        mLog.trace("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
         return new Messenger(mClientHandler);
     }
 
     /**
      * Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer
      */
+    @Override
     public void disableEphemeralNetwork(String SSID) {
         enforceAccessPermission();
         enforceChangePermission();
+        mLog.trace("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disableEphemeralNetwork(SSID);
     }
 
-    /**
-     * Get the IP and proxy configuration file
-     */
-    public String getConfigFile() {
-        enforceAccessPermission();
-        return mWifiStateMachine.getConfigFile();
-    }
-
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1472,10 +2093,55 @@
                 mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
             }
         };
-
-        mContext.getContentResolver().registerContentObserver(
+        mFrameworkFacade.registerContentObserver(mContext,
                 Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
                 false, contentObserver);
+
+    }
+
+    // Monitors settings changes related to background wifi scan throttling.
+    private void registerForBackgroundThrottleChanges() {
+        mFrameworkFacade.registerContentObserver(
+                mContext,
+                Settings.Global.getUriFor(
+                        Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS),
+                false,
+                new ContentObserver(null) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updateBackgroundThrottleInterval();
+                    }
+                }
+        );
+        mFrameworkFacade.registerContentObserver(
+                mContext,
+                Settings.Global.getUriFor(
+                        Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST),
+                false,
+                new ContentObserver(null) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updateBackgroundThrottlingWhitelist();
+                    }
+                }
+        );
+    }
+
+    private void updateBackgroundThrottleInterval() {
+        mBackgroundThrottleInterval = mFrameworkFacade.getLongSetting(
+                mContext,
+                Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS,
+                DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS);
+    }
+
+    private void updateBackgroundThrottlingWhitelist() {
+        String setting = mFrameworkFacade.getStringSetting(
+                mContext,
+                Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+        mBackgroundThrottlePackageWhitelist.clear();
+        if (setting != null) {
+            mBackgroundThrottlePackageWhitelist.addAll(Arrays.asList(setting.split(",")));
+        }
     }
 
     private void registerForBroadcasts() {
@@ -1538,72 +2204,59 @@
                     + ", uid=" + Binder.getCallingUid());
             return;
         }
-        if (args.length > 0 && WifiMetrics.PROTO_DUMP_ARG.equals(args[0])) {
+        if (args != null && args.length > 0 && WifiMetrics.PROTO_DUMP_ARG.equals(args[0])) {
             // WifiMetrics proto bytes were requested. Dump only these.
             mWifiStateMachine.updateWifiMetrics();
             mWifiMetrics.dump(fd, pw, args);
-        } else if (args.length > 0 && IpManager.DUMP_ARG.equals(args[0])) {
+        } else if (args != null && args.length > 0 && IpManager.DUMP_ARG.equals(args[0])) {
             // IpManager dump was requested. Pass it along and take no further action.
             String[] ipManagerArgs = new String[args.length - 1];
             System.arraycopy(args, 1, ipManagerArgs, 0, ipManagerArgs.length);
             mWifiStateMachine.dumpIpManager(fd, pw, ipManagerArgs);
+        } else if (args != null && args.length > 0
+                && DUMP_ARG_SET_IPREACH_DISCONNECT.equals(args[0])) {
+            if (args.length > 1) {
+                if (DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED.equals(args[1])) {
+                    mWifiStateMachine.setIpReachabilityDisconnectEnabled(true);
+                } else if (DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED.equals(args[1])) {
+                    mWifiStateMachine.setIpReachabilityDisconnectEnabled(false);
+                }
+            }
+            pw.println("IPREACH_DISCONNECT state is "
+                    + mWifiStateMachine.getIpReachabilityDisconnectEnabled());
+            return;
         } else {
             pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
             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);
+                    mFacade.getIntegerSetting(mContext,
+                            Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
             pw.println("mInIdleMode " + mInIdleMode);
             pw.println("mScanPending " + mScanPending);
             mWifiController.dump(fd, pw, args);
             mSettingsStore.dump(fd, pw, args);
             mNotificationController.dump(fd, pw, args);
             mTrafficPoller.dump(fd, pw, args);
-
-            pw.println("Latest scan results:");
-            List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
-            long nowMs = System.currentTimeMillis();
-            if (scanResults != null && scanResults.size() != 0) {
-                pw.println("    BSSID              Frequency  RSSI    Age      SSID " +
-                        "                                Flags");
-                for (ScanResult r : scanResults) {
-                    long ageSec = 0;
-                    long ageMilli = 0;
-                    if (nowMs > r.seen && r.seen > 0) {
-                        ageSec = (nowMs - r.seen) / 1000;
-                        ageMilli = (nowMs - r.seen) % 1000;
-                    }
-                    String candidate = " ";
-                    if (r.isAutoJoinCandidate > 0) candidate = "+";
-                    pw.printf("  %17s  %9d  %5d  %3d.%03d%s   %-32s  %s\n",
-                                             r.BSSID,
-                                             r.frequency,
-                                             r.level,
-                                             ageSec, ageMilli,
-                                             candidate,
-                                             r.SSID == null ? "" : r.SSID,
-                                             r.capabilities);
-                }
-            }
             pw.println();
             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();
+            mWifiStateMachine.updateWifiMetrics();
+            mWifiMetrics.dump(fd, pw, args);
+            pw.println();
+            mWifiBackupRestore.dump(fd, pw, args);
+            pw.println();
         }
     }
 
     @Override
     public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
+        mLog.trace("acquireWifiLock uid=% lockMode=%")
+                .c(Binder.getCallingUid())
+                .c(lockMode).flush();
         if (mWifiLockManager.acquireWifiLock(lockMode, tag, binder, ws)) {
             mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             return true;
@@ -1613,11 +2266,13 @@
 
     @Override
     public void updateWifiLockWorkSource(IBinder binder, WorkSource ws) {
+        mLog.trace("updateWifiLockWorkSource uid=%").c(Binder.getCallingUid()).flush();
         mWifiLockManager.updateWifiLockWorkSource(binder, ws);
     }
 
     @Override
     public boolean releaseWifiLock(IBinder binder) {
+        mLog.trace("releaseWifiLock uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiLockManager.releaseWifiLock(binder)) {
             mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             return true;
@@ -1625,171 +2280,117 @@
         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();
-            }
-        }
+        mLog.trace("initializeMulticastFiltering uid=%").c(Binder.getCallingUid()).flush();
+        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);
-        }
+        mLog.trace("acquireMulticastLock uid=%").c(Binder.getCallingUid()).flush();
+        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);
-                }
-            }
-        }
+        mLog.trace("releaseMulticastLock uid=%").c(Binder.getCallingUid()).flush();
+        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);
-        }
+        mLog.trace("isMulticastEnabled uid=%").c(Binder.getCallingUid()).flush();
+        return mWifiMulticastLockManager.isMulticastEnabled();
     }
 
+    @Override
     public void enableVerboseLogging(int verbose) {
         enforceAccessPermission();
+        mLog.trace("enableVerboseLogging uid=% verbose=%")
+                .c(Binder.getCallingUid())
+                .c(verbose).flush();
+        mFacade.setIntegerSetting(
+                mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, verbose);
+        enableVerboseLoggingInternal(verbose);
+    }
+
+    void enableVerboseLoggingInternal(int verbose) {
         mWifiStateMachine.enableVerboseLogging(verbose);
         mWifiLockManager.enableVerboseLogging(verbose);
+        mWifiMulticastLockManager.enableVerboseLogging(verbose);
+        mWifiInjector.getWifiLastResortWatchdog().enableVerboseLogging(verbose);
+        mWifiInjector.getWifiBackupRestore().enableVerboseLogging(verbose);
+        LogcatLog.enableVerboseLogging(verbose);
     }
 
+    @Override
     public int getVerboseLoggingLevel() {
         enforceAccessPermission();
-        return mWifiStateMachine.getVerboseLoggingLevel();
+        mLog.trace("getVerboseLoggingLevel uid=%").c(Binder.getCallingUid()).flush();
+        return mFacade.getIntegerSetting(
+                mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0);
     }
 
+    @Override
     public void enableAggressiveHandover(int enabled) {
         enforceAccessPermission();
+        mLog.trace("enableAggressiveHandover uid=% enabled=%")
+            .c(Binder.getCallingUid())
+            .c(enabled)
+            .flush();
         mWifiStateMachine.enableAggressiveHandover(enabled);
     }
 
+    @Override
     public int getAggressiveHandover() {
         enforceAccessPermission();
+        mLog.trace("getAggressiveHandover uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getAggressiveHandover();
     }
 
+    @Override
     public void setAllowScansWithTraffic(int enabled) {
         enforceAccessPermission();
+        mLog.trace("setAllowScansWithTraffic uid=% enabled=%")
+                .c(Binder.getCallingUid())
+                .c(enabled).flush();
         mWifiStateMachine.setAllowScansWithTraffic(enabled);
     }
 
+    @Override
     public int getAllowScansWithTraffic() {
         enforceAccessPermission();
+        mLog.trace("getAllowScansWithTraffic uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getAllowScansWithTraffic();
     }
 
+    @Override
     public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
         enforceChangePermission();
+        mLog.trace("setEnableAutoJoinWhenAssociated uid=% enabled=%")
+                .c(Binder.getCallingUid())
+                .c(enabled).flush();
         return mWifiStateMachine.setEnableAutoJoinWhenAssociated(enabled);
     }
 
+    @Override
     public boolean getEnableAutoJoinWhenAssociated() {
         enforceAccessPermission();
+        mLog.trace("getEnableAutoJoinWhenAssociated uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getEnableAutoJoinWhenAssociated();
     }
 
     /* Return the Wifi Connection statistics object */
+    @Override
     public WifiConnectionStatistics getConnectionStatistics() {
         enforceAccessPermission();
         enforceReadCredentialPermission();
+        mLog.trace("getConnectionStatistics uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncGetConnectionStatistics(mWifiStateMachineChannel);
         } else {
@@ -1798,16 +2399,18 @@
         }
     }
 
+    @Override
     public void factoryReset() {
         enforceConnectivityInternalPermission();
-
+        mLog.trace("factoryReset uid=%").c(Binder.getCallingUid()).flush();
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
             return;
         }
 
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
-            // Turn mobile hotspot off
-            setWifiApEnabled(null, false);
+            // Turn mobile hotspot off - will also clear any registered LOHS requests when it is
+            // shut down
+            stopSoftApInternal();
         }
 
         if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI)) {
@@ -1818,12 +2421,15 @@
                 /* ignore - local call */
             }
             // Delete all Wifi SSIDs
-            List<WifiConfiguration> networks = getConfiguredNetworks();
-            if (networks != null) {
-                for (WifiConfiguration config : networks) {
-                    removeNetwork(config.networkId);
+            if (mWifiStateMachineChannel != null) {
+                List<WifiConfiguration> networks = mWifiStateMachine.syncGetConfiguredNetworks(
+                        Binder.getCallingUid(), mWifiStateMachineChannel);
+                if (networks != null) {
+                    for (WifiConfiguration config : networks) {
+                        removeNetwork(config.networkId);
+                    }
+                    saveConfiguration();
                 }
-                saveConfiguration();
             }
         }
     }
@@ -1839,11 +2445,6 @@
         return validity == null || logAndReturnFalse(validity);
     }
 
-    public static boolean isValidPasspoint(WifiConfiguration config) {
-        String validity = checkPasspointValidity(config);
-        return validity == null || logAndReturnFalse(validity);
-    }
-
     public static String checkValidity(WifiConfiguration config) {
         if (config.allowedKeyManagement == null)
             return "allowed kmgmt";
@@ -1860,38 +2461,22 @@
                 return "not PSK or 8021X";
             }
         }
-        return null;
-    }
-
-    public static String checkPasspointValidity(WifiConfiguration config) {
-        if (!TextUtils.isEmpty(config.FQDN)) {
-            /* this is passpoint configuration; it must not have an SSID */
-            if (!TextUtils.isEmpty(config.SSID)) {
-                return "SSID not expected for Passpoint: '" + config.SSID +
-                        "' FQDN " + toHexString(config.FQDN);
+        if (config.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
+            StaticIpConfiguration staticIpConf = config.getStaticIpConfiguration();
+            if (staticIpConf == null) {
+                return "null StaticIpConfiguration";
             }
-            /* this is passpoint configuration; it must have a providerFriendlyName */
-            if (TextUtils.isEmpty(config.providerFriendlyName)) {
-                return "no provider friendly name";
-            }
-            WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
-            /* this is passpoint configuration; it must have enterprise config */
-            if (enterpriseConfig == null
-                    || enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.NONE ) {
-                return "no enterprise config";
-            }
-            if ((enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS ||
-                    enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TTLS ||
-                    enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.PEAP) &&
-                    enterpriseConfig.getCaCertificate() == null) {
-                return "no CA certificate";
+            if (staticIpConf.ipAddress == null) {
+                return "null static ip Address";
             }
         }
         return null;
     }
 
+    @Override
     public Network getCurrentNetwork() {
         enforceAccessPermission();
+        mLog.trace("getCurrentNetwork uid=%").c(Binder.getCallingUid()).flush();
         return mWifiStateMachine.getCurrentNetwork();
     }
 
@@ -1907,46 +2492,6 @@
         return sb.toString();
     }
 
-    /**
-     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION or
-     * android.Manifest.permission.ACCESS_FINE_LOCATION and a corresponding app op is allowed
-     */
-    private boolean checkCallerCanAccessScanResults(String callingPackage, int uid) {
-        if (ActivityManager.checkUidPermission(Manifest.permission.ACCESS_FINE_LOCATION, uid)
-                == PackageManager.PERMISSION_GRANTED
-                && checkAppOppAllowed(AppOpsManager.OP_FINE_LOCATION, callingPackage, uid)) {
-            return true;
-        }
-
-        if (ActivityManager.checkUidPermission(Manifest.permission.ACCESS_COARSE_LOCATION, uid)
-                == PackageManager.PERMISSION_GRANTED
-                && checkAppOppAllowed(AppOpsManager.OP_COARSE_LOCATION, callingPackage, uid)) {
-            return true;
-        }
-        boolean apiLevel23App = isMApp(mContext, callingPackage);
-        // Pre-M apps running in the foreground should continue getting scan results
-        if (!apiLevel23App && isForegroundApp(callingPackage)) {
-            return true;
-        }
-        Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION "
-                + "permission to get scan results");
-        return false;
-    }
-
-    private boolean checkAppOppAllowed(int op, String callingPackage, int uid) {
-        return mAppOps.noteOp(op, uid, callingPackage) == AppOpsManager.MODE_ALLOWED;
-    }
-
-    private static boolean isMApp(Context context, String pkgName) {
-        try {
-            return context.getPackageManager().getApplicationInfo(pkgName, 0)
-                    .targetSdkVersion >= Build.VERSION_CODES.M;
-        } catch (PackageManager.NameNotFoundException e) {
-            // In case of exception, assume M app (more strict checking)
-        }
-        return true;
-    }
-
     public void hideCertFromUnaffiliatedUsers(String alias) {
         mCertManager.hideCertFromUnaffiliatedUsers(alias);
     }
@@ -1956,23 +2501,105 @@
     }
 
     /**
-     * Return true if the specified package name is a foreground app.
-     *
-     * @param pkgName application package name.
-     */
-    private boolean isForegroundApp(String pkgName) {
-        ActivityManager am = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
-    }
-
-    /**
      * Enable/disable WifiConnectivityManager at runtime
      *
      * @param enabled true-enable; false-disable
      */
+    @Override
     public void enableWifiConnectivityManager(boolean enabled) {
         enforceConnectivityInternalPermission();
+        mLog.trace("enableWifiConnectivityManager uid=% enabled=%")
+            .c(Binder.getCallingUid())
+            .c(enabled).flush();
         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() {
+        enforceNetworkSettingsPermission();
+        mLog.trace("retrieveBackupData uid=%").c(Binder.getCallingUid()).flush();
+        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) {
+        enforceNetworkSettingsPermission();
+        mLog.trace("restoreBackupData uid=%").c(Binder.getCallingUid()).flush();
+        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) {
+        enforceNetworkSettingsPermission();
+        mLog.trace("restoreSupplicantBackupData uid=%").c(Binder.getCallingUid()).flush();
+        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/WifiSettingsStore.java b/service/java/com/android/server/wifi/WifiSettingsStore.java
index 298e16d..40ae05c 100644
--- a/service/java/com/android/server/wifi/WifiSettingsStore.java
+++ b/service/java/com/android/server/wifi/WifiSettingsStore.java
@@ -224,4 +224,14 @@
                 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
                 0) == 1;
     }
+
+    /**
+     * Get Location Mode settings for the context
+     * @param context
+     * @return Location Mode setting
+     */
+    public int getLocationModeSetting(Context context) {
+        return Settings.Secure.getInt(context.getContentResolver(),
+              Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 6e5e28e..ab7cd01 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;
@@ -42,6 +41,7 @@
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.DhcpResults;
+import android.net.IpConfiguration;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
@@ -54,9 +54,11 @@
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
+import android.net.TrafficStats;
 import android.net.dhcp.DhcpClient;
 import android.net.ip.IpManager;
-import android.net.wifi.PasspointManagementObjectDefinition;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IClientInterface;
 import android.net.wifi.RssiPacketCountInfo;
 import android.net.wifi.ScanResult;
 import android.net.wifi.ScanSettings;
@@ -73,6 +75,7 @@
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.net.wifi.WpsResult.Status;
+import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -85,7 +88,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;
@@ -105,11 +107,20 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.connectivity.KeepalivePacketData;
+import com.android.server.wifi.hotspot2.AnqpEvent;
 import com.android.server.wifi.hotspot2.IconEvent;
 import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.hotspot2.WnmData;
+import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
+import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.TelephonyUtil;
+import com.android.server.wifi.util.TelephonyUtil.SimAuthRequestData;
+import com.android.server.wifi.util.TelephonyUtil.SimAuthResponseData;
+import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import java.io.BufferedReader;
 import java.io.FileDescriptor;
@@ -121,12 +132,12 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Calendar;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Queue;
-import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -147,22 +158,25 @@
  *
  * @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 static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID";
+    private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME";
+
+    private boolean mVerboseLoggingEnabled = false;
+
     /* debug flag, indicating if handling of ASSOCIATION_REJECT ended up blacklisting
      * the corresponding BSSID.
      */
@@ -173,26 +187,29 @@
      *
      * @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;
     private WifiNative mWifiNative;
+    private WifiPermissionsUtil mWifiPermissionsUtil;
     private WifiConfigManager mWifiConfigManager;
     private WifiConnectivityManager mWifiConnectivityManager;
-    private WifiQualifiedNetworkSelector mWifiQualifiedNetworkSelector;
     private INetworkManagementService mNwService;
+    private IClientInterface mClientInterface;
     private ConnectivityManager mCm;
-    private BaseWifiLogger mWifiLogger;
+    private BaseWifiDiagnostics mWifiDiagnostics;
     private WifiApConfigStore mWifiApConfigStore;
     private final boolean mP2pSupported;
     private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
@@ -202,6 +219,9 @@
     private final PropertyService mPropertyService;
     private final BuildProperties mBuildProperties;
     private final WifiCountryCode mCountryCode;
+    // Object holding most recent wifi score report and bad Linkspeed count
+    private final WifiScoreReport mWifiScoreReport;
+    private final PasspointManager mPasspointManager;
 
     /* Scan results handling */
     private List<ScanDetail> mScanResults = new ArrayList<>();
@@ -214,21 +234,22 @@
 
     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;
+    private final StateMachineDeathRecipient mDeathRecipient =
+            new StateMachineDeathRecipient(this, CMD_CLIENT_INTERFACE_BINDER_DEATH);
+    private final WifiNative.VendorHalDeathEventHandler mVendorHalDeathRecipient = () -> {
+        sendMessage(CMD_VENDOR_HAL_HWBINDER_DEATH);
+    };
+    private boolean mIpReachabilityDisconnectEnabled = true;
 
     @Override
     public void onRssiThresholdBreached(byte curRssi) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             Log.e(TAG, "onRssiThresholdBreach event. Cur Rssi = " + curRssi);
         }
         sendMessage(CMD_RSSI_THRESHOLD_BREACH, curRssi);
@@ -248,7 +269,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) +
@@ -272,26 +293,16 @@
     private int mOperationalMode = CONNECT_MODE;
     private boolean mIsScanOngoing = false;
     private boolean mIsFullScanOngoing = false;
-    private boolean mSendScanResultsBroadcast = false;
 
-    private final Queue<Message> mBufferedScanMsg = new LinkedList<Message>();
-    private WorkSource mScanWorkSource = null;
+    private final Queue<Message> mBufferedScanMsg = new LinkedList<>();
     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";
     private static final String CUSTOMIZED_SCAN_WORKSOURCE = "customized_scan_worksource";
     private static final String SCAN_REQUEST_TIME = "scan_request_time";
 
-    /* Tracks if state machine has received any screen state change broadcast yet.
-     * We can miss one of these at boot.
-     */
-    private AtomicBoolean mScreenBroadcastReceived = new AtomicBoolean(false);
-
     private boolean mBluetoothConnectionActive = false;
 
     private PowerManager.WakeLock mSuspendWakeLock;
@@ -318,31 +329,13 @@
      */
     private static final int SUPPLICANT_RESTART_TRIES = 5;
 
+    /**
+     * Value to set in wpa_supplicant "bssid" field when we don't want to restrict connection to
+     * a specific AP.
+     */
+    public static final String SUPPLICANT_BSSID_ANY = "any";
+
     private int mSupplicantRestartCount = 0;
-    /* Tracks sequence number on stop failure message */
-    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;
-
-    /* Tracks sequence number on a driver time out */
-    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.
@@ -369,177 +362,76 @@
 
     private int mWifiLinkLayerStatsSupported = 4; // Temporary disable
 
-    // Whether the state machine goes thru the Disconnecting->Disconnected->ObtainingIpAddress
-    private boolean mAutoRoaming = false;
+    // Indicates that framework is attempting to roam, set true on CMD_START_ROAM, set false when
+    // wifi connects or fails to connect
+    private boolean mIsAutoRoaming = false;
 
     // Roaming failure count
     private int mRoamFailCount = 0;
 
-    // This is the BSSID we are trying to associate to, it can be set to "any"
+    // This is the BSSID we are trying to associate to, it can be set to SUPPLICANT_BSSID_ANY
     // if we havent selected a BSSID for joining.
-    // if we havent selected a BSSID for joining.
-    // The BSSID we are associated to is found in mWifiInfo
-    private String mTargetRoamBSSID = "any";
-    //This one is used to track whta is the current target network ID. This is used for error
+    private String mTargetRoamBSSID = SUPPLICANT_BSSID_ANY;
+    // This one is used to track whta is the current target network ID. This is used for error
     // handling during connection setup since many error message from supplicant does not report
     // SSID Once connected, it will be set to invalid
     private int mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
-
     private long mLastDriverRoamAttempt = 0;
-
     private WifiConfiguration targetWificonfiguration = null;
 
-    // Used as debug to indicate which configuration last was saved
-    private WifiConfiguration lastSavedConfigurationAttempt = null;
-
-    // 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;
-    }
-
-    public void autoRoamSetBSSID(int netId, String bssid) {
-        autoRoamSetBSSID(mWifiConfigManager.getWifiConfiguration(netId), bssid);
-    }
-
-    public boolean autoRoamSetBSSID(WifiConfiguration config, String bssid) {
-        boolean ret = true;
-        if (mTargetRoamBSSID == null) mTargetRoamBSSID = "any";
-        if (bssid == null) bssid = "any";
-        if (config == null) return false; // Nothing to do
-
-        if (mTargetRoamBSSID != null
-                && bssid.equals(mTargetRoamBSSID) && bssid.equals(config.BSSID)) {
-            return false; // We didnt change anything
+    /**
+     * Method to clear {@link #mTargetRoamBSSID} and reset the the current connected network's
+     * bssid in wpa_supplicant after a roam/connect attempt.
+     */
+    public boolean clearTargetBssid(String dbg) {
+        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
+        if (config == null) {
+            return false;
         }
-        if (!mTargetRoamBSSID.equals("any") && bssid.equals("any")) {
-            // Changing to ANY
-            if (!mWifiConfigManager.ROAM_ON_ANY) {
-                ret = false; // Nothing to do
-            }
-        }
+        String bssid = SUPPLICANT_BSSID_ANY;
         if (config.BSSID != null) {
             bssid = config.BSSID;
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "force BSSID to " + bssid + "due to config");
             }
         }
-
-        if (DBG) {
-            logd("autoRoamSetBSSID " + bssid + " key=" + config.configKey());
+        if (mVerboseLoggingEnabled) {
+            logd(dbg + " clearTargetBssid " + bssid + " key=" + config.configKey());
         }
         mTargetRoamBSSID = bssid;
-        mWifiConfigManager.saveWifiConfigBSSID(config, bssid);
-        return ret;
+        return mWifiNative.setConfiguredNetworkBSSID(bssid);
     }
 
     /**
-     * set Config's default BSSID (for association purpose)
+     * Set Config's default BSSID (for association purpose) and {@link #mTargetRoamBSSID}
      * @param config config need set BSSID
      * @param bssid  default BSSID to assocaite with when connect to this network
      * @return false -- does not change the current default BSSID of the configure
      *         true -- change the  current default BSSID of the configur
      */
     private boolean setTargetBssid(WifiConfiguration config, String bssid) {
-        if (config == null) {
+        if (config == null || bssid == null) {
             return false;
         }
-
         if (config.BSSID != null) {
             bssid = config.BSSID;
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "force BSSID to " + bssid + "due to config");
             }
         }
-
-        if (bssid == null) {
-            bssid = "any";
-        }
-
-        String networkSelectionBSSID = config.getNetworkSelectionStatus()
-                .getNetworkSelectionBSSID();
-        if (networkSelectionBSSID != null && networkSelectionBSSID.equals(bssid)) {
-            if (DBG) {
-                Log.d(TAG, "Current preferred BSSID is the same as the target one");
-            }
-            return false;
-        }
-
-        if (DBG) {
-            Log.d(TAG, "target set to " + config.SSID + ":" + bssid);
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "setTargetBssid set to " + bssid + " key=" + config.configKey());
         }
         mTargetRoamBSSID = bssid;
-        mWifiConfigManager.saveWifiConfigBSSID(config, bssid);
+        config.getNetworkSelectionStatus().setNetworkSelectionBSSID(bssid);
         return true;
     }
-    /**
-     * Save the UID correctly depending on if this is a new or existing network.
-     * @return true if operation is authorized, false otherwise
-     */
-    boolean recordUidIfAuthorized(WifiConfiguration config, int uid, boolean onlyAnnotate) {
-        if (!mWifiConfigManager.isNetworkConfigured(config)) {
-            config.creatorUid = uid;
-            config.creatorName = mContext.getPackageManager().getNameForUid(uid);
-        } else if (!mWifiConfigManager.canModifyNetwork(uid, config, onlyAnnotate)) {
-            return false;
-        }
-
-        config.lastUpdateUid = uid;
-        config.lastUpdateName = mContext.getPackageManager().getNameForUid(uid);
-
-        return true;
-
-    }
-
-    /**
-     * Checks to see if user has specified if the apps configuration is connectable.
-     * If the user hasn't specified we query the user and return true.
-     *
-     * @param message The message to be deferred
-     * @param netId Network id of the configuration to check against
-     * @param allowOverride If true we won't defer to the user if the uid of the message holds the
-     *                      CONFIG_OVERRIDE_PERMISSION
-     * @return True if we are waiting for user feedback or netId is invalid. False otherwise.
-     */
-    boolean deferForUserInput(Message message, int netId, boolean allowOverride){
-        final WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(netId);
-
-        // We can only evaluate saved configurations.
-        if (config == null) {
-            logd("deferForUserInput: configuration for netId=" + netId + " not stored");
-            return true;
-        }
-
-        switch (config.userApproved) {
-            case WifiConfiguration.USER_APPROVED:
-            case WifiConfiguration.USER_BANNED:
-                return false;
-            case WifiConfiguration.USER_PENDING:
-            default: // USER_UNSPECIFIED
-               /* the intention was to ask user here; but a dialog box is   *
-                * too invasive; so we are going to allow connection for now */
-                config.userApproved = WifiConfiguration.USER_APPROVED;
-                return false;
-        }
-    }
 
     private final IpManager mIpManager;
 
-    private AlarmManager mAlarmManager;
-    private PendingIntent mScanIntent;
-
-    /* Tracks current frequency mode */
-    private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
-
     // Channel for sending replies.
     private AsyncChannel mReplyChannel = new AsyncChannel();
 
-    private WifiP2pServiceImpl mWifiP2pServiceImpl;
-
     // Used to initiate a connection with WifiP2pService
     private AsyncChannel mWifiP2pChannel;
 
@@ -554,8 +446,6 @@
     private WifiNetworkAgent mNetworkAgent;
     private final Object mWifiReqCountLock = new Object();
 
-    private String[] mWhiteListedSsids = null;
-
     private byte[] mRssiRanges;
 
     // Keep track of various statistics, for retrieval by System Apps, i.e. under @SystemApi
@@ -575,16 +465,10 @@
     static final int CMD_START_SUPPLICANT                               = BASE + 11;
     /* Stop the supplicant */
     static final int CMD_STOP_SUPPLICANT                                = BASE + 12;
-    /* Start the driver */
-    static final int CMD_START_DRIVER                                   = BASE + 13;
-    /* Stop the driver */
-    static final int CMD_STOP_DRIVER                                    = BASE + 14;
     /* Indicates Static IP succeeded */
     static final int CMD_STATIC_IP_SUCCESS                              = BASE + 15;
     /* Indicates Static IP failed */
     static final int CMD_STATIC_IP_FAILURE                              = BASE + 16;
-    /* Indicates supplicant stop failed */
-    static final int CMD_STOP_SUPPLICANT_FAILED                         = BASE + 17;
     /* A delayed message sent to start driver when it fail to come up */
     static final int CMD_DRIVER_START_TIMED_OUT                         = BASE + 19;
 
@@ -600,34 +484,22 @@
     static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE                 = BASE + 31;
 
     /* Supplicant commands */
-    /* Is supplicant alive ? */
-    static final int CMD_PING_SUPPLICANT                                = BASE + 51;
     /* Add/update a network configuration */
     static final int CMD_ADD_OR_UPDATE_NETWORK                          = BASE + 52;
     /* Delete a network */
     static final int CMD_REMOVE_NETWORK                                 = BASE + 53;
     /* Enable a network. The device will attempt a connection to the given network. */
     static final int CMD_ENABLE_NETWORK                                 = BASE + 54;
-    /* Enable all networks */
-    static final int CMD_ENABLE_ALL_NETWORKS                            = BASE + 55;
-    /* Blacklist network. De-prioritizes the given BSSID for connection. */
-    static final int CMD_BLACKLIST_NETWORK                              = BASE + 56;
-    /* Clear the blacklist network list */
-    static final int CMD_CLEAR_BLACKLIST                                = BASE + 57;
     /* Save configuration */
     static final int CMD_SAVE_CONFIG                                    = BASE + 58;
     /* Get configured networks */
     static final int CMD_GET_CONFIGURED_NETWORKS                        = BASE + 59;
-    /* Get available frequencies */
-    static final int CMD_GET_CAPABILITY_FREQ                            = BASE + 60;
     /* Get adaptors */
     static final int CMD_GET_SUPPORTED_FEATURES                         = BASE + 61;
     /* Get configured networks with real preSharedKey */
     static final int CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS             = BASE + 62;
     /* Get Link Layer Stats thru HAL */
     static final int CMD_GET_LINK_LAYER_STATS                           = BASE + 63;
-    /* Has Carrier configured networks */
-    static final int CMD_HAS_CARRIER_CONFIGURED_NETWORKS                = BASE + 64;
     /* Supplicant commands after driver start*/
     /* Initiate a scan */
     static final int CMD_START_SCAN                                     = BASE + 71;
@@ -670,12 +542,8 @@
 
     private int testNetworkDisconnectCounter = 0;
 
-    /* Set the frequency band */
-    static final int CMD_SET_FREQUENCY_BAND                             = BASE + 90;
     /* Enable TDLS on a specific MAC address */
     static final int CMD_ENABLE_TDLS                                    = BASE + 92;
-    /* DHCP/IP configuration watchdog */
-    static final int CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER            = BASE + 93;
 
     /**
      * Watchdog for protecting against b/16823537
@@ -708,21 +576,19 @@
     static final int CMD_RESET_SIM_NETWORKS                             = BASE + 101;
 
     /* OSU APIs */
-    static final int CMD_ADD_PASSPOINT_MO                               = BASE + 102;
-    static final int CMD_MODIFY_PASSPOINT_MO                            = BASE + 103;
     static final int CMD_QUERY_OSU_ICON                                 = BASE + 104;
 
     /* 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;
+    // Add or update a Passpoint configuration.
+    static final int CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG                 = BASE + 106;
 
-    int obtainingIpWatchdogCount = 0;
+    // Remove a Passpoint configuration.
+    static final int CMD_REMOVE_PASSPOINT_CONFIG                        = BASE + 107;
+
+    // Get the list of installed Passpoint configurations.
+    static final int CMD_GET_PASSPOINT_CONFIGS                          = BASE + 108;
 
     /* Commands from/to the SupplicantStateTracker */
     /* Reset the supplicant state tracker */
@@ -731,6 +597,12 @@
     int disconnectingWatchdogCount = 0;
     static final int DISCONNECTING_GUARD_TIMER_MSEC = 5000;
 
+    /* Disable p2p watchdog */
+    static final int CMD_DISABLE_P2P_WATCHDOG_TIMER                   = BASE + 112;
+
+    int mDisableP2pWatchdogCount = 0;
+    static final int DISABLE_P2P_GUARD_TIMER_MSEC = 2000;
+
     /* P2p commands */
     /* We are ok with no response here since we wont do much with it anyway */
     public static final int CMD_ENABLE_P2P                              = BASE + 131;
@@ -739,7 +611,16 @@
     public static final int CMD_DISABLE_P2P_REQ                         = BASE + 132;
     public static final int CMD_DISABLE_P2P_RSP                         = BASE + 133;
 
-    public static final int CMD_BOOT_COMPLETED                          = BASE + 134;
+    /**
+     * Indicates the end of boot process, should be used to trigger load from config store,
+     * initiate connection attempt, etc.
+     * */
+    static final int CMD_BOOT_COMPLETED                                 = BASE + 134;
+    /**
+     * Initialize the WifiStateMachine. This is currently used to initialize the
+     * {@link HalDeviceManager} module.
+     */
+    static final int CMD_INITIALIZE                                     = BASE + 135;
 
     /* We now have a valid IP configuration. */
     static final int CMD_IP_CONFIGURATION_SUCCESSFUL                    = BASE + 138;
@@ -754,7 +635,7 @@
     /* Reload all networks and reconnect */
     static final int CMD_RELOAD_TLS_AND_RECONNECT                       = BASE + 142;
 
-    static final int CMD_AUTO_CONNECT                                   = BASE + 143;
+    static final int CMD_START_CONNECT                                  = BASE + 143;
 
     private static final int NETWORK_STATUS_UNWANTED_DISCONNECT         = 0;
     private static final int NETWORK_STATUS_UNWANTED_VALIDATION_FAILED  = 1;
@@ -762,9 +643,7 @@
 
     static final int CMD_UNWANTED_NETWORK                               = BASE + 144;
 
-    static final int CMD_AUTO_ROAM                                      = BASE + 145;
-
-    static final int CMD_AUTO_SAVE_NETWORK                              = BASE + 146;
+    static final int CMD_START_ROAM                                     = BASE + 145;
 
     static final int CMD_ASSOCIATED_BSSID                               = BASE + 147;
 
@@ -778,9 +657,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;
 
@@ -796,9 +672,6 @@
     /* used to indicated RSSI threshold breach in hw */
     static final int CMD_RSSI_THRESHOLD_BREACH                          = BASE + 164;
 
-    /* used to indicate that the foreground user was switched */
-    static final int CMD_USER_SWITCH                                    = BASE + 165;
-
     /* Enable/Disable WifiConnectivityManager */
     static final int CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER               = BASE + 166;
 
@@ -820,6 +693,24 @@
     /* Enable/disable Neighbor Discovery offload functionality. */
     static final int CMD_CONFIG_ND_OFFLOAD                              = BASE + 204;
 
+    /* used to indicate that the foreground user was switched */
+    static final int CMD_USER_SWITCH                                    = BASE + 205;
+
+    /* used to indicate that the foreground user was switched */
+    static final int CMD_USER_UNLOCK                                    = BASE + 206;
+
+    /* used to indicate that the foreground user was switched */
+    static final int CMD_USER_STOP                                      = BASE + 207;
+
+    /* Signals that IClientInterface instance underpinning our state is dead. */
+    private static final int CMD_CLIENT_INTERFACE_BINDER_DEATH          = BASE + 250;
+
+    /* Signals that the Vendor HAL instance underpinning our state is dead. */
+    private static final int CMD_VENDOR_HAL_HWBINDER_DEATH              = BASE + 251;
+
+    /* Indicates that diagnostics should time out a connection start event. */
+    private static final int CMD_DIAGS_CONNECT_TIMEOUT                  = BASE + 252;
+
     // For message logging.
     private static final Class[] sMessageClasses = {
             AsyncChannel.class, WifiStateMachine.class, DhcpClient.class };
@@ -834,6 +725,8 @@
     public static final int SCAN_ONLY_MODE = 2;
     /* SCAN_ONLY_WITH_WIFI_OFF - scan, but don't connect to any APs */
     public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3;
+    /* DISABLED_MODE - Don't connect, don't scan, don't be an AP */
+    public static final int DISABLED_MODE = 4;
 
     private static final int SUCCESS = 1;
     private static final int FAILURE = -1;
@@ -853,16 +746,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;
@@ -874,13 +757,17 @@
      */
     private long mSupplicantScanIntervalMs;
 
-    /**
-     * Minimum time interval between enabling all networks.
-     * A device can end up repeatedly connecting to a bad network on screen on/off toggle
-     * due to enabling every time. We add a threshold to avoid this.
-     */
-    private static final int MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS = 10 * 60 * 1000; /* 10 minutes */
-    private long mLastEnableAllNetworksTime;
+    private boolean mEnableAutoJoinWhenAssociated;
+    private int mAlwaysEnableScansWhileAssociated;
+    private final int mThresholdQualifiedRssi24;
+    private final int mThresholdQualifiedRssi5;
+    private final int mThresholdSaturatedRssi24;
+    private final int mThresholdSaturatedRssi5;
+    private final int mThresholdMinimumRssi5;
+    private final int mThresholdMinimumRssi24;
+    private final boolean mEnableLinkDebouncing;
+    private final boolean mEnableChipWakeUpWhenAssociated;
+    private final boolean mEnableRssiPollWhenAssociated;
 
     int mRunningBeaconCount = 0;
 
@@ -894,19 +781,11 @@
     private State mSupplicantStartedState = new SupplicantStartedState();
     /* Waiting for supplicant to stop and monitor to exit */
     private State mSupplicantStoppingState = new SupplicantStoppingState();
-    /* Driver start issued, waiting for completed event */
-    private State mDriverStartingState = new DriverStartingState();
-    /* Driver started */
-    private State mDriverStartedState = new DriverStartedState();
     /* Wait until p2p is disabled
      * This is a special state which is entered right after we exit out of DriverStartedState
      * before transitioning to another state.
      */
     private State mWaitForP2pDisableState = new WaitForP2pDisableState();
-    /* Driver stopping */
-    private State mDriverStoppingState = new DriverStoppingState();
-    /* Driver stopped */
-    private State mDriverStoppedState = new DriverStoppedState();
     /* Scan for networks, no connection will be established */
     private State mScanModeState = new ScanModeState();
     /* Connecting to an access point */
@@ -928,15 +807,6 @@
     /* Soft ap state */
     private State mSoftApState = new SoftApState();
 
-    public static class SimAuthRequestData {
-        int networkId;
-        int protocol;
-        String ssid;
-        // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges
-        // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge
-        String[] data;
-    }
-
     /**
      * One of  {@link WifiManager#WIFI_STATE_DISABLED},
      * {@link WifiManager#WIFI_STATE_DISABLING},
@@ -955,8 +825,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,6 +850,14 @@
      */
     private final WorkSource mLastRunningWifiUids = new WorkSource();
 
+    private TelephonyManager mTelephonyManager;
+    private TelephonyManager getTelephonyManager() {
+        if (mTelephonyManager == null) {
+            mTelephonyManager = mWifiInjector.makeTelephonyManager();
+        }
+        return mTelephonyManager;
+    }
+
     private final IBatteryStats mBatteryStats;
 
     private final String mTcpBufferSizes;
@@ -989,70 +865,52 @@
     // 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 WifiStateTracker mWifiStateTracker;
     private final BackupManagerProxy mBackupManagerProxy;
 
-    private int mSystemUiUid = -1;
-
     public WifiStateMachine(Context context, FrameworkFacade facade, Looper looper,
                             UserManager userManager, WifiInjector wifiInjector,
-                            BackupManagerProxy backupManagerProxy,
-                            WifiCountryCode countryCode) {
+                            BackupManagerProxy backupManagerProxy, WifiCountryCode countryCode,
+                            WifiNative wifiNative) {
         super("WifiStateMachine", looper);
         mWifiInjector = wifiInjector;
         mWifiMetrics = mWifiInjector.getWifiMetrics();
-        mWifiLastResortWatchdog = wifiInjector.getWifiLastResortWatchdog();
         mClock = wifiInjector.getClock();
         mPropertyService = wifiInjector.getPropertyService();
         mBuildProperties = wifiInjector.getBuildProperties();
         mContext = context;
         mFacade = facade;
-        mWifiNative = WifiNative.getWlanNativeInterface();
+        mWifiNative = wifiNative;
         mBackupManagerProxy = backupManagerProxy;
 
         // TODO refactor WifiNative use of context out into it's own class
-        mWifiNative.initContext(mContext);
         mInterfaceName = mWifiNative.getInterfaceName();
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
         mBatteryStats = IBatteryStats.Stub.asInterface(mFacade.getService(
                 BatteryStats.SERVICE_NAME));
-
+        mWifiStateTracker = wifiInjector.getWifiStateTracker();
         IBinder b = mFacade.getService(Context.NETWORKMANAGEMENT_SERVICE);
         mNwService = INetworkManagementService.Stub.asInterface(b);
 
         mP2pSupported = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_DIRECT);
-        mWifiConfigManager = mFacade.makeWifiConfigManager(context, mWifiNative, facade,
-                mWifiInjector.getClock(), userManager, mWifiInjector.getKeyStore());
 
-        mWifiMonitor = WifiMonitor.getInstance();
+        mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
+        mWifiConfigManager = mWifiInjector.getWifiConfigManager();
+        mWifiApConfigStore = mWifiInjector.getWifiApConfigStore();
 
-        boolean enableFirmwareLogs = mContext.getResources().getBoolean(
-                R.bool.config_wifi_enable_wifi_firmware_debugging);
+        mPasspointManager = mWifiInjector.getPasspointManager();
 
-        if (enableFirmwareLogs) {
-            mWifiLogger = facade.makeRealLogger(mContext, this, mWifiNative, mBuildProperties);
-        } else {
-            mWifiLogger = facade.makeBaseLogger();
-        }
+        mWifiMonitor = mWifiInjector.getWifiMonitor();
+        mWifiDiagnostics = mWifiInjector.makeWifiDiagnostics(mWifiNative);
 
         mWifiInfo = new WifiInfo();
-        mWifiQualifiedNetworkSelector = new WifiQualifiedNetworkSelector(mWifiConfigManager,
-                mContext, mWifiInfo, mWifiInjector.getClock());
-        mSupplicantStateTracker = mFacade.makeSupplicantStateTracker(
-                context, mWifiConfigManager, getHandler());
+        mSupplicantStateTracker =
+                mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler());
 
         mLinkProperties = new LinkProperties();
 
-        IBinder s1 = mFacade.getService(Context.WIFI_P2P_SERVICE);
-        mWifiP2pServiceImpl = (WifiP2pServiceImpl) IWifiP2pManager.Stub.asInterface(s1);
-
         mNetworkInfo.setIsAvailable(false);
         mLastBssid = null;
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
@@ -1061,27 +919,20 @@
         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);
 
         mCountryCode = countryCode;
 
+        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager);
+
         mUserWantsSuspendOpt.set(mFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
 
@@ -1139,15 +990,33 @@
         mTcpBufferSizes = mContext.getResources().getString(
                 com.android.internal.R.string.config_wifi_tcp_buffers);
 
+        // Load Device configs
+        mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
+                R.bool.config_wifi_framework_enable_associated_network_selection);
+        mThresholdQualifiedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+        mThresholdQualifiedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+        mThresholdSaturatedRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
+        mThresholdSaturatedRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
+        mThresholdMinimumRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdMinimumRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mEnableLinkDebouncing = mContext.getResources().getBoolean(
+                R.bool.config_wifi_enable_disconnection_debounce);
+        mEnableChipWakeUpWhenAssociated = true;
+        mEnableRssiPollWhenAssociated = true;
+
         // CHECKSTYLE:OFF IndentationCheck
         addState(mDefaultState);
             addState(mInitialState, mDefaultState);
             addState(mSupplicantStartingState, mDefaultState);
             addState(mSupplicantStartedState, mDefaultState);
-                addState(mDriverStartingState, mSupplicantStartedState);
-                addState(mDriverStartedState, mSupplicantStartedState);
-                    addState(mScanModeState, mDriverStartedState);
-                    addState(mConnectModeState, mDriverStartedState);
+                    addState(mScanModeState, mSupplicantStartedState);
+                    addState(mConnectModeState, mSupplicantStartedState);
                         addState(mL2ConnectedState, mConnectModeState);
                             addState(mObtainingIpState, mL2ConnectedState);
                             addState(mConnectedState, mL2ConnectedState);
@@ -1156,8 +1025,6 @@
                         addState(mDisconnectedState, mConnectModeState);
                         addState(mWpsRunningState, mConnectModeState);
                 addState(mWaitForP2pDisableState, mSupplicantStartedState);
-                addState(mDriverStoppingState, mSupplicantStartedState);
-                addState(mDriverStoppedState, mSupplicantStartedState);
             addState(mSupplicantStoppingState, mDefaultState);
             addState(mSoftApState, mDefaultState);
         // CHECKSTYLE:ON IndentationCheck
@@ -1170,6 +1037,10 @@
         //start the state machine
         start();
 
+        // Learn the initial state of whether the screen is on.
+        // We update this field when we receive broadcasts from the system.
+        handleScreenStateChanged(powerManager.isInteractive());
+
         mWifiMonitor.registerHandler(mInterfaceName, CMD_TARGET_BSSID, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, CMD_ASSOCIATED_BSSID, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ANQP_DONE_EVENT, getHandler());
@@ -1177,7 +1048,6 @@
                 getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                 getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.DRIVER_HUNG_EVENT, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.GAS_QUERY_DONE_EVENT, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.GAS_QUERY_START_EVENT,
                 getHandler());
@@ -1191,8 +1061,6 @@
                 getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SCAN_FAILED_EVENT, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SCAN_RESULTS_EVENT, getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SSID_REENABLED, getHandler());
-        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SSID_TEMP_DISABLED, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUP_CONNECTION_EVENT, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUP_DISCONNECTION_EVENT,
                 getHandler());
@@ -1205,21 +1073,25 @@
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.WPS_SUCCESS_EVENT, getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.WPS_TIMEOUT_EVENT, getHandler());
 
+        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATION_REJECTION_EVENT,
+                mWifiMetrics.getHandler());
+        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+                mWifiMetrics.getHandler());
+        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
+                mWifiMetrics.getHandler());
+        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_DISCONNECTION_EVENT,
+                mWifiMetrics.getHandler());
+        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
+                mWifiMetrics.getHandler());
+        mWifiMonitor.registerHandler(mInterfaceName, CMD_ASSOCIATED_BSSID,
+                mWifiMetrics.getHandler());
+        mWifiMonitor.registerHandler(mInterfaceName, CMD_TARGET_BSSID,
+                mWifiMetrics.getHandler());
+
         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_DISABLED);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-
-        try {
-            mSystemUiUid = mContext.getPackageManager().getPackageUidAsUser("com.android.systemui",
-                    PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
-        } 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 {
@@ -1239,20 +1111,22 @@
                 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);
             }
         }
 
         @Override
         public void onProvisioningSuccess(LinkProperties newLp) {
+            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL);
             sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
             sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
         }
 
         @Override
         public void onProvisioningFailure(LinkProperties newLp) {
+            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST);
             sendMessage(CMD_IP_CONFIGURATION_LOST);
         }
 
@@ -1263,6 +1137,7 @@
 
         @Override
         public void onReachabilityLost(String logMsg) {
+            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);
             sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
         }
 
@@ -1295,54 +1170,36 @@
         return mFacade.getBroadcast(mContext, requestCode, intent, 0);
     }
 
-    int getVerboseLoggingLevel() {
-        return mVerboseLoggingLevel;
-    }
-
-    void enableVerboseLogging(int verbose) {
-        if (mVerboseLoggingLevel == verbose) {
-            // We are already at the desired verbosity, avoid resetting StateMachine log records by
-            // returning here until underlying bug is fixed (b/28027593)
-            return;
-        }
-        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) {
-            mWifiNative.setSupplicantLogLevel("DEBUG");
-        } else {
-            mWifiNative.setSupplicantLogLevel("INFO");
-        }
+        mWifiNative.setSupplicantLogLevel(mVerboseLoggingEnabled);
     }
 
-    void updateLoggingLevel() {
-        if (mVerboseLoggingLevel > 0) {
-            DBG = true;
+    /**
+     * Method to update logging level in wifi service related classes.
+     *
+     * @param verbose int logging level to use
+     */
+    public 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);
-        if (mWifiConnectivityManager != null) {
-            mWifiConnectivityManager.enableVerboseLogging(mVerboseLoggingLevel);
-        }
+        mCountryCode.enableVerboseLogging(verbose);
+        mWifiScoreReport.enableVerboseLogging(mVerboseLoggingEnabled);
+        mWifiDiagnostics.startLogging(mVerboseLoggingEnabled);
+        mWifiMonitor.enableVerboseLogging(verbose);
+        mWifiNative.enableVerboseLogging(verbose);
+        mWifiConfigManager.enableVerboseLogging(verbose);
+        mSupplicantStateTracker.enableVerboseLogging(verbose);
     }
 
     private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL";
@@ -1356,15 +1213,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() {
@@ -1376,15 +1224,16 @@
     }
 
     public void clearANQPCache() {
-        mWifiConfigManager.trimANQPCache(true);
+        // TODO(b/31065385)
+        // mWifiConfigManager.trimANQPCache(true);
     }
 
     public void setAllowScansWithTraffic(int enabled) {
-        mWifiConfigManager.mAlwaysEnableScansWhileAssociated.set(enabled);
+        mAlwaysEnableScansWhileAssociated = enabled;
     }
 
     public int getAllowScansWithTraffic() {
-        return mWifiConfigManager.mAlwaysEnableScansWhileAssociated.get();
+        return mAlwaysEnableScansWhileAssociated;
     }
 
     /*
@@ -1396,7 +1245,7 @@
     }
 
     public boolean getEnableAutoJoinWhenAssociated() {
-        return mWifiConfigManager.getEnableAutoJoinWhenAssociated();
+        return mEnableAutoJoinWhenAssociated;
     }
 
     private boolean setRandomMacOui() {
@@ -1415,6 +1264,56 @@
     }
 
     /**
+     * Helper method to lookup the framework network ID of the network currently configured in
+     * wpa_supplicant using the provided supplicant network ID. This is needed for translating the
+     * networkID received from all {@link WifiMonitor} events.
+     *
+     * @param supplicantNetworkId Network ID of network in wpa_supplicant.
+     * @return Corresponding Internal configured network ID
+     * TODO(b/31080843): This is ugly! We need to hide this translation of networkId's. This will
+     * be handled once we move all of this connection logic to wificond.
+     */
+    private int lookupFrameworkNetworkId(int supplicantNetworkId) {
+        return mWifiNative.getFrameworkNetworkId(supplicantNetworkId);
+    }
+
+    /**
+     * Initiates connection to a network specified by the user/app. This method checks if the
+     * requesting app holds the WIFI_CONFIG_OVERRIDE permission.
+     *
+     * @param netId Id network to initiate connection.
+     * @param uid UID of the app requesting the connection.
+     * @param forceReconnect Whether to force a connection even if we're connected to the same
+     *                       network currently.
+     */
+    private boolean connectToUserSelectNetwork(int netId, int uid, boolean forceReconnect) {
+        logd("connectToUserSelectNetwork netId " + netId + ", uid " + uid
+                + ", forceReconnect = " + forceReconnect);
+        if (mWifiConfigManager.getConfiguredNetwork(netId) == null) {
+            loge("connectToUserSelectNetwork Invalid network Id=" + netId);
+            return false;
+        }
+        if (!mWifiConfigManager.enableNetwork(netId, true, uid)
+                || !mWifiConfigManager.checkAndUpdateLastConnectUid(netId, uid)) {
+            logi("connectToUserSelectNetwork Allowing uid " + uid
+                    + " with insufficient permissions to connect=" + netId);
+        } else {
+            // Note user connect choice here, so that it will be considered in the next network
+            // selection.
+            mWifiConnectivityManager.setUserConnectChoice(netId);
+        }
+        if (!forceReconnect && mWifiInfo.getNetworkId() == netId) {
+            // We're already connected to the user specified network, don't trigger a
+            // reconnection unless it was forced.
+            logi("connectToUserSelectNetwork already connecting/connected=" + netId);
+        } else {
+            mWifiConnectivityManager.prepareForForcedConnection(netId);
+            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+        }
+        return true;
+    }
+
+    /**
      * ******************************************************
      * Methods exposed for public use
      * ******************************************************
@@ -1425,16 +1324,6 @@
     }
 
     /**
-     * TODO: doc
-     */
-    public boolean syncPingSupplicant(AsyncChannel channel) {
-        Message resultMsg = channel.sendMessageSynchronously(CMD_PING_SUPPLICANT);
-        boolean result = (resultMsg.arg1 != FAILURE);
-        resultMsg.recycle();
-        return result;
-    }
-
-    /**
      * Initiate a wifi scan. If workSource is not null, blame is given to it, otherwise blame is
      * given to callingUid.
      *
@@ -1448,47 +1337,16 @@
         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);
     }
 
-    // called from BroadcastListener
-
-    /**
-     * Start reading new scan data
-     * Data comes in as:
-     * "scancount=5\n"
-     * "nextcount=5\n"
-     * "apcount=3\n"
-     * "trunc\n" (optional)
-     * "bssid=...\n"
-     * "ssid=...\n"
-     * "freq=...\n" (in Mhz)
-     * "level=...\n"
-     * "dist=...\n" (in cm)
-     * "distsd=...\n" (standard deviation, in cm)
-     * "===="
-     * "bssid=...\n"
-     * etc
-     * "===="
-     * "bssid=...\n"
-     * etc
-     * "%%%%"
-     * "apcount=2\n"
-     * "bssid=...\n"
-     * etc
-     * "%%%%
-     * etc
-     * "----"
-     */
-    private final static boolean DEBUG_PARSE = false;
-
     private long mDisconnectedTimeStamp = 0;
 
     public long getDisconnectedTimeMilli() {
         if (getCurrentState() == mDisconnectedState
                 && mDisconnectedTimeStamp != 0) {
-            long now_ms = System.currentTimeMillis();
+            long now_ms = mClock.getWallClockMillis();
             return now_ms - mDisconnectedTimeStamp;
         }
         return 0;
@@ -1501,23 +1359,23 @@
 
     // For debugging, keep track of last message status handling
     // TODO, find an equivalent mechanism as part of parent class
-    private static int MESSAGE_HANDLING_STATUS_PROCESSED = 2;
-    private static int MESSAGE_HANDLING_STATUS_OK = 1;
-    private static int MESSAGE_HANDLING_STATUS_UNKNOWN = 0;
-    private static int MESSAGE_HANDLING_STATUS_REFUSED = -1;
-    private static int MESSAGE_HANDLING_STATUS_FAIL = -2;
-    private static int MESSAGE_HANDLING_STATUS_OBSOLETE = -3;
-    private static int MESSAGE_HANDLING_STATUS_DEFERRED = -4;
-    private static int MESSAGE_HANDLING_STATUS_DISCARD = -5;
-    private static int MESSAGE_HANDLING_STATUS_LOOPED = -6;
-    private static int MESSAGE_HANDLING_STATUS_HANDLING_ERROR = -7;
+    private static final int MESSAGE_HANDLING_STATUS_PROCESSED = 2;
+    private static final int MESSAGE_HANDLING_STATUS_OK = 1;
+    private static final int MESSAGE_HANDLING_STATUS_UNKNOWN = 0;
+    private static final int MESSAGE_HANDLING_STATUS_REFUSED = -1;
+    private static final int MESSAGE_HANDLING_STATUS_FAIL = -2;
+    private static final int MESSAGE_HANDLING_STATUS_OBSOLETE = -3;
+    private static final int MESSAGE_HANDLING_STATUS_DEFERRED = -4;
+    private static final int MESSAGE_HANDLING_STATUS_DISCARD = -5;
+    private static final int MESSAGE_HANDLING_STATUS_LOOPED = -6;
+    private static final int MESSAGE_HANDLING_STATUS_HANDLING_ERROR = -7;
 
     private int messageHandlingStatus = 0;
 
     //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));
@@ -1540,7 +1398,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;
@@ -1559,7 +1417,7 @@
         return sb.toString();
     }
 
-    WifiLinkLayerStats getWifiLinkLayerStats(boolean dbg) {
+    WifiLinkLayerStats getWifiLinkLayerStats() {
         WifiLinkLayerStats stats = null;
         if (mWifiLinkLayerStatsSupported > 0) {
             String name = "wlan0";
@@ -1567,7 +1425,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;
@@ -1579,7 +1437,7 @@
             long mRxPkts = mFacade.getRxPackets(mInterfaceName);
             mWifiInfo.updatePacketRates(mTxPkts, mRxPkts);
         } else {
-            mWifiInfo.updatePacketRates(stats);
+            mWifiInfo.updatePacketRates(stats, lastLinkLayerStatsUpdate);
         }
         return stats;
     }
@@ -1627,26 +1485,22 @@
 
         Set<Integer> freqs = null;
         if (settings != null && settings.channelSet != null) {
-            freqs = new HashSet<Integer>();
+            freqs = new HashSet<>();
             for (WifiChannel channel : settings.channelSet) {
                 freqs.add(channel.freqMHz);
             }
         }
 
-        // Retrieve the list of hidden networkId's to scan for.
-        Set<Integer> hiddenNetworkIds = mWifiConfigManager.getHiddenConfiguredNetworkIds();
+        // Retrieve the list of hidden network SSIDs to scan for.
+        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks =
+                mWifiConfigManager.retrieveHiddenNetworkList();
 
         // call wifi native to start the scan
-        if (startScanNative(freqs, hiddenNetworkIds, workSource)) {
+        if (startScanNative(freqs, hiddenNetworks, workSource)) {
             // a full scan covers everything, clearing scan request buffer
             if (freqs == null)
                 mBufferedScanMsg.clear();
             messageHandlingStatus = MESSAGE_HANDLING_STATUS_OK;
-            if (workSource != null) {
-                // External worksource was passed along the scan request,
-                // hence always send a broadcast
-                mSendScanResultsBroadcast = true;
-            }
             return;
         }
 
@@ -1691,7 +1545,8 @@
     /**
      * return true iff scan request is accepted
      */
-    private boolean startScanNative(final Set<Integer> freqs, Set<Integer> hiddenNetworkIds,
+    private boolean startScanNative(final Set<Integer> freqs,
+            List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList,
             WorkSource workSource) {
         WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
         if (freqs == null) {
@@ -1706,25 +1561,28 @@
         }
         settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
                 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
-        if (hiddenNetworkIds != null && hiddenNetworkIds.size() > 0) {
-            int i = 0;
-            settings.hiddenNetworkIds = new int[hiddenNetworkIds.size()];
-            for (Integer netId : hiddenNetworkIds) {
-                settings.hiddenNetworkIds[i++] = netId;
-            }
-        }
+
+        settings.hiddenNetworks =
+                hiddenNetworkList.toArray(
+                        new WifiScanner.ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
+
         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) {
                 }
             };
@@ -1749,7 +1607,7 @@
     /**
      * TODO: doc
      */
-    public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
+    public void setHostApRunning(SoftApModeConfiguration wifiConfig, boolean enable) {
         if (enable) {
             sendMessage(CMD_START_AP, wifiConfig);
         } else {
@@ -1828,19 +1686,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);
             }
         }
 
@@ -1848,7 +1706,7 @@
     }
 
     public boolean isLinkDebouncing() {
-        return linkDebouncing;
+        return mIsLinkDebouncing;
     }
 
     /**
@@ -1873,19 +1731,8 @@
     /**
      * TODO: doc
      */
-    public void setDriverStart(boolean enable) {
-        if (enable) {
-            sendMessage(CMD_START_DRIVER);
-        } else {
-            sendMessage(CMD_STOP_DRIVER);
-        }
-    }
-
-    /**
-     * 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);
     }
 
@@ -1902,7 +1749,7 @@
      */
     public List<ScanResult> syncGetScanResultsList() {
         synchronized (mScanResultsLock) {
-            List<ScanResult> scanList = new ArrayList<ScanResult>();
+            List<ScanResult> scanList = new ArrayList<>();
             for (ScanDetail result : mScanResults) {
                 scanList.add(new ScanResult(result.getScanResult()));
             }
@@ -1910,30 +1757,10 @@
         }
     }
 
-    public int syncAddPasspointManagementObject(AsyncChannel channel, String managementObject) {
-        Message resultMsg =
-                channel.sendMessageSynchronously(CMD_ADD_PASSPOINT_MO, managementObject);
-        int result = resultMsg.arg1;
-        resultMsg.recycle();
-        return result;
-    }
-
-    public int syncModifyPasspointManagementObject(AsyncChannel channel, String fqdn,
-                                                   List<PasspointManagementObjectDefinition>
-                                                           managementObjectDefinitions) {
-        Bundle bundle = new Bundle();
-        bundle.putString("FQDN", fqdn);
-        bundle.putParcelableList("MOS", managementObjectDefinitions);
-        Message resultMsg = channel.sendMessageSynchronously(CMD_MODIFY_PASSPOINT_MO, bundle);
-        int result = resultMsg.arg1;
-        resultMsg.recycle();
-        return result;
-    }
-
     public boolean syncQueryPasspointIcon(AsyncChannel channel, long bssid, String fileName) {
         Bundle bundle = new Bundle();
-        bundle.putLong("BSSID", bssid);
-        bundle.putString("FILENAME", fileName);
+        bundle.putLong(EXTRA_OSU_ICON_QUERY_BSSID, bssid);
+        bundle.putString(EXTRA_OSU_ICON_QUERY_FILENAME, fileName);
         Message resultMsg = channel.sendMessageSynchronously(CMD_QUERY_OSU_ICON, bundle);
         int result = resultMsg.arg1;
         resultMsg.recycle();
@@ -2025,21 +1852,6 @@
         }
     }
 
-    /**
-     * Check if Carrier networks have been configured synchronously
-     *
-     * @param channel
-     * @return
-     */
-    public boolean syncHasCarrierConfiguredNetworks(
-            int uuid, AsyncChannel channel) {
-        Message resultMsg = channel.sendMessageSynchronously(
-                CMD_HAS_CARRIER_CONFIGURED_NETWORKS, uuid);
-        boolean result = resultMsg.obj != null && (boolean) resultMsg.obj;
-        resultMsg.recycle();
-        return result;
-    }
-
     public List<WifiConfiguration> syncGetPrivilegedConfiguredNetwork(AsyncChannel channel) {
         Message resultMsg = channel.sendMessageSynchronously(
                 CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS);
@@ -2050,7 +1862,53 @@
 
     public WifiConfiguration syncGetMatchingWifiConfig(ScanResult scanResult, AsyncChannel channel) {
         Message resultMsg = channel.sendMessageSynchronously(CMD_GET_MATCHING_CONFIG, scanResult);
-        return (WifiConfiguration) resultMsg.obj;
+        WifiConfiguration config = (WifiConfiguration) resultMsg.obj;
+        resultMsg.recycle();
+        return config;
+    }
+
+    /**
+     * Add or update a Passpoint configuration synchronously.
+     *
+     * @param channel Channel for communicating with the state machine
+     * @param config The configuration to add or update
+     * @return true on success
+     */
+    public boolean syncAddOrUpdatePasspointConfig(AsyncChannel channel,
+            PasspointConfiguration config, int uid) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG,
+                uid, 0, config);
+        boolean result = (resultMsg.arg1 == SUCCESS);
+        resultMsg.recycle();
+        return result;
+    }
+
+    /**
+     * Remove a Passpoint configuration synchronously.
+     *
+     * @param channel Channel for communicating with the state machine
+     * @param fqdn The FQDN of the Passpoint configuration to remove
+     * @return true on success
+     */
+    public boolean syncRemovePasspointConfig(AsyncChannel channel, String fqdn) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_REMOVE_PASSPOINT_CONFIG,
+                fqdn);
+        boolean result = (resultMsg.arg1 == SUCCESS);
+        resultMsg.recycle();
+        return result;
+    }
+
+    /**
+     * Get the list of installed Passpoint configurations synchronously.
+     *
+     * @param channel Channel for communicating with the state machine
+     * @return List of {@link PasspointConfiguration}
+     */
+    public List<PasspointConfiguration> syncGetPasspointConfigs(AsyncChannel channel) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_PASSPOINT_CONFIGS);
+        List<PasspointConfiguration> result = (List<PasspointConfiguration>) resultMsg.obj;
+        resultMsg.recycle();
+        return result;
     }
 
     /**
@@ -2131,7 +1989,7 @@
      */
     public boolean syncDisableNetwork(AsyncChannel channel, int netId) {
         Message resultMsg = channel.sendMessageSynchronously(WifiManager.DISABLE_NETWORK, netId);
-        boolean result = (resultMsg.arg1 != WifiManager.DISABLE_NETWORK_FAILED);
+        boolean result = (resultMsg.what != WifiManager.DISABLE_NETWORK_FAILED);
         resultMsg.recycle();
         return result;
     }
@@ -2141,35 +1999,14 @@
      *
      * @return a hex string representation of the WPS-NFC configuration token
      */
-    public String syncGetWpsNfcConfigurationToken(int netId) {
-        return mWifiNative.getNfcWpsConfigurationToken(netId);
-    }
-
-    /**
-     * Blacklist a BSSID. This will avoid the AP if there are
-     * alternate APs to connect
-     *
-     * @param bssid BSSID of the network
-     */
-    public void addToBlacklist(String bssid) {
-        sendMessage(CMD_BLACKLIST_NETWORK, bssid);
-    }
-
-    /**
-     * Clear the blacklist list
-     */
-    public void clearBlacklist() {
-        sendMessage(CMD_CLEAR_BLACKLIST);
+    public String syncGetCurrentNetworkWpsNfcConfigurationToken() {
+        return mWifiNative.getCurrentNetworkWpsNfcConfigurationToken();
     }
 
     public void enableRssiPolling(boolean enabled) {
         sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
     }
 
-    public void enableAllNetworks() {
-        sendMessage(CMD_ENABLE_ALL_NETWORKS);
-    }
-
     /**
      * Start filtering Multicast v4 packets
      */
@@ -2215,22 +2052,6 @@
         }
     }
 
-
-    /**
-     * Set the operational frequency band
-     *
-     * @param band
-     * @param persist {@code true} if the setting should be remembered.
-     */
-    public void setFrequencyBand(int band, boolean persist) {
-        if (persist) {
-            Settings.Global.putInt(mContext.getContentResolver(),
-                    Settings.Global.WIFI_FREQUENCY_BAND,
-                    band);
-        }
-        sendMessage(CMD_SET_FREQUENCY_BAND, band, 0);
-    }
-
     /**
      * Enable TDLS for a specific MAC address
      */
@@ -2240,20 +2061,6 @@
     }
 
     /**
-     * Returns the operational frequency band
-     */
-    public int getFrequencyBand() {
-        return mFrequencyBand.get();
-    }
-
-    /**
-     * Returns the wifi configuration file
-     */
-    public String getConfigFile() {
-        return mWifiConfigManager.getConfigFile();
-    }
-
-    /**
      * Send a message indicating bluetooth adapter connection state changed
      */
     public void sendBluetoothAdapterStateChange(int state) {
@@ -2333,13 +2140,6 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if ((args != null) && args.length > 1 && WifiMetrics.PROTO_DUMP_ARG.equals(args[0])
-                && WifiMetrics.CLEAN_DUMP_ARG.equals(args[1])) {
-            // Dump only wifi metrics serialized proto bytes (base64)
-            updateWifiMetrics();
-            mWifiMetrics.dump(fd, pw, args);
-            return;
-        }
         super.dump(fd, pw, args);
         mSupplicantStateTracker.dump(fd, pw, args);
         pw.println("mLinkProperties " + mLinkProperties);
@@ -2352,7 +2152,6 @@
         pw.println("mOperationalMode " + mOperationalMode);
         pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
         pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
-        pw.println("Supplicant status " + mWifiNative.status(true));
         if (mCountryCode.getCountryCodeSentToDriver() != null) {
             pw.println("CountryCode sent to driver " + mCountryCode.getCountryCodeSentToDriver());
         } else {
@@ -2363,14 +2162,6 @@
                 pw.println("CountryCode was 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 {
@@ -2384,18 +2175,18 @@
         }
         pw.println("Wlan Wake Reasons:" + mWifiNative.getWlanWakeReasonCount());
         pw.println();
-        updateWifiMetrics();
-        mWifiMetrics.dump(fd, pw, args);
-        pw.println();
 
         mWifiConfigManager.dump(fd, pw, args);
         pw.println();
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_USER_ACTION);
-        mWifiLogger.dump(fd, pw, args);
-        mWifiQualifiedNetworkSelector.dump(fd, pw, args);
+        mPasspointManager.dump(pw);
+        pw.println();
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_USER_ACTION);
+        mWifiDiagnostics.dump(fd, pw, args);
         dumpIpManager(fd, pw, args);
         if (mWifiConnectivityManager != null) {
             mWifiConnectivityManager.dump(fd, pw, args);
+        } else {
+            pw.println("mWifiConnectivityManager is not initialized");
         }
     }
 
@@ -2403,6 +2194,14 @@
         sendMessage(CMD_USER_SWITCH, userId);
     }
 
+    public void handleUserUnlock(int userId) {
+        sendMessage(CMD_USER_UNLOCK, userId);
+    }
+
+    public void handleUserStop(int userId) {
+        sendMessage(CMD_USER_STOP, userId);
+    }
+
     /**
      * ******************************************************
      * Internal private functions
@@ -2411,19 +2210,19 @@
 
     private void logStateAndMessage(Message message, State state) {
         messageHandlingStatus = 0;
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd(" " + state.getClass().getSimpleName() + " " + getLogRecString(message));
         }
     }
 
-    /**
-     * helper, prints the milli time since boot wi and w/o suspended time
-     */
-    String printTime() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(" rt=").append(SystemClock.uptimeMillis());
-        sb.append("/").append(SystemClock.elapsedRealtime());
-        return sb.toString();
+    @Override
+    protected boolean recordLogRec(Message msg) {
+        switch (msg.what) {
+            case CMD_RSSI_POLL:
+                return mVerboseLoggingEnabled;
+            default:
+                return true;
+        }
     }
 
     /**
@@ -2432,6 +2231,7 @@
      * @param msg that was processed
      * @return information to be logged as a String
      */
+    @Override
     protected String getLogRecString(Message msg) {
         WifiConfiguration config;
         Long now;
@@ -2448,24 +2248,11 @@
         if (msg.sendingUid > 0 && msg.sendingUid != Process.WIFI_UID) {
             sb.append(" uid=" + msg.sendingUid);
         }
-        sb.append(" ").append(printTime());
+        sb.append(" rt=").append(mClock.getUptimeSinceBootMillis());
+        sb.append("/").append(mClock.getElapsedSinceBootMillis());
         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(" ");
@@ -2511,29 +2298,29 @@
                 }
                 break;
             case WifiManager.SAVE_NETWORK:
-            case WifiStateMachine.CMD_AUTO_SAVE_NETWORK:
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                if (lastSavedConfigurationAttempt != null) {
-                    sb.append(" ").append(lastSavedConfigurationAttempt.configKey());
-                    sb.append(" nid=").append(lastSavedConfigurationAttempt.networkId);
-                    if (lastSavedConfigurationAttempt.hiddenSSID) {
+                config = (WifiConfiguration) msg.obj;
+                if (config != null) {
+                    sb.append(" ").append(config.configKey());
+                    sb.append(" nid=").append(config.networkId);
+                    if (config.hiddenSSID) {
                         sb.append(" hidden");
                     }
-                    if (lastSavedConfigurationAttempt.preSharedKey != null
-                            && !lastSavedConfigurationAttempt.preSharedKey.equals("*")) {
+                    if (config.preSharedKey != null
+                            && !config.preSharedKey.equals("*")) {
                         sb.append(" hasPSK");
                     }
-                    if (lastSavedConfigurationAttempt.ephemeral) {
+                    if (config.ephemeral) {
                         sb.append(" ephemeral");
                     }
-                    if (lastSavedConfigurationAttempt.selfAdded) {
+                    if (config.selfAdded) {
                         sb.append(" selfAdded");
                     }
-                    sb.append(" cuid=").append(lastSavedConfigurationAttempt.creatorUid);
-                    sb.append(" suid=").append(lastSavedConfigurationAttempt.lastUpdateUid);
+                    sb.append(" cuid=").append(config.creatorUid);
+                    sb.append(" suid=").append(config.lastUpdateUid);
                 }
                 break;
             case WifiManager.FORGET_NETWORK:
@@ -2541,32 +2328,33 @@
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                if (lastForgetConfigurationAttempt != null) {
-                    sb.append(" ").append(lastForgetConfigurationAttempt.configKey());
-                    sb.append(" nid=").append(lastForgetConfigurationAttempt.networkId);
-                    if (lastForgetConfigurationAttempt.hiddenSSID) {
+                config = (WifiConfiguration) msg.obj;
+                if (config != null) {
+                    sb.append(" ").append(config.configKey());
+                    sb.append(" nid=").append(config.networkId);
+                    if (config.hiddenSSID) {
                         sb.append(" hidden");
                     }
-                    if (lastForgetConfigurationAttempt.preSharedKey != null) {
+                    if (config.preSharedKey != null) {
                         sb.append(" hasPSK");
                     }
-                    if (lastForgetConfigurationAttempt.ephemeral) {
+                    if (config.ephemeral) {
                         sb.append(" ephemeral");
                     }
-                    if (lastForgetConfigurationAttempt.selfAdded) {
+                    if (config.selfAdded) {
                         sb.append(" selfAdded");
                     }
-                    sb.append(" cuid=").append(lastForgetConfigurationAttempt.creatorUid);
-                    sb.append(" suid=").append(lastForgetConfigurationAttempt.lastUpdateUid);
+                    sb.append(" cuid=").append(config.creatorUid);
+                    sb.append(" suid=").append(config.lastUpdateUid);
                     WifiConfiguration.NetworkSelectionStatus netWorkSelectionStatus =
-                            lastForgetConfigurationAttempt.getNetworkSelectionStatus();
+                            config.getNetworkSelectionStatus();
                     sb.append(" ajst=").append(
                             netWorkSelectionStatus.getNetworkStatusString());
                 }
                 break;
             case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                 sb.append(" ");
-                sb.append(Integer.toString(msg.arg1));
+                sb.append(" timedOut=" + Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
                 String bssid = (String) msg.obj;
@@ -2590,7 +2378,7 @@
                 sb.append(String.format(" bcn=%d", mRunningBeaconCount));
                 sb.append(String.format(" con=%d", mConnectionReqCount));
                 sb.append(String.format(" untrustedcn=%d", mUntrustedReqCount));
-                key = mWifiConfigManager.getLastSelectedConfiguration();
+                key = mWifiConfigManager.getLastSelectedNetworkConfigKey();
                 if (key != null) {
                     sb.append(" last=").append(key);
                 }
@@ -2608,7 +2396,7 @@
                 if (config != null) {
                     sb.append(" ").append(config.configKey());
                 }
-                key = mWifiConfigManager.getLastSelectedConfiguration();
+                key = mWifiConfigManager.getLastSelectedNetworkConfigKey();
                 if (key != null) {
                     sb.append(" last=").append(key);
                 }
@@ -2625,7 +2413,7 @@
                 if (mTargetRoamBSSID != null) {
                     sb.append(" Target=").append(mTargetRoamBSSID);
                 }
-                sb.append(" roam=").append(Boolean.toString(mAutoRoaming));
+                sb.append(" roam=").append(Boolean.toString(mIsAutoRoaming));
                 break;
             case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                 if (msg.obj != null) {
@@ -2640,44 +2428,10 @@
                     sb.append(" freq=").append(mWifiInfo.getFrequency());
                     sb.append(" rssi=").append(mWifiInfo.getRssi());
                 }
-                if (linkDebouncing) {
+                if (isLinkDebouncing()) {
                     sb.append(" debounce");
                 }
                 break;
-            case WifiMonitor.SSID_TEMP_DISABLED:
-            case WifiMonitor.SSID_REENABLED:
-                sb.append(" nid=").append(msg.arg1);
-                if (msg.obj != null) {
-                    sb.append(" ").append((String) msg.obj);
-                }
-                config = getCurrentWifiConfiguration();
-                if (config != null) {
-                    WifiConfiguration.NetworkSelectionStatus netWorkSelectionStatus =
-                            config.getNetworkSelectionStatus();
-                    sb.append(" cur=").append(config.configKey());
-                    sb.append(" ajst=").append(netWorkSelectionStatus.getNetworkStatusString());
-                    if (config.selfAdded) {
-                        sb.append(" selfAdded");
-                    }
-                    if (config.status != 0) {
-                        sb.append(" st=").append(config.status);
-                        sb.append(" rs=").append(
-                                netWorkSelectionStatus.getNetworkDisableReasonString());
-                    }
-                    if (config.lastConnected != 0) {
-                        now = System.currentTimeMillis();
-                        sb.append(" lastconn=").append(now - config.lastConnected).append("(ms)");
-                    }
-                    if (mLastBssid != null) {
-                        sb.append(" lastbssid=").append(mLastBssid);
-                    }
-                    if (mWifiInfo.getFrequency() != -1) {
-                        sb.append(" freq=").append(mWifiInfo.getFrequency());
-                        sb.append(" rssi=").append(mWifiInfo.getRssi());
-                        sb.append(" bssid=").append(mWifiInfo.getBSSID());
-                    }
-                }
-                break;
             case CMD_RSSI_POLL:
             case CMD_UNWANTED_NETWORK:
             case WifiManager.RSSI_PKTCNT_FETCH:
@@ -2703,22 +2457,17 @@
                 if (report != null) {
                     sb.append(" ").append(report);
                 }
-                if (mWifiScoreReport != null) {
-                    sb.append(mWifiScoreReport.getReport());
-                }
-                if (mConnectedModeGScanOffloadStarted) {
-                    sb.append(" offload-started periodMilli " + mGScanPeriodMilli);
-                } else {
-                    sb.append(" offload-stopped");
+                if (mWifiScoreReport.isLastReportValid()) {
+                    sb.append(mWifiScoreReport.getLastReport());
                 }
                 break;
-            case CMD_AUTO_CONNECT:
+            case CMD_START_CONNECT:
             case WifiManager.CONNECT_NETWORK:
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                config = mWifiConfigManager.getWifiConfiguration(msg.arg1);
+                config = mWifiConfigManager.getConfiguredNetwork(msg.arg1);
                 if (config != null) {
                     sb.append(" ").append(config.configKey());
                     if (config.visibility != null) {
@@ -2728,7 +2477,7 @@
                 if (mTargetRoamBSSID != null) {
                     sb.append(" ").append(mTargetRoamBSSID);
                 }
-                sb.append(" roam=").append(Boolean.toString(mAutoRoaming));
+                sb.append(" roam=").append(Boolean.toString(mIsAutoRoaming));
                 config = getCurrentWifiConfiguration();
                 if (config != null) {
                     sb.append(config.configKey());
@@ -2737,14 +2486,14 @@
                     }
                 }
                 break;
-            case CMD_AUTO_ROAM:
+            case CMD_START_ROAM:
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 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);
@@ -2758,7 +2507,7 @@
                 if (mTargetRoamBSSID != null) {
                     sb.append(" ").append(mTargetRoamBSSID);
                 }
-                sb.append(" roam=").append(Boolean.toString(mAutoRoaming));
+                sb.append(" roam=").append(Boolean.toString(mIsAutoRoaming));
                 sb.append(" fail count=").append(Integer.toString(mRoamFailCount));
                 break;
             case CMD_ADD_OR_UPDATE_NETWORK:
@@ -2792,11 +2541,11 @@
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                key = mWifiConfigManager.getLastSelectedConfiguration();
+                key = mWifiConfigManager.getLastSelectedNetworkConfigKey();
                 if (key != null) {
                     sb.append(" last=").append(key);
                 }
-                config = mWifiConfigManager.getWifiConfiguration(msg.arg1);
+                config = mWifiConfigManager.getConfiguredNetwork(msg.arg1);
                 if (config != null && (key == null || !config.configKey().equals(key))) {
                     sb.append(" target=").append(key);
                 }
@@ -2806,14 +2555,7 @@
                 sb.append(Integer.toString(msg.arg1));
                 sb.append(" ");
                 sb.append(Integer.toString(msg.arg2));
-                sb.append(" num=").append(mWifiConfigManager.getConfiguredNetworksSize());
-                break;
-            case CMD_HAS_CARRIER_CONFIGURED_NETWORKS:
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg1));
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg2));
-                sb.append(" hasCarrierNetworks=").append(mWifiConfigManager.hasCarrierNetworks());
+                sb.append(" num=").append(mWifiConfigManager.getConfiguredNetworks().size());
                 break;
             case DhcpClient.CMD_PRE_DHCP_ACTION:
                 sb.append(" ");
@@ -2870,7 +2612,8 @@
                 sb.append(" failures: ");
                 sb.append(Integer.toString(count));
                 sb.append("/");
-                sb.append(Integer.toString(mWifiConfigManager.getMaxDhcpRetries()));
+                sb.append(Integer.toString(mFacade.getIntegerSetting(
+                        mContext, Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT, 0)));
                 if (mWifiInfo.getBSSID() != null) {
                     sb.append(" ").append(mWifiInfo.getBSSID());
                 }
@@ -2911,6 +2654,13 @@
                 sb.append(Integer.toString(msg.arg2));
                 sb.append(" cur=").append(disconnectingWatchdogCount);
                 break;
+            case CMD_DISABLE_P2P_WATCHDOG_TIMER:
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg1));
+                sb.append(" ");
+                sb.append(Integer.toString(msg.arg2));
+                sb.append(" cur=").append(mDisableP2pWatchdogCount);
+                break;
             case CMD_START_RSSI_MONITORING_OFFLOAD:
             case CMD_STOP_RSSI_MONITORING_OFFLOAD:
             case CMD_RSSI_THRESHOLD_BREACH:
@@ -2956,7 +2706,7 @@
 
     private void handleScreenStateChanged(boolean screenOn) {
         mScreenOn = screenOn;
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd(" handleScreenStateChanged Enter: screenOn=" + screenOn
                     + " mUserWantsSuspendOpt=" + mUserWantsSuspendOpt
                     + " state " + getCurrentState().getName()
@@ -2976,9 +2726,8 @@
                 sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, shouldReleaseWakeLock);
             }
         }
-        mScreenBroadcastReceived.set(true);
 
-        getWifiLinkLayerStats(false);
+        getWifiLinkLayerStats();
         mOnTimeScreenStateChange = mOnTime;
         lastScreenStateChangeTimeStamp = lastLinkLayerStatsUpdate;
 
@@ -2988,7 +2737,7 @@
             mWifiConnectivityManager.handleScreenStateChanged(screenOn);
         }
 
-        if (DBG) log("handleScreenStateChanged Exit: " + screenOn);
+        if (mVerboseLoggingEnabled) log("handleScreenStateChanged Exit: " + screenOn);
     }
 
     private void checkAndSetConnectivityInstance() {
@@ -2997,28 +2746,8 @@
         }
     }
 
-
-    /**
-     * Set the frequency band from the system setting value, if any.
-     */
-    private void setFrequencyBand() {
-        int band = WifiManager.WIFI_FREQUENCY_BAND_AUTO;
-
-        if (mWifiNative.setBand(band)) {
-            mFrequencyBand.set(band);
-            if (mWifiConnectivityManager != null) {
-                mWifiConnectivityManager.setUserPreferredBand(band);
-            }
-            if (DBG) {
-                logd("done set frequency band " + band);
-            }
-        } else {
-            loge("Failed to set frequency band " + band);
-        }
-    }
-
     private void setSuspendOptimizationsNative(int reason, boolean enabled) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             log("setSuspendOptimizationsNative: " + reason + " " + enabled
                     + " -want " + mUserWantsSuspendOpt.get()
                     + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
@@ -3032,7 +2761,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()
@@ -3048,13 +2777,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) {
@@ -3072,7 +2801,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);
@@ -3081,7 +2810,7 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private void setWifiApState(int wifiApState, int reason) {
+    private void setWifiApState(int wifiApState, int reason, String ifaceName, int mode) {
         final int previousWifiApState = mWifiApState.get();
 
         try {
@@ -3097,7 +2826,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);
@@ -3108,6 +2837,12 @@
             intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
         }
 
+        if (ifaceName == null) {
+            loge("Updating wifiApState with a null iface name");
+        }
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, ifaceName);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mode);
+
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
@@ -3122,7 +2857,7 @@
             return;
         }
 
-        mWifiConfigManager.trimANQPCache(false);
+        // TODO(b/31065385): mWifiConfigManager.trimANQPCache(false);
 
         boolean connected = mLastBssid != null;
         long activeBssid = 0L;
@@ -3135,45 +2870,17 @@
         }
 
         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
-                // Frame), these scanResult DTIM's are negative and ignored.
-                // <TODO> Cache these per BSSID, since dtim can change vary
-                NetworkDetail networkDetail = resultDetail.getNetworkDetail();
-                if (networkDetail != null && networkDetail.getDtimInterval() > 0) {
-                    List<WifiConfiguration> associatedWifiConfigurations =
-                            mWifiConfigManager.getSavedNetworkFromScanDetail(resultDetail);
-                    if (associatedWifiConfigurations != null) {
-                        for (WifiConfiguration associatedConf : associatedWifiConfigurations) {
-                            if (associatedConf != null) {
-                                associatedConf.dtimInterval = networkDetail.getDtimInterval();
-                            }
-                        }
-                    }
-                }
-            }
-            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,
             // just try to reconnect to the same SSID by triggering a roam
             // The third parameter 1 means roam not from network selection but debouncing
-            sendMessage(CMD_AUTO_ROAM, mLastNetworkId, 1, null);
+            sendMessage(CMD_START_ROAM, mLastNetworkId, 1, null);
         }
     }
 
@@ -3184,29 +2891,16 @@
         Integer newRssi = null;
         Integer newLinkSpeed = null;
         Integer newFrequency = null;
-
-        String signalPoll = mWifiNative.signalPoll();
-
-        if (signalPoll != null) {
-            String[] lines = signalPoll.split("\n");
-            for (String line : lines) {
-                String[] prop = line.split("=");
-                if (prop.length < 2) continue;
-                try {
-                    if (prop[0].equals("RSSI")) {
-                        newRssi = Integer.parseInt(prop[1]);
-                    } else if (prop[0].equals("LINKSPEED")) {
-                        newLinkSpeed = Integer.parseInt(prop[1]);
-                    } else if (prop[0].equals("FREQUENCY")) {
-                        newFrequency = Integer.parseInt(prop[1]);
-                    }
-                } catch (NumberFormatException e) {
-                    //Ignore, defaults on rssi and linkspeed are assigned
-                }
-            }
+        WifiNative.SignalPollResult pollResult = mWifiNative.signalPoll();
+        if (pollResult == null) {
+            return;
         }
 
-        if (DBG) {
+        newRssi = pollResult.currentRssi;
+        newLinkSpeed = pollResult.txBitrate;
+        newFrequency = pollResult.associationFrequency;
+
+        if (mVerboseLoggingEnabled) {
             logd("fetchRssiLinkSpeedAndFrequencyNative rssi=" + newRssi +
                  " linkspeed=" + newLinkSpeed + " freq=" + newFrequency);
         }
@@ -3219,10 +2913,6 @@
             if (newRssi > 0) newRssi -= 256;
             mWifiInfo.setRssi(newRssi);
             /*
-             * Log the rssi poll value in metrics
-             */
-            mWifiMetrics.incrementRssiPollRssiCount(newRssi);
-            /*
              * Rather then sending the raw RSSI out every time it
              * changes, we precalculate the signal level that would
              * be displayed in the status bar, and only send the
@@ -3255,7 +2945,13 @@
             }
             mWifiInfo.setFrequency(newFrequency);
         }
-        mWifiConfigManager.updateConfiguration(mWifiInfo);
+        mWifiConfigManager.updateScanDetailCacheFromWifiInfo(mWifiInfo);
+        /*
+         * Increment various performance metrics
+         */
+        if (newRssi != null && newLinkSpeed != null && newFrequency != null) {
+            mWifiMetrics.handlePollResult(mWifiInfo);
+        }
     }
 
     // Polling has completed, hence we wont have a score anymore
@@ -3264,46 +2960,11 @@
         mWifiInfo.txSuccessRate = 0;
         mWifiInfo.txRetriesRate = 0;
         mWifiInfo.rxSuccessRate = 0;
-        mWifiScoreReport = null;
-    }
-
-    // Object holding most recent wifi score report and bad Linkspeed count
-    WifiScoreReport mWifiScoreReport = null;
-
-    public double getTxPacketRate() {
-        return mWifiInfo.txSuccessRate;
-    }
-
-    public double getRxPacketRate() {
-        return mWifiInfo.rxSuccessRate;
-    }
-
-    /**
-     * Fetch TX packet counters on current connection
-     */
-    private void fetchPktcntNative(RssiPacketCountInfo info) {
-        String pktcntPoll = mWifiNative.pktcntPoll();
-
-        if (pktcntPoll != null) {
-            String[] lines = pktcntPoll.split("\n");
-            for (String line : lines) {
-                String[] prop = line.split("=");
-                if (prop.length < 2) continue;
-                try {
-                    if (prop[0].equals("TXGOOD")) {
-                        info.txgood = Integer.parseInt(prop[1]);
-                    } else if (prop[0].equals("TXBAD")) {
-                        info.txbad = Integer.parseInt(prop[1]);
-                    }
-                } catch (NumberFormatException e) {
-                    // Ignore
-                }
-            }
-        }
+        mWifiScoreReport.reset();
     }
 
     private void updateLinkProperties(LinkProperties newLp) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             log("Link configuration changed for netId: " + mLastNetworkId
                     + " old: " + mLinkProperties + " new: " + newLp);
         }
@@ -3319,7 +2980,7 @@
             sendLinkConfigurationChangedBroadcast();
         }
 
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             StringBuilder sb = new StringBuilder();
             sb.append("updateLinkProperties nid: " + mLastNetworkId);
             sb.append(" state: " + getNetworkDetailedState());
@@ -3358,7 +3019,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());
                     }
@@ -3366,6 +3027,7 @@
                     /* The gateway's MAC address is known */
                     if ((address == null) && (timeout > 0)) {
                         boolean reachable = false;
+                        TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
                         try {
                             reachable = gateway.isReachable(timeout);
                         } catch (Exception e) {
@@ -3373,10 +3035,11 @@
                                     + gateway.getHostAddress());
 
                         } finally {
+                            TrafficStats.clearThreadStatsTag();
                             if (reachable == true) {
 
                                 address = macAddressFromRoute(gateway.getHostAddress());
-                                if (DBG) {
+                                if (mVerboseLoggingEnabled) {
                                     logd("updateDefaultRouteMacAddress reachable (tried again) :"
                                             + gateway.getHostAddress() + " found " + address);
                                 }
@@ -3384,7 +3047,7 @@
                         }
                     }
                     if (address != null) {
-                        mWifiConfigManager.setDefaultGwMacAddress(mLastNetworkId, address);
+                        mWifiConfigManager.setNetworkDefaultGwMacAddress(mLastNetworkId, address);
                     }
                 }
             }
@@ -3392,13 +3055,6 @@
         return address;
     }
 
-    void sendScanResultsAvailableBroadcast(boolean scanSucceeded) {
-        Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
     private void sendRssiChangeBroadcast(final int newRssi) {
         try {
             mBatteryStats.noteWifiRssiChanged(newRssi);
@@ -3475,7 +3131,7 @@
     private boolean setNetworkDetailedState(NetworkInfo.DetailedState state) {
         boolean hidden = false;
 
-        if (linkDebouncing || isRoaming()) {
+        if (isLinkDebouncing() || mIsAutoRoaming) {
             // 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.
@@ -3490,7 +3146,7 @@
             //
             hidden = true;
         }
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             log("setDetailed state, old ="
                     + mNetworkInfo.getDetailedState() + " and new state=" + state
                     + " hidden=" + hidden);
@@ -3499,7 +3155,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());
@@ -3537,46 +3193,40 @@
         // 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
         if (SupplicantState.isConnecting(state)) {
-            mWifiInfo.setNetworkId(stateChangeResult.networkId);
+            mWifiInfo.setNetworkId(lookupFrameworkNetworkId(stateChangeResult.networkId));
         } else {
             mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
         }
 
         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);
+        WifiConfiguration config = getCurrentWifiConfiguration();
+        if (config != null) {
+            // Set meteredHint to true if the access network type of the connecting/connected AP
+            // is a chargeable public network.
+            ScanDetailCache scanDetailCache = mWifiConfigManager.getScanDetailCacheForNetwork(
+                    config.networkId);
+            if (scanDetailCache != null) {
+                ScanDetail scanDetail = scanDetailCache.getScanDetail(stateChangeResult.BSSID);
+                if (scanDetail != null) {
+                    NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+                    if (networkDetail != null
+                            && networkDetail.getAnt() == NetworkDetail.Ant.ChargeablePublic) {
+                        mWifiInfo.setMeteredHint(true);
+                    }
                 }
             }
-        }
 
-        mWifiInfo.setSSID(stateChangeResult.wifiSsid);
-        mWifiInfo.setEphemeral(mWifiConfigManager.isEphemeral(mWifiInfo.getNetworkId()));
-        if (!mWifiInfo.getMeteredHint()) { // don't override the value if already set.
-            mWifiInfo.setMeteredHint(mWifiConfigManager.getMeteredHint(mWifiInfo.getNetworkId()));
+            mWifiInfo.setEphemeral(config.ephemeral);
+            if (!mWifiInfo.getMeteredHint()) { // don't override the value if already set.
+                mWifiInfo.setMeteredHint(config.meteredHint);
+            }
         }
 
         mSupplicantStateTracker.sendMessage(Message.obtain(message));
@@ -3589,31 +3239,32 @@
      * 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();
 
-        clearCurrentConfigBSSID("handleNetworkDisconnect");
+        clearTargetBssid("handleNetworkDisconnect");
 
         stopIpManager();
 
         /* Reset data structures */
-        mWifiScoreReport = null;
+        mWifiScoreReport.reset();
         mWifiInfo.reset();
-        linkDebouncing = false;
+        mIsLinkDebouncing = false;
         /* Reset roaming parameters */
-        mAutoRoaming = false;
+        mIsAutoRoaming = false;
 
         setNetworkDetailedState(DetailedState.DISCONNECTED);
         if (mNetworkAgent != null) {
             mNetworkAgent.sendNetworkInfo(mNetworkInfo);
             mNetworkAgent = null;
         }
-        mWifiConfigManager.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
 
         /* Clear network properties */
         clearLinkProperties();
@@ -3621,8 +3272,6 @@
         /* Cend event to CM & network change broadcast */
         sendNetworkStateChangeBroadcast(mLastBssid);
 
-        /* Cancel auto roam requests */
-        autoRoamSetBSSID(mLastNetworkId, "any");
         mLastBssid = null;
         registerDisconnected();
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
@@ -3633,7 +3282,10 @@
         * or when the driver is hung. Ensure supplicant is stopped here.
         */
         if (killSupplicant) {
-            mWifiMonitor.killSupplicant(mP2pSupported);
+            mWifiMonitor.stopAllMonitoring();
+            if (!mWifiNative.disableSupplicant()) {
+                loge("Failed to disable supplicant after connection loss");
+            }
         }
         mWifiNative.closeSupplicantConnection();
         sendSupplicantConnectionChangedBroadcast(false);
@@ -3660,7 +3312,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
@@ -3671,15 +3323,20 @@
         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);
+        } else {
+            // If the p2p service is not running, we can proceed directly.
+            sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+        }
     }
 
     void handlePostDhcpSetup() {
@@ -3687,32 +3344,64 @@
         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);
+    }
+
+    private static final long DIAGS_CONNECT_TIMEOUT_MILLIS = 60 * 1000;
+    private long mDiagsConnectionStartMillis = -1;
+    /**
+     * Inform other components that a new connection attempt is starting.
+     */
+    private void reportConnectionAttemptStart(
+            WifiConfiguration config, String targetBSSID, int roamType) {
+        mWifiMetrics.startConnectionEvent(config, targetBSSID, roamType);
+        mDiagsConnectionStartMillis = mClock.getElapsedSinceBootMillis();
+        mWifiDiagnostics.reportConnectionEvent(
+                mDiagsConnectionStartMillis, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        // TODO(b/35329124): Remove CMD_DIAGS_CONNECT_TIMEOUT, once WifiStateMachine
+        // grows a proper CONNECTING state.
+        sendMessageDelayed(CMD_DIAGS_CONNECT_TIMEOUT,
+                mDiagsConnectionStartMillis, DIAGS_CONNECT_TIMEOUT_MILLIS);
     }
 
     /**
-     * Inform other components (WifiMetrics, WifiLogger, etc.) that the current connection attempt
+     * Inform other components (WifiMetrics, WifiDiagnostics, etc.) that the current connection attempt
      * has concluded.
      */
     private void reportConnectionAttemptEnd(int level2FailureCode, int connectivityFailureCode) {
         mWifiMetrics.endConnectionEvent(level2FailureCode, connectivityFailureCode);
         switch (level2FailureCode) {
             case WifiMetrics.ConnectionEvent.FAILURE_NONE:
+                // Ideally, we'd wait until IP reachability has been confirmed. this code falls
+                // short in two ways:
+                // - at the time of the CMD_IP_CONFIGURATION_SUCCESSFUL event, we don't know if we
+                //   actually have ARP reachability. it might be better to wait until the wifi
+                //   network has been validated by IpManager.
+                // - in the case of a roaming event (intra-SSID), we probably trigger when L2 is
+                //   complete.
+                //
+                // TODO(b/34181219): Fix the above.
+                mWifiDiagnostics.reportConnectionEvent(
+                        mDiagsConnectionStartMillis, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+                break;
             case WifiMetrics.ConnectionEvent.FAILURE_REDUNDANT_CONNECTION_ATTEMPT:
-                // WifiLogger doesn't care about success, or pre-empted connections.
+            case WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED:
+                // WifiDiagnostics doesn't care about pre-empted connections, or cases
+                // where we failed to initiate a connection attempt with supplicant.
                 break;
             default:
-                mWifiLogger.reportConnectionFailure();
+                mWifiDiagnostics.reportConnectionEvent(
+                        mDiagsConnectionStartMillis, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         }
+        mDiagsConnectionStartMillis = -1;
     }
 
     private void handleIPv4Success(DhcpResults dhcpResults) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd("handleIPv4Success <" + dhcpResults.toString() + ">");
             logd("link address " + dhcpResults.ipAddress);
         }
@@ -3723,7 +3412,7 @@
             addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
         }
 
-        if (isRoaming()) {
+        if (mIsAutoRoaming) {
             int previousAddress = mWifiInfo.getIpAddress();
             int newAddress = NetworkUtils.inetAddressToInt(addr);
             if (previousAddress != newAddress) {
@@ -3757,12 +3446,6 @@
             } else {
                 // Clear the per BSSID failure count
                 result.numIpConfigFailures = 0;
-                // Clear the WHOLE BSSID blacklist, which means supplicant is free to retry
-                // any BSSID, even though it may already have a non zero ip failure count,
-                // this will typically happen if the user walks away and come back to his arrea
-                // TODO: implement blacklisting based on a timer, i.e. keep BSSID blacklisted
-                // in supplicant for a couple of hours or a day
-                mWifiConfigManager.clearBssidBlacklist();
             }
         }
     }
@@ -3770,8 +3453,8 @@
     private void handleIPv4Failure() {
         // 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) {
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_DHCP_FAILURE);
+        if (mVerboseLoggingEnabled) {
             int count = -1;
             WifiConfiguration config = getCurrentWifiConfiguration();
             if (config != null) {
@@ -3788,7 +3471,7 @@
                  mDhcpResults.clear();
              }
         }
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             logd("handleIPv4Failure");
         }
     }
@@ -3817,97 +3500,6 @@
         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;
-        }
-
-        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");
-        }
-
-        try {
-            mNwService.wifiFirmwareReload(mInterfaceName, "AP");
-            if (DBG) Log.d(TAG, "Firmware reloaded in AP mode");
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to reload AP firmware " + e);
-        }
-
-        if (!mWifiNative.startHal()) {
-            /* starting HAL is optional */
-            Log.e(TAG, "Failed to start HAL");
-        }
-        return true;
-    }
-
-    private byte[] macAddressFromString(String macString) {
-        String[] macBytes = macString.split(":");
-        if (macBytes.length != 6) {
-            throw new IllegalArgumentException("MAC address should be 6 bytes long!");
-        }
-        byte[] mac = new byte[6];
-        for (int i = 0; i < macBytes.length; i++) {
-            Integer hexVal = Integer.parseInt(macBytes[i], 16);
-            mac[i] = hexVal.byteValue();
-        }
-        return mac;
-    }
-
     /*
      * Read a MAC address in /proc/arp/table, used by WifistateMachine
      * so as to record MAC address of default gateway.
@@ -3987,6 +3579,7 @@
             }
         }
 
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("mConnectionReqCount " + mConnectionReqCount);
         }
@@ -4032,6 +3625,7 @@
             }
         }
 
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("mUntrustedReqCount " + mUntrustedReqCount);
         }
@@ -4056,11 +3650,33 @@
         }
     }
 
+    /**
+     * WifiStateMachine needs to enable/disable other services when wifi is in client mode.  This
+     * method allows WifiStateMachine to get these additional system services.
+     *
+     * At this time, this method is used to setup variables for P2P service and Wifi Aware.
+     */
+    private void getAdditionalWifiServiceInterfaces() {
+        // First set up Wifi Direct
+        if (mP2pSupported) {
+            IBinder s1 = mFacade.getService(Context.WIFI_P2P_SERVICE);
+            WifiP2pServiceImpl wifiP2pServiceImpl =
+                    (WifiP2pServiceImpl) IWifiP2pManager.Stub.asInterface(s1);
+
+            if (wifiP2pServiceImpl != null) {
+                mWifiP2pChannel = new AsyncChannel();
+                mWifiP2pChannel.connect(mContext, getHandler(),
+                        wifiP2pServiceImpl.getP2pStateMachineMessenger());
+            }
+        }
+    }
+
     /********************************************************
      * HSM states
      *******************************************************/
 
     class DefaultState extends State {
+
         @Override
         public boolean processMessage(Message message) {
             logStateAndMessage(message, this);
@@ -4070,8 +3686,19 @@
                     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);
+                            // since the p2p channel is connected, we should enable p2p if we are in
+                            // connect mode.  We may not be in connect mode yet, we may have just
+                            // set the operational mode and started to set up for connect mode.
+                            if (mOperationalMode == CONNECT_MODE) {
+                                // This message will only be handled if we are in Connect mode.
+                                // If we are not in connect mode yet, this will be dropped and the
+                                // ConnectMode.enter method will call to enable p2p.
+                                sendMessage(CMD_ENABLE_P2P);
+                            }
                         } else {
+                            // TODO: We should probably do some cleanup or attempt a retry
+                            // b/34283611
                             loge("WifiP2pService connection failure, error=" + message.arg1);
                         }
                     } else {
@@ -4083,7 +3710,7 @@
                     AsyncChannel ac = (AsyncChannel) message.obj;
                     if (ac == mWifiP2pChannel) {
                         loge("WifiP2pService channel lost, message.arg1 =" + message.arg1);
-                        //TODO: Re-establish connection to state machine after a delay
+                        //TODO: Re-establish connection to state machine after a delay (b/34283611)
                         // mWifiP2pChannel.connect(mContext, getHandler(),
                         // mWifiP2pManager.getMessenger());
                     }
@@ -4093,25 +3720,37 @@
                     mBluetoothConnectionActive = (message.arg1 !=
                             BluetoothAdapter.STATE_DISCONNECTED);
                     break;
-                    /* Synchronous call returns */
-                case CMD_PING_SUPPLICANT:
                 case CMD_ENABLE_NETWORK:
+                    boolean disableOthers = message.arg2 == 1;
+                    int netId = message.arg1;
+                    boolean ok = mWifiConfigManager.enableNetwork(
+                            netId, disableOthers, message.sendingUid);
+                    if (!ok) {
+                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+                    }
+                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+                    break;
                 case CMD_ADD_OR_UPDATE_NETWORK:
-                case CMD_REMOVE_NETWORK:
+                    WifiConfiguration config = (WifiConfiguration) message.obj;
+                    NetworkUpdateResult result =
+                            mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
+                    if (!result.isSuccess()) {
+                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+                    }
+                    replyToMessage(message, message.what, result.getNetworkId());
+                    break;
                 case CMD_SAVE_CONFIG:
                     replyToMessage(message, message.what, FAILURE);
                     break;
-                case CMD_GET_CAPABILITY_FREQ:
-                    replyToMessage(message, message.what, null);
+                case CMD_REMOVE_NETWORK:
+                    deleteNetworkConfigAndSendReply(message, false);
                     break;
                 case CMD_GET_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what, (List<WifiConfiguration>) null);
-                    break;
-                case CMD_HAS_CARRIER_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what, null);
+                    replyToMessage(message, message.what, mWifiConfigManager.getSavedNetworks());
                     break;
                 case CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what, (List<WifiConfiguration>) null);
+                    replyToMessage(message, message.what,
+                            mWifiConfigManager.getConfiguredNetworksWithPasswords());
                     break;
                 case CMD_ENABLE_RSSI_POLL:
                     mEnableRssiPolling = (message.arg1 == 1);
@@ -4123,7 +3762,16 @@
                         setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, true);
                     }
                     break;
+                case CMD_INITIALIZE:
+                    ok = mWifiNative.initializeVendorHal(mVendorHalDeathRecipient);
+                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+                    break;
                 case CMD_BOOT_COMPLETED:
+                    // get other services that we need to manage
+                    getAdditionalWifiServiceInterfaces();
+                    if (!mWifiConfigManager.loadFromStore()) {
+                        Log.e(TAG, "Failed to load from config store");
+                    }
                     maybeRegisterNetworkFactory();
                     break;
                 case CMD_SCREEN_STATE_CHANGED:
@@ -4135,9 +3783,6 @@
                     break;
                 case CMD_START_SUPPLICANT:
                 case CMD_STOP_SUPPLICANT:
-                case CMD_STOP_SUPPLICANT_FAILED:
-                case CMD_START_DRIVER:
-                case CMD_STOP_DRIVER:
                 case CMD_DRIVER_START_TIMED_OUT:
                 case CMD_START_AP:
                 case CMD_START_AP_FAILURE:
@@ -4157,31 +3802,26 @@
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                 case WifiMonitor.WPS_OVERLAP_EVENT:
-                case CMD_BLACKLIST_NETWORK:
-                case CMD_CLEAR_BLACKLIST:
                 case CMD_SET_OPERATIONAL_MODE:
-                case CMD_SET_FREQUENCY_BAND:
                 case CMD_RSSI_POLL:
-                case CMD_ENABLE_ALL_NETWORKS:
                 case DhcpClient.CMD_PRE_DHCP_ACTION:
                 case DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE:
                 case DhcpClient.CMD_POST_DHCP_ACTION:
                 case CMD_NO_NETWORKS_PERIODIC_SCAN:
+                case CMD_ENABLE_P2P:
                 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:
-                case CMD_AUTO_ROAM:
-                case CMD_AUTO_SAVE_NETWORK:
+                case CMD_START_CONNECT:
+                case CMD_START_ROAM:
                 case CMD_ASSOCIATED_BSSID:
                 case CMD_UNWANTED_NETWORK:
                 case CMD_DISCONNECTING_WATCHDOG_TIMER:
                 case CMD_ROAM_WATCHDOG_TIMER:
+                case CMD_DISABLE_P2P_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:
@@ -4194,17 +3834,12 @@
                         setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
                     }
                     break;
-                case WifiMonitor.DRIVER_HUNG_EVENT:
-                    setSupplicantRunning(false);
-                    setSupplicantRunning(true);
-                    break;
                 case WifiManager.CONNECT_NETWORK:
                     replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                             WifiManager.BUSY);
                     break;
                 case WifiManager.FORGET_NETWORK:
-                    replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
-                            WifiManager.BUSY);
+                    deleteNetworkConfigAndSendReply(message, true);
                     break;
                 case WifiManager.SAVE_NETWORK:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
@@ -4232,10 +3867,10 @@
                     replyToMessage(message, message.what, featureSet);
                     break;
                 case CMD_FIRMWARE_ALERT:
-                    if (mWifiLogger != null) {
+                    if (mWifiDiagnostics != null) {
                         byte[] buffer = (byte[])message.obj;
                         int alertReason = message.arg1;
-                        mWifiLogger.captureAlertData(alertReason, buffer);
+                        mWifiDiagnostics.captureAlertData(alertReason, buffer);
                         mWifiMetrics.incrementAlertReasonCount(alertReason);
                     }
                     break;
@@ -4289,10 +3924,20 @@
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
                 case CMD_USER_SWITCH:
-                    mWifiConfigManager.handleUserSwitch(message.arg1);
+                    Set<Integer> removedNetworkIds =
+                            mWifiConfigManager.handleUserSwitch(message.arg1);
+                    if (removedNetworkIds.contains(mTargetNetworkId) ||
+                            removedNetworkIds.contains(mLastNetworkId)) {
+                        // Disconnect and let autojoin reselect a new network
+                        sendMessage(CMD_DISCONNECT);
+                    }
                     break;
-                case CMD_ADD_PASSPOINT_MO:
-                case CMD_MODIFY_PASSPOINT_MO:
+                case CMD_USER_UNLOCK:
+                    mWifiConfigManager.handleUserUnlock(message.arg1);
+                    break;
+                case CMD_USER_STOP:
+                    mWifiConfigManager.handleUserStop(message.arg1);
+                    break;
                 case CMD_QUERY_OSU_ICON:
                 case CMD_MATCH_PROVIDER_NETWORK:
                     /* reply with arg1 = 0 - it returns API failure to the calling app
@@ -4300,6 +3945,20 @@
                      */
                     replyToMessage(message, message.what);
                     break;
+                case CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG:
+                    int addResult = mPasspointManager.addOrUpdateProvider(
+                            (PasspointConfiguration) message.obj, message.arg1)
+                            ? SUCCESS : FAILURE;
+                    replyToMessage(message, message.what, addResult);
+                    break;
+                case CMD_REMOVE_PASSPOINT_CONFIG:
+                    int removeResult = mPasspointManager.removeProvider(
+                            (String) message.obj) ? SUCCESS : FAILURE;
+                    replyToMessage(message, message.what, removeResult);
+                    break;
+                case CMD_GET_PASSPOINT_CONFIGS:
+                    replyToMessage(message, message.what, mPasspointManager.getProviderConfigs());
+                    break;
                 case CMD_RESET_SIM_NETWORKS:
                     /* Defer this message until supplicant is started. */
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
@@ -4315,6 +3974,20 @@
                         mWifiNative.stopFilteringMulticastV4Packets();
                     }
                     break;
+                case CMD_CLIENT_INTERFACE_BINDER_DEATH:
+                    Log.e(TAG, "wificond died unexpectedly. Triggering recovery");
+                    mWifiMetrics.incrementNumWificondCrashes();
+                    mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFICOND_CRASH);
+                    break;
+                case CMD_VENDOR_HAL_HWBINDER_DEATH:
+                    Log.e(TAG, "Vendor HAL died unexpectedly. Triggering recovery");
+                    mWifiMetrics.incrementNumHalCrashes();
+                    mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_HAL_CRASH);
+                    break;
+                case CMD_DIAGS_CONNECT_TIMEOUT:
+                    mWifiDiagnostics.reportConnectionEvent(
+                            (Long) message.obj, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+                    break;
                 default:
                     loge("Error! unhandled message" + message);
                     break;
@@ -4324,99 +3997,74 @@
     }
 
     class InitialState extends State {
+
+        private void cleanup() {
+            // Tearing down the client interfaces below is going to stop our supplicant.
+            mWifiMonitor.stopAllMonitoring();
+
+            mDeathRecipient.unlinkToDeath();
+            mWifiNative.tearDown();
+        }
+
         @Override
         public void enter() {
-            mWifiNative.stopHal();
-            mWifiNative.unloadDriver();
-            if (mWifiP2pChannel == null) {
-                mWifiP2pChannel = new AsyncChannel();
-                mWifiP2pChannel.connect(mContext, getHandler(),
-                    mWifiP2pServiceImpl.getP2pStateMachineMessenger());
-            }
-
-            if (mWifiApConfigStore == null) {
-                mWifiApConfigStore =
-                        mFacade.makeApConfigStore(mContext, mBackupManagerProxy);
-            }
+            mWifiStateTracker.updateState(WifiStateTracker.INVALID);
+            cleanup();
         }
+
         @Override
         public boolean processMessage(Message message) {
             logStateAndMessage(message, this);
             switch (message.what) {
                 case CMD_START_SUPPLICANT:
-                    if (mWifiNative.loadDriver()) {
-                        try {
-                            mNwService.wifiFirmwareReload(mInterfaceName, "STA");
-                        } catch (Exception e) {
-                            loge("Failed to reload STA firmware " + e);
-                            setWifiState(WifiManager.WIFI_STATE_UNKNOWN);
-                            return HANDLED;
-                        }
-
-                        try {
-                            // A runtime crash can leave the interface up and
-                            // IP addresses configured, and this affects
-                            // connectivity when supplicant starts up.
-                            // Ensure interface is down and we have no IP
-                            // addresses before a supplicant start.
-                            mNwService.setInterfaceDown(mInterfaceName);
-                            mNwService.clearInterfaceAddresses(mInterfaceName);
-
-                            // Set privacy extensions
-                            mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
-
-                            // IPv6 is enabled only as long as access point is connected since:
-                            // - IPv6 addresses and routes stick around after disconnection
-                            // - kernel is unaware when connected and fails to start IPv6 negotiation
-                            // - kernel can start autoconfiguration when 802.1x is not complete
-                            mNwService.disableIpv6(mInterfaceName);
-                        } catch (RemoteException re) {
-                            loge("Unable to change interface settings: " + re);
-                        } catch (IllegalStateException ie) {
-                            loge("Unable to change interface settings: " + ie);
-                        }
-
-                       /* Stop a running supplicant after a runtime restart
-                        * Avoids issues with drivers that do not handle interface down
-                        * on a running supplicant properly.
-                        */
-                        mWifiMonitor.killSupplicant(mP2pSupported);
-
-                        if (mWifiNative.startHal() == false) {
-                            /* starting HAL is optional */
-                            loge("Failed to start HAL");
-                        }
-
-                        if (mWifiNative.startSupplicant(mP2pSupported)) {
-                            setSupplicantLogLevel();
-                            setWifiState(WIFI_STATE_ENABLING);
-                            if (DBG) log("Supplicant start successful");
-                            mWifiMonitor.startMonitoring(mInterfaceName);
-                            transitionTo(mSupplicantStartingState);
-                        } else {
-                            loge("Failed to start supplicant!");
-                            setWifiState(WifiManager.WIFI_STATE_UNKNOWN);
-                        }
-                    } else {
-                        loge("Failed to load driver");
+                    mClientInterface = mWifiNative.setupForClientMode();
+                    if (mClientInterface == null
+                            || !mDeathRecipient.linkToDeath(mClientInterface.asBinder())) {
                         setWifiState(WifiManager.WIFI_STATE_UNKNOWN);
+                        cleanup();
+                        break;
                     }
+
+                    try {
+                        // A runtime crash or shutting down AP mode can leave
+                        // IP addresses configured, and this affects
+                        // connectivity when supplicant starts up.
+                        // Ensure we have no IP addresses before a supplicant start.
+                        mNwService.clearInterfaceAddresses(mInterfaceName);
+
+                        // Set privacy extensions
+                        mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+
+                        // IPv6 is enabled only as long as access point is connected since:
+                        // - IPv6 addresses and routes stick around after disconnection
+                        // - kernel is unaware when connected and fails to start IPv6 negotiation
+                        // - kernel can start autoconfiguration when 802.1x is not complete
+                        mNwService.disableIpv6(mInterfaceName);
+                    } catch (RemoteException re) {
+                        loge("Unable to change interface settings: " + re);
+                    } catch (IllegalStateException ie) {
+                        loge("Unable to change interface settings: " + ie);
+                    }
+
+                    if (!mWifiNative.enableSupplicant()) {
+                        loge("Failed to start supplicant!");
+                        setWifiState(WifiManager.WIFI_STATE_UNKNOWN);
+                        cleanup();
+                        break;
+                    }
+                    if (mVerboseLoggingEnabled) log("Supplicant start successful");
+                    mWifiMonitor.startMonitoring(mInterfaceName, true);
+                    setSupplicantLogLevel();
+                    transitionTo(mSupplicantStartingState);
                     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);
-                    }
+                    transitionTo(mSoftApState);
                     break;
                 case CMD_SET_OPERATIONAL_MODE:
                     mOperationalMode = message.arg1;
+                    if (mOperationalMode != DISABLED_MODE) {
+                        sendMessage(CMD_START_SUPPLICANT);
+                    }
                     break;
                 default:
                     return NOT_HANDLED;
@@ -4462,7 +4110,7 @@
 
             switch(message.what) {
                 case WifiMonitor.SUP_CONNECTION_EVENT:
-                    if (DBG) log("Supplicant connection established");
+                    if (mVerboseLoggingEnabled) log("Supplicant connection established");
 
                     mSupplicantRestartCount = 0;
                     /* Reset the supplicant state to indicate the supplicant
@@ -4474,22 +4122,19 @@
                     mLastSignalLevel = -1;
 
                     mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
-                    /* set frequency band of operation */
-                    setFrequencyBand();
-                    mWifiNative.enableSaveConfig();
-                    mWifiConfigManager.loadAndEnableAllNetworks();
-                    if (mWifiConfigManager.mEnableVerboseLogging.get() > 0) {
-                        enableVerboseLogging(mWifiConfigManager.mEnableVerboseLogging.get());
+                    // Attempt to migrate data out of legacy store.
+                    if (!mWifiConfigManager.migrateFromLegacyStore()) {
+                        Log.e(TAG, "Failed to migrate from legacy config store");
                     }
                     initializeWpsDetails();
-
                     sendSupplicantConnectionChangedBroadcast(true);
-                    transitionTo(mDriverStartedState);
+                    transitionTo(mSupplicantStartedState);
                     break;
                 case WifiMonitor.SUP_DISCONNECTION_EVENT:
                     if (++mSupplicantRestartCount <= SUPPLICANT_RESTART_TRIES) {
                         loge("Failed to setup control channel, restart supplicant");
-                        mWifiMonitor.killSupplicant(mP2pSupported);
+                        mWifiMonitor.stopAllMonitoring();
+                        mWifiNative.disableSupplicant();
                         transitionTo(mInitialState);
                         sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
                     } else {
@@ -4504,10 +4149,7 @@
                 case CMD_STOP_SUPPLICANT:
                 case CMD_START_AP:
                 case CMD_STOP_AP:
-                case CMD_START_DRIVER:
-                case CMD_STOP_DRIVER:
                 case CMD_SET_OPERATIONAL_MODE:
-                case CMD_SET_FREQUENCY_BAND:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
                     deferMessage(message);
                     break;
@@ -4521,22 +4163,86 @@
     class SupplicantStartedState extends State {
         @Override
         public void enter() {
-            int defaultInterval = mContext.getResources().getInteger(
-                    R.integer.config_wifi_supplicant_scan_interval);
+            if (mVerboseLoggingEnabled) {
+                logd("SupplicantStartedState enter");
+            }
 
-            mSupplicantScanIntervalMs = mFacade.getLongSetting(mContext,
-                    Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
-                    defaultInterval);
-
-            mWifiNative.setScanInterval((int)mSupplicantScanIntervalMs / 1000);
             mWifiNative.setExternalSim(true);
 
-            /* turn on use of DFS channels */
-            mWifiNative.setDfsFlag(true);
-
             setRandomMacOui();
-            mWifiNative.enableAutoConnect(false);
             mCountryCode.setReadyForChange(true);
+
+            // We can't do this in the constructor because WifiStateMachine is created before the
+            // wifi scanning service is initialized
+            if (mWifiScanner == null) {
+                mWifiScanner = mWifiInjector.getWifiScanner();
+
+                synchronized (mWifiReqCountLock) {
+                    mWifiConnectivityManager =
+                            mWifiInjector.makeWifiConnectivityManager(mWifiInfo,
+                                                                      hasConnectionRequests());
+                    mWifiConnectivityManager.setUntrustedConnectionAllowed(mUntrustedReqCount > 0);
+                    mWifiConnectivityManager.handleScreenStateChanged(mScreenOn);
+                }
+            }
+
+            mWifiDiagnostics.startLogging(mVerboseLoggingEnabled);
+            mIsRunning = true;
+            updateBatteryWorkSource(null);
+            /**
+             * Enable bluetooth coexistence scan mode when bluetooth connection is active.
+             * When this mode is on, some of the low-level scan parameters used by the
+             * driver are changed to reduce interference with bluetooth
+             */
+            mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
+            // initialize network state
+            setNetworkDetailedState(DetailedState.DISCONNECTED);
+
+            // Disable legacy multicast filtering, which on some chipsets defaults to enabled.
+            // Legacy IPv6 multicast filtering blocks ICMPv6 router advertisements which breaks IPv6
+            // provisioning. Legacy IPv4 multicast filtering may be re-enabled later via
+            // IpManager.Callback.setFallbackMulticastFilter()
+            mWifiNative.stopFilteringMulticastV4Packets();
+            mWifiNative.stopFilteringMulticastV6Packets();
+
+            if (mOperationalMode == SCAN_ONLY_MODE ||
+                    mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+                mWifiNative.disconnect();
+                setWifiState(WIFI_STATE_DISABLED);
+                transitionTo(mScanModeState);
+            } else if (mOperationalMode == CONNECT_MODE) {
+                setWifiState(WIFI_STATE_ENABLING);
+                // Transitioning to Disconnected state will trigger a scan and subsequently AutoJoin
+                transitionTo(mDisconnectedState);
+            } else if (mOperationalMode == DISABLED_MODE) {
+                transitionTo(mSupplicantStoppingState);
+            }
+
+            // Set the right suspend mode settings
+            mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
+                    && mUserWantsSuspendOpt.get());
+
+            mWifiNative.setPowerSave(true);
+
+            if (mP2pSupported) {
+                if (mOperationalMode == CONNECT_MODE) {
+                    p2pSendMessage(WifiStateMachine.CMD_ENABLE_P2P);
+                } else {
+                    // 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.
+                }
+            }
+
+            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);
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+
+            // Disable wpa_supplicant from auto reconnecting.
+            mWifiNative.enableStaAutoReconnect(false);
+            // STA has higher priority over P2P
+            mWifiNative.setConcurrencyPriority(true);
         }
 
         @Override
@@ -4563,39 +4269,32 @@
                     }
                     sendMessageDelayed(CMD_START_SUPPLICANT, SUPPLICANT_RESTART_INTERVAL_MSECS);
                     break;
+                case CMD_START_SCAN:
+                    // TODO: remove scan request path (b/31445200)
+                    handleScanRequest(message);
+                    break;
                 case WifiMonitor.SCAN_RESULTS_EVENT:
                 case WifiMonitor.SCAN_FAILED_EVENT:
+                    // TODO: remove handing of SCAN_RESULTS_EVENT and SCAN_FAILED_EVENT when scan
+                    // results are retrieved from WifiScanner (b/31444878)
                     maybeRegisterNetworkFactory(); // Make sure our NetworkFactory is registered
                     setScanResults();
-                    if (mIsFullScanOngoing || mSendScanResultsBroadcast) {
-                        /* Just updated results from full scan, let apps know about this */
-                        boolean scanSucceeded = message.what == WifiMonitor.SCAN_RESULTS_EVENT;
-                        sendScanResultsAvailableBroadcast(scanSucceeded);
-                    }
-                    mSendScanResultsBroadcast = false;
                     mIsScanOngoing = false;
                     mIsFullScanOngoing = false;
                     if (mBufferedScanMsg.size() > 0)
                         sendMessage(mBufferedScanMsg.remove());
                     break;
-                case CMD_PING_SUPPLICANT:
-                    boolean ok = mWifiNative.ping();
-                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
-                    break;
-                case CMD_GET_CAPABILITY_FREQ:
-                    String freqs = mWifiNative.getFreqCapability();
-                    replyToMessage(message, message.what, freqs);
-                    break;
                 case CMD_START_AP:
                     /* Cannot start soft AP while in client mode */
                     loge("Failed to start soft AP with a running supplicant");
-                    setWifiApState(WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL);
+                    setWifiApState(WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL,
+                            null, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
                     break;
                 case CMD_SET_OPERATIONAL_MODE:
                     mOperationalMode = message.arg1;
-                    mWifiConfigManager.
-                            setAndEnableLastSelectedConfiguration(
-                                    WifiConfiguration.INVALID_NETWORK_ID);
+                    if (mOperationalMode == DISABLED_MODE) {
+                        transitionTo(mSupplicantStoppingState);
+                    }
                     break;
                 case CMD_TARGET_BSSID:
                     // Trying to associate to this BSSID
@@ -4604,307 +4303,18 @@
                     }
                     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:
                     log("resetting EAP-SIM/AKA/AKA' networks since SIM was changed");
                     mWifiConfigManager.resetSimNetworks();
                     break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-
-        @Override
-        public void exit() {
-            mNetworkInfo.setIsAvailable(false);
-            if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo);
-            mCountryCode.setReadyForChange(false);
-        }
-    }
-
-    class SupplicantStoppingState extends State {
-        @Override
-        public void enter() {
-            /* Send any reset commands to supplicant before shutting it down */
-            handleNetworkDisconnect();
-
-            String suppState = System.getProperty("init.svc.wpa_supplicant");
-            if (suppState == null) suppState = "unknown";
-            String p2pSuppState = System.getProperty("init.svc.p2p_supplicant");
-            if (p2pSuppState == null) p2pSuppState = "unknown";
-
-            logd("SupplicantStoppingState: stopSupplicant "
-                    + " init.svc.wpa_supplicant=" + suppState
-                    + " init.svc.p2p_supplicant=" + p2pSuppState);
-            mWifiMonitor.stopSupplicant();
-
-            /* Send ourselves a delayed message to indicate failure after a wait time */
-            sendMessageDelayed(obtainMessage(CMD_STOP_SUPPLICANT_FAILED,
-                    ++mSupplicantStopFailureToken, 0), SUPPLICANT_RESTART_INTERVAL_MSECS);
-            setWifiState(WIFI_STATE_DISABLING);
-            mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
-        }
-        @Override
-        public boolean processMessage(Message message) {
-            logStateAndMessage(message, this);
-
-            switch(message.what) {
-                case WifiMonitor.SUP_CONNECTION_EVENT:
-                    loge("Supplicant connection received while stopping");
-                    break;
-                case WifiMonitor.SUP_DISCONNECTION_EVENT:
-                    if (DBG) log("Supplicant connection lost");
-                    handleSupplicantConnectionLoss(false);
-                    transitionTo(mInitialState);
-                    break;
-                case CMD_STOP_SUPPLICANT_FAILED:
-                    if (message.arg1 == mSupplicantStopFailureToken) {
-                        loge("Timed out on a supplicant stop, kill and proceed");
-                        handleSupplicantConnectionLoss(true);
-                        transitionTo(mInitialState);
-                    }
-                    break;
-                case CMD_START_SUPPLICANT:
-                case CMD_STOP_SUPPLICANT:
-                case CMD_START_AP:
-                case CMD_STOP_AP:
-                case CMD_START_DRIVER:
-                case CMD_STOP_DRIVER:
-                case CMD_SET_OPERATIONAL_MODE:
-                case CMD_SET_FREQUENCY_BAND:
-                    deferMessage(message);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class DriverStartingState extends State {
-        private int mTries;
-        @Override
-        public void enter() {
-            mTries = 1;
-            /* Send ourselves a delayed message to start driver a second time */
-            sendMessageDelayed(obtainMessage(CMD_DRIVER_START_TIMED_OUT,
-                        ++mDriverStartToken, 0), DRIVER_START_TIME_OUT_MSECS);
-        }
-        @Override
-        public boolean processMessage(Message message) {
-            logStateAndMessage(message, this);
-
-            switch(message.what) {
-               case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                    SupplicantState state = handleSupplicantStateChange(message);
-                    /* If suplicant is exiting out of INTERFACE_DISABLED state into
-                     * a state that indicates driver has started, it is ready to
-                     * receive driver commands
-                     */
-                    if (SupplicantState.isDriverActive(state)) {
-                        transitionTo(mDriverStartedState);
-                    }
-                    break;
-                case CMD_DRIVER_START_TIMED_OUT:
-                    if (message.arg1 == mDriverStartToken) {
-                        if (mTries >= 2) {
-                            loge("Failed to start driver after " + mTries);
-                            setSupplicantRunning(false);
-                            setSupplicantRunning(true);
-                        } else {
-                            loge("Driver start failed, retrying");
-                            mWakeLock.acquire();
-                            mWifiNative.startDriver();
-                            mWakeLock.release();
-
-                            ++mTries;
-                            /* Send ourselves a delayed message to start driver again */
-                            sendMessageDelayed(obtainMessage(CMD_DRIVER_START_TIMED_OUT,
-                                        ++mDriverStartToken, 0), DRIVER_START_TIME_OUT_MSECS);
-                        }
-                    }
-                    break;
-                    /* Queue driver commands & connection events */
-                case CMD_START_DRIVER:
-                case CMD_STOP_DRIVER:
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
-                case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
-                case WifiMonitor.WPS_OVERLAP_EVENT:
-                case CMD_SET_FREQUENCY_BAND:
-                case CMD_START_SCAN:
-                case CMD_DISCONNECT:
-                case CMD_REASSOCIATE:
-                case CMD_RECONNECT:
-                    messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
-                    deferMessage(message);
-                    break;
-                case WifiMonitor.SCAN_RESULTS_EVENT:
-                case WifiMonitor.SCAN_FAILED_EVENT:
-                    // Loose scan results obtained in Driver Starting state, they can only confuse
-                    // the state machine
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class DriverStartedState extends State {
-        @Override
-        public void enter() {
-            if (DBG) {
-                logd("DriverStartedState enter");
-            }
-
-            // We can't do this in the constructor because WifiStateMachine is created before the
-            // wifi scanning service is initialized
-            if (mWifiScanner == null) {
-                mWifiScanner = mFacade.makeWifiScanner(mContext, getHandler().getLooper());
-
-                synchronized (mWifiReqCountLock) {
-                    mWifiConnectivityManager = new WifiConnectivityManager(mContext,
-                        WifiStateMachine.this, mWifiScanner, mWifiConfigManager, mWifiInfo,
-                        mWifiQualifiedNetworkSelector, mWifiInjector,
-                        getHandler().getLooper(), hasConnectionRequests());
-                    mWifiConnectivityManager.setUntrustedConnectionAllowed(mUntrustedReqCount > 0);
-                }
-            }
-
-            mWifiLogger.startLogging(DBG);
-            mIsRunning = true;
-            updateBatteryWorkSource(null);
-            /**
-             * Enable bluetooth coexistence scan mode when bluetooth connection is active.
-             * When this mode is on, some of the low-level scan parameters used by the
-             * driver are changed to reduce interference with bluetooth
-             */
-            mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
-            /* initialize network state */
-            setNetworkDetailedState(DetailedState.DISCONNECTED);
-
-            // Disable legacy multicast filtering, which on some chipsets defaults to enabled.
-            // Legacy IPv6 multicast filtering blocks ICMPv6 router advertisements which breaks IPv6
-            // provisioning. Legacy IPv4 multicast filtering may be re-enabled later via
-            // IpManager.Callback.setFallbackMulticastFilter()
-            mWifiNative.stopFilteringMulticastV4Packets();
-            mWifiNative.stopFilteringMulticastV6Packets();
-
-            if (mOperationalMode != CONNECT_MODE) {
-                mWifiNative.disconnect();
-                mWifiConfigManager.disableAllNetworksNative();
-                if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
-                    setWifiState(WIFI_STATE_DISABLED);
-                }
-                transitionTo(mScanModeState);
-            } else {
-
-                // 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
-                // 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());
-            } else {
-                // Set the right suspend mode settings
-                mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
-                        && mUserWantsSuspendOpt.get());
-
-                // Inform WifiConnectivtyManager the screen state in case
-                // WifiConnectivityManager missed the last screen update because
-                // it was not started yet.
-                mWifiConnectivityManager.handleScreenStateChanged(mScreenOn);
-            }
-            mWifiNative.setPowerSave(true);
-
-            if (mP2pSupported) {
-                if (mOperationalMode == CONNECT_MODE) {
-                    mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
-                } else {
-                    // P2P statemachine 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.
-                }
-            }
-
-            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);
-            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-
-            // Enable link layer stats gathering
-            mWifiNative.setWifiLinkLayerStats("wlan0", 1);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            logStateAndMessage(message, this);
-
-            switch(message.what) {
-                case CMD_START_SCAN:
-                    handleScanRequest(message);
-                    break;
-                case CMD_SET_FREQUENCY_BAND:
-                    int band =  message.arg1;
-                    if (DBG) log("set frequency band " + band);
-                    if (mWifiNative.setBand(band)) {
-
-                        if (DBG)  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);
-
-                    } else {
-                        loge("Failed to set frequency band " + band);
-                    }
-                    break;
                 case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
                     mBluetoothConnectionActive = (message.arg1 !=
                             BluetoothAdapter.STATE_DISCONNECTED);
                     mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
                     break;
-                case CMD_STOP_DRIVER:
-                    int mode = message.arg1;
-
-                    log("stop driver");
-                    mWifiConfigManager.disableAllNetworksNative();
-
-                    if (getCurrentState() != mDisconnectedState) {
-                        mWifiNative.disconnect();
-                        handleNetworkDisconnect();
-                    }
-                    mWakeLock.acquire();
-                    mWifiNative.stopDriver();
-                    mWakeLock.release();
-                    if (mP2pSupported) {
-                        transitionTo(mWaitForP2pDisableState);
-                    } else {
-                        transitionTo(mDriverStoppingState);
-                    }
-                    break;
-                case CMD_START_DRIVER:
-                    if (mOperationalMode == CONNECT_MODE) {
-                        mWifiConfigManager.enableAllNetworks();
-                    }
-                    break;
                 case CMD_SET_SUSPEND_OPT_ENABLED:
                     if (message.arg1 == 1) {
                         setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
@@ -4930,7 +4340,8 @@
                     }
                     break;
                 case WifiMonitor.ANQP_DONE_EVENT:
-                    mWifiConfigManager.notifyANQPDone((Long) message.obj, message.arg1 != 0);
+                    // TODO(zqiu): remove this when switch over to wificond for ANQP requests.
+                    mPasspointManager.notifyANQPDone((AnqpEvent) message.obj);
                     break;
                 case CMD_STOP_IP_PACKET_OFFLOAD: {
                     int slot = message.arg1;
@@ -4941,29 +4352,28 @@
                     break;
                 }
                 case WifiMonitor.RX_HS20_ANQP_ICON_EVENT:
-                    mWifiConfigManager.notifyIconReceived((IconEvent) message.obj);
+                    // TODO(zqiu): remove this when switch over to wificond for icon requests.
+                    mPasspointManager.notifyIconDone((IconEvent) message.obj);
                     break;
                 case WifiMonitor.HS20_REMEDIATION_EVENT:
-                    wnmFrameReceived((WnmData) message.obj);
+                    // TODO(zqiu): remove this when switch over to wificond for WNM frames
+                    // monitoring.
+                    mPasspointManager.receivedWnmFrame((WnmData) message.obj);
                     break;
                 case CMD_CONFIG_ND_OFFLOAD:
                     final boolean enabled = (message.arg1 > 0);
                     mWifiNative.configureNeighborDiscoveryOffload(enabled);
                     break;
                 case CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER:
-                    if (mWifiConnectivityManager != null) {
-                        mWifiConnectivityManager.enable(message.arg1 == 1 ? true : false);
-                    }
+                    mWifiConnectivityManager.enable(message.arg1 == 1 ? true : false);
                     break;
                 case CMD_ENABLE_AUTOJOIN_WHEN_ASSOCIATED:
                     final boolean allowed = (message.arg1 > 0);
-                    boolean old_state = mWifiConfigManager.getEnableAutoJoinWhenAssociated();
-                    mWifiConfigManager.setEnableAutoJoinWhenAssociated(allowed);
+                    boolean old_state = mEnableAutoJoinWhenAssociated;
+                    mEnableAutoJoinWhenAssociated = allowed;
                     if (!old_state && allowed && mScreenOn
                             && getCurrentState() == mConnectedState) {
-                        if (mWifiConnectivityManager != null) {
-                            mWifiConnectivityManager.forceConnectivityScan();
-                        }
+                        mWifiConnectivityManager.forceConnectivityScan();
                     }
                     break;
                 default:
@@ -4971,10 +4381,10 @@
             }
             return HANDLED;
         }
+
         @Override
         public void exit() {
-
-            mWifiLogger.stopLogging();
+            mWifiDiagnostics.stopLogging();
 
             mIsRunning = false;
             updateBatteryWorkSource(null);
@@ -4985,6 +4395,35 @@
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
             mBufferedScanMsg.clear();
+
+            mNetworkInfo.setIsAvailable(false);
+            if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+            mCountryCode.setReadyForChange(false);
+        }
+    }
+
+    class SupplicantStoppingState extends State {
+        @Override
+        public void enter() {
+            /* Send any reset commands to supplicant before shutting it down */
+            handleNetworkDisconnect();
+
+            String suppState = System.getProperty("init.svc.wpa_supplicant");
+            if (suppState == null) suppState = "unknown";
+
+            setWifiState(WIFI_STATE_DISABLING);
+            mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+            logd("SupplicantStoppingState: disableSupplicant "
+                    + " init.svc.wpa_supplicant=" + suppState);
+            if (mWifiNative.disableSupplicant()) {
+                mWifiNative.closeSupplicantConnection();
+                sendSupplicantConnectionChangedBroadcast(false);
+                setWifiState(WIFI_STATE_DISABLED);
+            } else {
+                // Failed to disable supplicant
+                handleSupplicantConnectionLoss(true);
+            }
+            transitionTo(mInitialState);
         }
     }
 
@@ -4996,17 +4435,17 @@
                 case WifiMonitor.SUP_DISCONNECTION_EVENT:
                     mTransitionToState = mInitialState;
                     break;
-                case CMD_STOP_DRIVER:
-                    mTransitionToState = mDriverStoppingState;
-                    break;
                 case CMD_STOP_SUPPLICANT:
+                default:
                     mTransitionToState = mSupplicantStoppingState;
                     break;
-                default:
-                    mTransitionToState = mDriverStoppingState;
-                    break;
             }
-            mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_REQ);
+            if (p2pSendMessage(WifiStateMachine.CMD_DISABLE_P2P_REQ)) {
+                sendMessageDelayed(obtainMessage(CMD_DISABLE_P2P_WATCHDOG_TIMER,
+                        mDisableP2pWatchdogCount, 0), DISABLE_P2P_GUARD_TIMER_MSEC);
+            } else {
+                transitionTo(mTransitionToState);
+            }
         }
         @Override
         public boolean processMessage(Message message) {
@@ -5016,16 +4455,19 @@
                 case WifiStateMachine.CMD_DISABLE_P2P_RSP:
                     transitionTo(mTransitionToState);
                     break;
+                case WifiStateMachine.CMD_DISABLE_P2P_WATCHDOG_TIMER:
+                    if (mDisableP2pWatchdogCount == message.arg1) {
+                        logd("Timeout waiting for CMD_DISABLE_P2P_RSP");
+                        transitionTo(mTransitionToState);
+                    }
+                    break;
                 /* Defer wifi start/shut and driver commands */
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                 case CMD_START_SUPPLICANT:
                 case CMD_STOP_SUPPLICANT:
                 case CMD_START_AP:
                 case CMD_STOP_AP:
-                case CMD_START_DRIVER:
-                case CMD_STOP_DRIVER:
                 case CMD_SET_OPERATIONAL_MODE:
-                case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_SCAN:
                 case CMD_DISCONNECT:
                 case CMD_REASSOCIATE:
@@ -5040,68 +4482,12 @@
         }
     }
 
-    class DriverStoppingState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            logStateAndMessage(message, this);
-
-            switch(message.what) {
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                    SupplicantState state = handleSupplicantStateChange(message);
-                    if (state == SupplicantState.INTERFACE_DISABLED) {
-                        transitionTo(mDriverStoppedState);
-                    }
-                    break;
-                    /* Queue driver commands */
-                case CMD_START_DRIVER:
-                case CMD_STOP_DRIVER:
-                case CMD_SET_FREQUENCY_BAND:
-                case CMD_START_SCAN:
-                case CMD_DISCONNECT:
-                case CMD_REASSOCIATE:
-                case CMD_RECONNECT:
-                    messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
-                    deferMessage(message);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class DriverStoppedState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            logStateAndMessage(message, this);
-            switch (message.what) {
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
-                    SupplicantState state = stateChangeResult.state;
-                    // A WEXT bug means that we can be back to driver started state
-                    // unexpectedly
-                    if (SupplicantState.isDriverActive(state)) {
-                        transitionTo(mDriverStartedState);
-                    }
-                    break;
-                case CMD_START_DRIVER:
-                    mWakeLock.acquire();
-                    mWifiNative.startDriver();
-                    mWakeLock.release();
-                    transitionTo(mDriverStartingState);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
     class ScanModeState extends State {
         private int mLastOperationMode;
         @Override
         public void enter() {
             mLastOperationMode = mOperationalMode;
+            mWifiStateTracker.updateState(WifiStateTracker.SCAN_MODE);
         }
         @Override
         public boolean processMessage(Message message) {
@@ -5110,38 +4496,19 @@
             switch(message.what) {
                 case CMD_SET_OPERATIONAL_MODE:
                     if (message.arg1 == CONNECT_MODE) {
-
-                        if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
-                            setWifiState(WIFI_STATE_ENABLED);
-                            // 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);
-                        } else {
-                            mWifiConfigManager.enableAllNetworks();
-                        }
-
-                        // Loose last selection choice since user toggled WiFi
-                        mWifiConfigManager.
-                                setAndEnableLastSelectedConfiguration(
-                                        WifiConfiguration.INVALID_NETWORK_ID);
-
                         mOperationalMode = CONNECT_MODE;
+                        setWifiState(WIFI_STATE_ENABLING);
                         transitionTo(mDisconnectedState);
-                    } else {
-                        // Nothing to do
-                        return HANDLED;
+                    } else if (message.arg1 == DISABLED_MODE) {
+                        transitionTo(mSupplicantStoppingState);
                     }
+                    // Nothing to do
                     break;
                 // Handle scan. All the connection related commands are
                 // handled only in ConnectModeState
                 case CMD_START_SCAN:
                     handleScanRequest(message);
                     break;
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                    SupplicantState state = handleSupplicantStateChange(message);
-                    if (DBG) log("SupplicantState= " + state);
-                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -5160,9 +4527,6 @@
             return s;
         }
         switch (what) {
-            case WifiMonitor.DRIVER_HUNG_EVENT:
-                s = "DRIVER_HUNG_EVENT";
-                break;
             case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                 s = "AsyncChannel.CMD_CHANNEL_HALF_CONNECTED";
                 break;
@@ -5202,12 +4566,6 @@
             case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                 s = "AUTHENTICATION_FAILURE_EVENT";
                 break;
-            case WifiMonitor.SSID_TEMP_DISABLED:
-                s = "SSID_TEMP_DISABLED";
-                break;
-            case WifiMonitor.SSID_REENABLED:
-                s = "SSID_REENABLED";
-                break;
             case WifiMonitor.WPS_SUCCESS_EVENT:
                 s = "WPS_SUCCESS_EVENT";
                 break;
@@ -5289,68 +4647,28 @@
 
     void registerConnected() {
         if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(mLastNetworkId);
-            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.numAssociation++;
-                WifiConfiguration.NetworkSelectionStatus networkSelectionStatus =
-                        config.getNetworkSelectionStatus();
-                networkSelectionStatus.clearDisableReasonCounter();
-                networkSelectionStatus.setHasEverConnected(true);
-            }
+            mWifiConfigManager.updateNetworkAfterConnect(mLastNetworkId);
             // On connect, reset wifiScoreReport
-            mWifiScoreReport = null;
+            mWifiScoreReport.reset();
        }
     }
 
     void registerDisconnected() {
         if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
+            mWifiConfigManager.updateNetworkAfterDisconnect(mLastNetworkId);
             // We are switching away from this configuration,
             // hence record the time we were connected last
-            WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(mLastNetworkId);
+            WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(mLastNetworkId);
             if (config != null) {
-                config.lastDisconnected = System.currentTimeMillis();
-                if (config.ephemeral) {
-                    // Remove ephemeral WifiConfigurations from file
-                    mWifiConfigManager.forgetNetwork(mLastNetworkId);
+                // Remove WifiConfiguration for ephemeral or Passpoint networks, since they're
+                // temporary networks.
+                if (config.ephemeral || config.isPasspoint()) {
+                    mWifiConfigManager.removeNetwork(mLastNetworkId, Process.WIFI_UID);
                 }
             }
         }
     }
 
-    void noteWifiDisabledWhileAssociated() {
-        // We got disabled by user while we were associated, make note of it
-        int rssi = mWifiInfo.getRssi();
-        WifiConfiguration config = getCurrentWifiConfiguration();
-        if (getCurrentState() == mConnectedState
-                && rssi != WifiInfo.INVALID_RSSI
-                && config != null) {
-            boolean is24GHz = mWifiInfo.is24GHz();
-            boolean isBadRSSI = (is24GHz && rssi < mWifiConfigManager.mThresholdMinimumRssi24.get())
-                    || (!is24GHz && rssi < mWifiConfigManager.mThresholdMinimumRssi5.get());
-            boolean isLowRSSI =
-                    (is24GHz && rssi < mWifiConfigManager.mThresholdQualifiedRssi24.get())
-                            || (!is24GHz && mWifiInfo.getRssi() <
-                                    mWifiConfigManager.mThresholdQualifiedRssi5.get());
-            boolean isHighRSSI = (is24GHz && rssi
-                    >= mWifiConfigManager.mThresholdSaturatedRssi24.get())
-                    || (!is24GHz && mWifiInfo.getRssi()
-                    >= mWifiConfigManager.mThresholdSaturatedRssi5.get());
-            if (isBadRSSI) {
-                // Take note that we got disabled while RSSI was Bad
-                config.numUserTriggeredWifiDisableLowRSSI++;
-            } else if (isLowRSSI) {
-                // Take note that we got disabled while RSSI was Low
-                config.numUserTriggeredWifiDisableBadRSSI++;
-            } else if (!isHighRSSI) {
-                // Take note that we got disabled while RSSI was Not high
-                config.numUserTriggeredWifiDisableNotHighRSSI++;
-            }
-        }
-    }
-
     /**
      * Returns Wificonfiguration object correponding to the currently connected network, null if
      * not connected.
@@ -5359,7 +4677,7 @@
         if (mLastNetworkId == WifiConfiguration.INVALID_NETWORK_ID) {
             return null;
         }
-        return mWifiConfigManager.getWifiConfiguration(mLastNetworkId);
+        return mWifiConfigManager.getConfiguredNetwork(mLastNetworkId);
     }
 
     ScanResult getCurrentScanResult() {
@@ -5372,7 +4690,7 @@
             BSSID = mTargetRoamBSSID;
         }
         ScanDetailCache scanDetailCache =
-                mWifiConfigManager.getScanDetailCache(config);
+                mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
 
         if (scanDetailCache == null) {
             return null;
@@ -5382,7 +4700,7 @@
     }
 
     String getCurrentBSSID() {
-        if (linkDebouncing) {
+        if (isLinkDebouncing()) {
             return null;
         }
         return mLastBssid;
@@ -5392,7 +4710,12 @@
 
         @Override
         public void enter() {
-            // Let the system know that wifi is enabled
+            if (!mWifiNative.removeAllNetworks()) {
+                loge("Failed to remove networks on entering connect mode");
+            }
+            mWifiInfo.reset();
+            mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
+            // Let the system know that wifi is available in client mode.
             setWifiState(WIFI_STATE_ENABLED);
 
             mNetworkInfo.setIsAvailable(true);
@@ -5402,26 +4725,29 @@
             setNetworkDetailedState(DetailedState.DISCONNECTED);
 
             // Inform WifiConnectivityManager that Wifi is enabled
-            if (mWifiConnectivityManager != null) {
-                mWifiConnectivityManager.setWifiEnabled(true);
-            }
+            mWifiConnectivityManager.setWifiEnabled(true);
             // Inform metrics that Wifi is Enabled (but not yet connected)
             mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
+            // Inform p2p service that wifi is up and ready when applicable
+            p2pSendMessage(WifiStateMachine.CMD_ENABLE_P2P);
         }
 
         @Override
         public void exit() {
             // Let the system know that wifi is not available since we are exiting client mode.
-            setWifiState(WIFI_STATE_DISABLED);
             mNetworkInfo.setIsAvailable(false);
             if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo);
 
             // Inform WifiConnectivityManager that Wifi is disabled
-            if (mWifiConnectivityManager != null) {
-                mWifiConnectivityManager.setWifiEnabled(false);
-            }
+            mWifiConnectivityManager.setWifiEnabled(false);
             // Inform metrics that Wifi is being disabled (Toggled, airplane enabled, etc)
             mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISABLED);
+
+            if (!mWifiNative.removeAllNetworks()) {
+                loge("Failed to remove networks on exiting connect mode");
+            }
+            mWifiInfo.reset();
+            mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
         }
 
         @Override
@@ -5434,73 +4760,57 @@
             String ssid;
             NetworkUpdateResult result;
             Set<Integer> removedNetworkIds;
+            int reasonCode;
+            boolean timedOut;
             logStateAndMessage(message, this);
 
             switch (message.what) {
                 case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
-                    mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_ASSOC_FAILURE);
+                    mWifiDiagnostics.captureBugReportData(
+                            WifiDiagnostics.REPORT_REASON_ASSOC_FAILURE);
                     didBlackListBSSID = false;
                     bssid = (String) message.obj;
+                    timedOut = message.arg1 > 0;
+                    reasonCode = message.arg2;
+                    Log.d(TAG, "Assocation Rejection event: bssid=" + bssid + " reason code="
+                            + reasonCode + " timedOut=" + Boolean.toString(timedOut));
                     if (bssid == null || TextUtils.isEmpty(bssid)) {
                         // If BSSID is null, use the target roam BSSID
                         bssid = mTargetRoamBSSID;
                     }
                     if (bssid != null) {
                         // If we have a BSSID, tell configStore to black list it
-                        if (mWifiConnectivityManager != null) {
-                            didBlackListBSSID = mWifiConnectivityManager.trackBssid(bssid,
-                                    false);
-                        }
+                        didBlackListBSSID = mWifiConnectivityManager.trackBssid(bssid, false,
+                            reasonCode);
                     }
-
                     mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
                             WifiConfiguration.NetworkSelectionStatus
                             .DISABLED_ASSOCIATION_REJECTION);
-
                     mSupplicantStateTracker.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT);
                     //If rejection occurred while Metrics is tracking a ConnnectionEvent, end it.
                     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);
+                    mWifiDiagnostics.captureBugReportData(
+                            WifiDiagnostics.REPORT_REASON_AUTH_FAILURE);
                     mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
-                    if (mTargetNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
-                        mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
-                                WifiConfiguration.NetworkSelectionStatus
-                                        .DISABLED_AUTHENTICATION_FAILURE);
-                    }
+                    mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
+                            WifiConfiguration.NetworkSelectionStatus
+                                    .DISABLED_AUTHENTICATION_FAILURE);
                     //If failure occurred while Metrics is tracking a ConnnectionEvent, end it.
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(getTargetSsid(),
-                            mTargetRoamBSSID,
-                            WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
-                    break;
-                case WifiMonitor.SSID_TEMP_DISABLED:
-                    Log.e(TAG, "Supplicant SSID temporary disabled:"
-                            + mWifiConfigManager.getWifiConfiguration(message.arg1));
-                    mWifiConfigManager.updateNetworkSelectionStatus(
-                            message.arg1,
-                            WifiConfiguration.NetworkSelectionStatus
-                            .DISABLED_AUTHENTICATION_FAILURE);
-                    reportConnectionAttemptEnd(
-                            WifiMetrics.ConnectionEvent.FAILURE_SSID_TEMP_DISABLED,
-                            WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    mWifiLastResortWatchdog.noteConnectionFailureAndTriggerIfNeeded(getTargetSsid(),
-                            mTargetRoamBSSID,
-                            WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
-                    break;
-                case WifiMonitor.SSID_REENABLED:
-                    Log.d(TAG, "Supplicant SSID reenable:"
-                            + mWifiConfigManager.getWifiConfiguration(message.arg1));
-                    // Do not re-enable it in Quality Network Selection since framework has its own
-                    // Algorithm of disable/enable
+                    mWifiInjector.getWifiLastResortWatchdog()
+                            .noteConnectionFailureAndTriggerIfNeeded(
+                                    getTargetSsid(), mTargetRoamBSSID,
+                                    WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
                     break;
                 case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                     SupplicantState state = handleSupplicantStateChange(message);
@@ -5511,8 +4821,10 @@
                             handleNetworkDisconnect();
                         }
                         log("Detected an interface down, restart driver");
-                        transitionTo(mDriverStoppedState);
-                        sendMessage(CMD_START_DRIVER);
+                        // Rely on the fact that this will force us into killing supplicant and then
+                        // restart supplicant from a clean state.
+                        transitionTo(mSupplicantStoppingState);
+                        sendMessage(CMD_START_SUPPLICANT);
                         break;
                     }
 
@@ -5521,9 +4833,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);
                     }
@@ -5537,6 +4851,8 @@
                     break;
                 case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
                     if (message.arg1 == 1) {
+                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST);
                         mWifiNative.disconnect();
                         mTemporarilyDisconnectWifi = true;
                     } else {
@@ -5544,144 +4860,44 @@
                         mTemporarilyDisconnectWifi = false;
                     }
                     break;
-                case CMD_ADD_OR_UPDATE_NETWORK:
-                    // Only the current foreground user can modify networks.
-                    if (!mWifiConfigManager.isCurrentUserProfile(
-                            UserHandle.getUserId(message.sendingUid))) {
-                        loge("Only the current foreground user can modify networks "
-                                + " currentUserId=" + mWifiConfigManager.getCurrentUserId()
-                                + " sendingUserId=" + UserHandle.getUserId(message.sendingUid));
-                        replyToMessage(message, message.what, FAILURE);
-                        break;
-                    }
-
-                    config = (WifiConfiguration) message.obj;
-
-                    if (!recordUidIfAuthorized(config, message.sendingUid,
-                            /* onlyAnnotate */ false)) {
-                        logw("Not authorized to update network "
-                             + " config=" + config.SSID
-                             + " cnid=" + config.networkId
-                             + " uid=" + message.sendingUid);
-                        replyToMessage(message, message.what, FAILURE);
-                        break;
-                    }
-
-                    int res = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
-                    if (res < 0) {
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                    } else {
-                        WifiConfiguration curConfig = getCurrentWifiConfiguration();
-                        if (curConfig != null && config != null) {
-                            WifiConfiguration.NetworkSelectionStatus networkStatus =
-                                    config.getNetworkSelectionStatus();
-                            if (curConfig.priority < config.priority && networkStatus != null
-                                    && !networkStatus.isNetworkPermanentlyDisabled()) {
-                                // Interpret this as a connect attempt
-                                // Set the last selected configuration so as to allow the system to
-                                // stick the last user choice without persisting the choice
-                                mWifiConfigManager.setAndEnableLastSelectedConfiguration(res);
-                                mWifiConfigManager.updateLastConnectUid(config, message.sendingUid);
-                                boolean persist = mWifiConfigManager
-                                        .checkConfigOverridePermission(message.sendingUid);
-                                if (mWifiConnectivityManager != null) {
-                                    mWifiConnectivityManager.connectToUserSelectNetwork(res,
-                                            persist);
-                                }
-
-                                // Remember time of last connection attempt
-                                lastConnectAttemptTimestamp = System.currentTimeMillis();
-                                mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
-
-                                // As a courtesy to the caller, trigger a scan now
-                                startScan(ADD_OR_UPDATE_SOURCE, 0, null, WIFI_WORK_SOURCE);
-                            }
-                        }
-                    }
-                    replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK, res);
-                    break;
                 case CMD_REMOVE_NETWORK:
-                    // Only the current foreground user can modify networks.
-                    if (!mWifiConfigManager.isCurrentUserProfile(
-                            UserHandle.getUserId(message.sendingUid))) {
-                        loge("Only the current foreground user can modify networks "
-                                + " currentUserId=" + mWifiConfigManager.getCurrentUserId()
-                                + " sendingUserId=" + UserHandle.getUserId(message.sendingUid));
-                        replyToMessage(message, message.what, FAILURE);
-                        break;
-                    }
-                    netId = message.arg1;
-
-                    if (!mWifiConfigManager.canModifyNetwork(message.sendingUid, netId,
-                            /* onlyAnnotate */ false)) {
-                        logw("Not authorized to remove network "
-                             + " cnid=" + netId
-                             + " uid=" + message.sendingUid);
-                        replyToMessage(message, message.what, FAILURE);
-                        break;
-                    }
-
-                    ok = mWifiConfigManager.removeNetwork(message.arg1);
-                    if (!ok) {
+                    if (!deleteNetworkConfigAndSendReply(message, false)) {
+                        // failed to remove the config and caller was notified
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+                        break;
                     }
-                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+                    //  we successfully deleted the network config
+                    netId = message.arg1;
+                    if (netId == mTargetNetworkId || netId == mLastNetworkId) {
+                        // Disconnect and let autojoin reselect a new network
+                        sendMessage(CMD_DISCONNECT);
+                    }
                     break;
                 case CMD_ENABLE_NETWORK:
-                    // Only the current foreground user can modify networks.
-                    if (!mWifiConfigManager.isCurrentUserProfile(
-                            UserHandle.getUserId(message.sendingUid))) {
-                        loge("Only the current foreground user can modify networks "
-                                + " currentUserId=" + mWifiConfigManager.getCurrentUserId()
-                                + " sendingUserId=" + UserHandle.getUserId(message.sendingUid));
-                        replyToMessage(message, message.what, FAILURE);
-                        break;
-                    }
-
                     boolean disableOthers = message.arg2 == 1;
                     netId = message.arg1;
-                    config = mWifiConfigManager.getWifiConfiguration(netId);
-                    if (config == null) {
-                        loge("No network with id = " + netId);
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, message.what, FAILURE);
-                        break;
-                    }
-                    // disable other only means select this network, does not mean all other
-                    // networks need to be disabled
                     if (disableOthers) {
-                        // Remember time of last connection attempt
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
-                        mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
+                        // If the app has all the necessary permissions, this will trigger a connect
+                        // attempt.
+                        ok = connectToUserSelectNetwork(netId, message.sendingUid, false);
+                    } else {
+                        ok = mWifiConfigManager.enableNetwork(netId, false, message.sendingUid);
                     }
-                    // Cancel auto roam requests
-                    autoRoamSetBSSID(netId, "any");
-
-                    ok = mWifiConfigManager.enableNetwork(
-                            config, disableOthers, message.sendingUid);
                     if (!ok) {
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                    } else {
-                        if (disableOthers) {
-                            mTargetNetworkId = netId;
-                        }
-                        mWifiConnectivityManager.forceConnectivityScan();
                     }
-
                     replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                     break;
-                case CMD_ENABLE_ALL_NETWORKS:
-                    long time = android.os.SystemClock.elapsedRealtime();
-                    if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) {
-                        mWifiConfigManager.enableAllNetworks();
-                        mLastEnableAllNetworksTime = time;
-                    }
-                    break;
                 case WifiManager.DISABLE_NETWORK:
-                    if (mWifiConfigManager.updateNetworkSelectionStatus(message.arg1,
-                            WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER)) {
+                    netId = message.arg1;
+                    if (mWifiConfigManager.disableNetwork(netId, message.sendingUid)) {
                         replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
+                        if (netId == mTargetNetworkId || netId == mLastNetworkId) {
+                            // Disconnect and let autojoin reselect a new network
+                            sendMessage(CMD_DISCONNECT);
+                        }
                     } else {
+                        loge("Failed to disable network");
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                         replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
                                 WifiManager.ERROR);
@@ -5690,57 +4906,35 @@
                 case CMD_DISABLE_EPHEMERAL_NETWORK:
                     config = mWifiConfigManager.disableEphemeralNetwork((String)message.obj);
                     if (config != null) {
-                        if (config.networkId == mLastNetworkId) {
+                        if (config.networkId == mTargetNetworkId
+                                || config.networkId == mLastNetworkId) {
                             // Disconnect and let autojoin reselect a new network
                             sendMessage(CMD_DISCONNECT);
                         }
                     }
                     break;
-                case CMD_BLACKLIST_NETWORK:
-                    mWifiConfigManager.blackListBssid((String) message.obj);
-                    break;
-                case CMD_CLEAR_BLACKLIST:
-                    mWifiConfigManager.clearBssidBlacklist();
-                    break;
                 case CMD_SAVE_CONFIG:
-                    ok = mWifiConfigManager.saveConfig();
-
-                    if (DBG) logd("did save config " + ok);
+                    ok = mWifiConfigManager.saveToStore(true);
                     replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
-
                     // Inform the backup manager about a data change
                     mBackupManagerProxy.notifyDataChanged();
                     break;
-                case CMD_GET_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what,
-                            mWifiConfigManager.getSavedNetworks());
-                    break;
-                case CMD_HAS_CARRIER_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what,
-                            (Boolean)mWifiConfigManager.hasCarrierNetworks());
-                    break;
                 case WifiMonitor.SUP_REQUEST_IDENTITY:
-                    int networkId = message.arg2;
+                    int supplicantNetworkId = message.arg2;
+                    netId = lookupFrameworkNetworkId(supplicantNetworkId);
                     boolean identitySent = false;
-                    int eapMethod = WifiEnterpriseConfig.Eap.NONE;
-
-                    if (targetWificonfiguration != null
-                            && targetWificonfiguration.enterpriseConfig != null) {
-                        eapMethod = targetWificonfiguration.enterpriseConfig.getEapMethod();
-                    }
-
                     // For SIM & AKA/AKA' EAP method Only, get identity from ICC
                     if (targetWificonfiguration != null
-                            && targetWificonfiguration.networkId == networkId
-                            && (targetWificonfiguration.allowedKeyManagement
-                                    .get(WifiConfiguration.KeyMgmt.IEEE8021X)
-                                || targetWificonfiguration.allowedKeyManagement
-                                    .get(WifiConfiguration.KeyMgmt.WPA_EAP))
-                            && TelephonyUtil.isSimEapMethod(eapMethod)) {
-                        String identity = TelephonyUtil.getSimIdentity(mContext, eapMethod);
+                            && targetWificonfiguration.networkId == netId
+                            && TelephonyUtil.isSimConfig(targetWificonfiguration)) {
+                        String identity =
+                                TelephonyUtil.getSimIdentity(getTelephonyManager(),
+                                        targetWificonfiguration);
                         if (identity != null) {
-                            mWifiNative.simIdentityResponse(networkId, identity);
+                            mWifiNative.simIdentityResponse(supplicantNetworkId, identity);
                             identitySent = true;
+                        } else {
+                            Log.e(TAG, "Unable to retrieve identity from Telephony");
                         }
                     }
                     if (!identitySent) {
@@ -5749,14 +4943,13 @@
                         if (targetWificonfiguration != null && ssid != null
                                 && targetWificonfiguration.SSID != null
                                 && targetWificonfiguration.SSID.equals("\"" + ssid + "\"")) {
-                            mWifiConfigManager.updateNetworkSelectionStatus(targetWificonfiguration,
+                            mWifiConfigManager.updateNetworkSelectionStatus(
+                                    targetWificonfiguration.networkId,
                                     WifiConfiguration.NetworkSelectionStatus
                                             .DISABLED_AUTHENTICATION_NO_CREDENTIALS);
                         }
-                        // Disconnect now, as we don't have any way to fullfill
-                        // the  supplicant request.
-                        mWifiConfigManager.setAndEnableLastSelectedConfiguration(
-                                WifiConfiguration.INVALID_NETWORK_ID);
+                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                StaEvent.DISCONNECT_GENERIC);
                         mWifiNative.disconnect();
                     }
                     break;
@@ -5774,158 +4967,64 @@
                         loge("Invalid sim auth request");
                     }
                     break;
-                case CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what,
-                            mWifiConfigManager.getPrivilegedSavedNetworks());
-                    break;
                 case CMD_GET_MATCHING_CONFIG:
                     replyToMessage(message, message.what,
-                            mWifiConfigManager.getMatchingConfig((ScanResult)message.obj));
+                            mPasspointManager.getMatchingWifiConfig((ScanResult) message.obj));
                     break;
                 case CMD_RECONNECT:
-                    if (mWifiConnectivityManager != null) {
-                        mWifiConnectivityManager.forceConnectivityScan();
-                    }
+                    mWifiConnectivityManager.forceConnectivityScan();
                     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;
-                case CMD_AUTO_ROAM:
+                case CMD_START_ROAM:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     return HANDLED;
-                case CMD_AUTO_CONNECT:
-                    /* Work Around: wpa_supplicant can get in a bad state where it returns a non
-                     * associated status to the STATUS command but somehow-someplace still thinks
-                     * it is associated and thus will ignore select/reconnect command with
-                     * following message:
-                     * "Already associated with the selected network - do nothing"
-                     *
-                     * Hence, sends a disconnect to supplicant first.
-                     */
-                    didDisconnect = false;
-                    if (getCurrentState() != mDisconnectedState) {
-                        /** Supplicant will ignore the reconnect if we are currently associated,
-                         * hence trigger a disconnect
-                         */
-                        didDisconnect = true;
-                        mWifiNative.disconnect();
-                    }
-
+                case CMD_START_CONNECT:
                     /* connect command coming from auto-join */
                     netId = message.arg1;
-                    mTargetNetworkId = netId;
-                    mTargetRoamBSSID = (String) message.obj;
-                    config = mWifiConfigManager.getWifiConfiguration(netId);
-                    logd("CMD_AUTO_CONNECT sup state "
+                    bssid = (String) message.obj;
+                    config = mWifiConfigManager.getConfiguredNetworkWithPassword(netId);
+                    logd("CMD_START_CONNECT sup state "
                             + mSupplicantStateTracker.getSupplicantStateName()
                             + " my state " + getCurrentState().getName()
                             + " nid=" + Integer.toString(netId)
-                            + " roam=" + Boolean.toString(mAutoRoaming));
+                            + " roam=" + Boolean.toString(mIsAutoRoaming));
                     if (config == null) {
-                        loge("AUTO_CONNECT and no config, bail out...");
+                        loge("CMD_START_CONNECT and no config, bail out...");
                         break;
                     }
+                    mTargetNetworkId = netId;
+                    setTargetBssid(config, bssid);
 
-                    /* Make sure we cancel any previous roam request */
-                    setTargetBssid(config, mTargetRoamBSSID);
-
-                    /* Save the network config */
-                    logd("CMD_AUTO_CONNECT will save config -> " + config.SSID
-                            + " nid=" + Integer.toString(netId));
-                    result = mWifiConfigManager.saveNetwork(config, WifiConfiguration.UNKNOWN_UID);
-                    netId = result.getNetworkId();
-                    logd("CMD_AUTO_CONNECT did save config -> "
-                            + " nid=" + Integer.toString(netId));
-
-                    // Since we updated the config,read it back from config store:
-                    config = mWifiConfigManager.getWifiConfiguration(netId);
-                    if (config == null) {
-                        loge("CMD_AUTO_CONNECT couldn't update the config, got null config");
-                        break;
-                    }
-                    if (netId != config.networkId) {
-                        loge("CMD_AUTO_CONNECT couldn't update the config, want"
-                                + " nid=" + Integer.toString(netId) + " but got" + config.networkId);
-                        break;
-                    }
-
-                    if (deferForUserInput(message, netId, false)) {
-                        break;
-                    } else if (mWifiConfigManager.getWifiConfiguration(netId).userApproved ==
-                                                                   WifiConfiguration.USER_BANNED) {
-                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
-                                WifiManager.NOT_AUTHORIZED);
-                        break;
-                    }
-
-                    // If we're autojoining a network that the user or an app explicitly selected,
-                    // keep track of the UID that selected it.
-                    // TODO(b/26786318): Keep track of the lastSelectedConfiguration and the
-                    // lastConnectUid on a per-user basis.
-                    int lastConnectUid = WifiConfiguration.UNKNOWN_UID;
-
-                    //Start a new ConnectionEvent due to auto_connect, assume we are connecting
-                    //between different networks due to QNS, setting ROAM_UNRELATED
-                    mWifiMetrics.startConnectionEvent(config, mTargetRoamBSSID,
+                    reportConnectionAttemptStart(config, mTargetRoamBSSID,
                             WifiMetricsProto.ConnectionEvent.ROAM_UNRELATED);
-                    if (!didDisconnect) {
-                        //If we were originally disconnected, then this was not any kind of ROAM
-                        mWifiMetrics.setConnectionEventRoamType(
-                                WifiMetricsProto.ConnectionEvent.ROAM_NONE);
-                    }
-                    //Determine if this CONNECTION is for a user selection
-                    if (mWifiConfigManager.isLastSelectedConfiguration(config)
-                            && mWifiConfigManager.isCurrentUserProfile(
-                                UserHandle.getUserId(config.lastConnectUid))) {
-                        lastConnectUid = config.lastConnectUid;
-                        mWifiMetrics.setConnectionEventRoamType(
-                                WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);
-                    }
-                    if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ false,
-                            lastConnectUid) && mWifiNative.reconnect()) {
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
-                        targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
-                        config = mWifiConfigManager.getWifiConfiguration(netId);
-                        if (config != null
-                                && !mWifiConfigManager.isLastSelectedConfiguration(config)) {
-                            // If we autojoined a different config than the user selected one,
-                            // it means we could not see the last user selection,
-                            // or that the last user selection was faulty and ended up blacklisted
-                            // for some reason (in which case the user is notified with an error
-                            // message in the Wifi picker), and thus we managed to auto-join away
-                            // from the selected  config. -> in that case we need to forget
-                            // the selection because we don't want to abruptly switch back to it.
-                            //
-                            // Note that the user selection is also forgotten after a period of time
-                            // during which the device has been disconnected.
-                            // The default value is 30 minutes : see the code path at bottom of
-                            // setScanResults() function.
-                            mWifiConfigManager.
-                                 setAndEnableLastSelectedConfiguration(
-                                         WifiConfiguration.INVALID_NETWORK_ID);
-                        }
-                        mAutoRoaming = false;
-                        if (isRoaming() || linkDebouncing) {
+                    if (mWifiNative.connectToNetwork(config)) {
+                        mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT, config);
+                        lastConnectAttemptTimestamp = mClock.getWallClockMillis();
+                        targetWificonfiguration = config;
+                        mIsAutoRoaming = false;
+                        if (isLinkDebouncing()) {
                             transitionTo(mRoamingState);
-                        } else if (didDisconnect) {
+                        } else if (getCurrentState() != mDisconnectedState) {
                             transitionTo(mDisconnectingState);
                         }
                     } else {
-                        loge("Failed to connect config: " + config + " netId: " + netId);
-                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
-                                WifiManager.ERROR);
+                        loge("CMD_START_CONNECT Failed to start connection to network " + config);
                         reportConnectionAttemptEnd(
                                 WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
                                 WifiMetricsProto.ConnectionEvent.HLF_NONE);
+                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
+                                WifiManager.ERROR);
                         break;
                     }
                     break;
@@ -5948,21 +5047,8 @@
                     }
                     break;
                 case WifiManager.CONNECT_NETWORK:
-                    // Only the current foreground user and System UI (which runs as user 0 but acts
-                    // on behalf of the current foreground user) can modify networks.
-                    if (!mWifiConfigManager.isCurrentUserProfile(
-                            UserHandle.getUserId(message.sendingUid)) &&
-                            message.sendingUid != mSystemUiUid) {
-                        loge("Only the current foreground user can modify networks "
-                                + " currentUserId=" + mWifiConfigManager.getCurrentUserId()
-                                + " sendingUserId=" + UserHandle.getUserId(message.sendingUid));
-                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
-                                       WifiManager.NOT_AUTHORIZED);
-                        break;
-                    }
-
                     /**
-                     *  The connect message can contain a network id passed as arg1 on message or
+                     * The connect message can contain a network id passed as arg1 on message or
                      * or a config passed as obj on message.
                      * For a new network, a config is passed to create and connect.
                      * For an existing network, a network id is passed
@@ -5970,153 +5056,36 @@
                     netId = message.arg1;
                     config = (WifiConfiguration) message.obj;
                     mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
-                    boolean updatedExisting = false;
-
-                    /* Save the network config */
+                    boolean hasCredentialChanged = false;
+                    // New network addition.
                     if (config != null) {
-                        // When connecting to an access point, WifiStateMachine wants to update the
-                        // relevant config with administrative data. This update should not be
-                        // considered a 'real' update, therefore lockdown by Device Owner must be
-                        // disregarded.
-                        if (!recordUidIfAuthorized(config, message.sendingUid,
-                                /* onlyAnnotate */ true)) {
-                            logw("Not authorized to update network "
-                                 + " config=" + config.SSID
-                                 + " cnid=" + config.networkId
-                                 + " uid=" + message.sendingUid);
+                        result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
+                        if (!result.isSuccess()) {
+                            loge("CONNECT_NETWORK adding/updating config=" + config + " failed");
+                            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                             replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
-                                           WifiManager.NOT_AUTHORIZED);
+                                    WifiManager.ERROR);
                             break;
                         }
-                        String configKey = config.configKey(true /* allowCached */);
-                        WifiConfiguration savedConfig =
-                                mWifiConfigManager.getWifiConfiguration(configKey);
-                        if (savedConfig != null) {
-                            // There is an existing config with this netId, but it wasn't exposed
-                            // (either AUTO_JOIN_DELETED or ephemeral; see WifiConfigManager#
-                            // getConfiguredNetworks). Remove those bits and update the config.
-                            config = savedConfig;
-                            logd("CONNECT_NETWORK updating existing config with id=" +
-                                    config.networkId + " configKey=" + configKey);
-                            config.ephemeral = false;
-                            mWifiConfigManager.updateNetworkSelectionStatus(config,
-                                    WifiConfiguration.NetworkSelectionStatus
-                                    .NETWORK_SELECTION_ENABLE);
-                            updatedExisting = true;
-                        }
-
-                        result = mWifiConfigManager.saveNetwork(config, message.sendingUid);
                         netId = result.getNetworkId();
+                        hasCredentialChanged = result.hasCredentialChanged();
                     }
-                    config = mWifiConfigManager.getWifiConfiguration(netId);
-                    if (config == null) {
-                        logd("CONNECT_NETWORK no config for id=" + Integer.toString(netId) + " "
-                                + mSupplicantStateTracker.getSupplicantStateName() + " my state "
-                                + getCurrentState().getName());
-                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
-                    mTargetNetworkId = netId;
-                    autoRoamSetBSSID(netId, "any");
-                    if (message.sendingUid == Process.WIFI_UID
-                        || message.sendingUid == Process.SYSTEM_UID) {
-                        // As a sanity measure, clear the BSSID in the supplicant network block.
-                        // If system or Wifi Settings want to connect, they will not
-                        // specify the BSSID.
-                        // If an app however had added a BSSID to this configuration, and the BSSID
-                        // was wrong, Then we would forever fail to connect until that BSSID
-                        // is cleaned up.
-                        clearConfigBSSID(config, "CONNECT_NETWORK");
-                    }
-
-                    if (deferForUserInput(message, netId, true)) {
-                        break;
-                    } else if (mWifiConfigManager.getWifiConfiguration(netId).userApproved ==
-                                                                    WifiConfiguration.USER_BANNED) {
+                    if (!connectToUserSelectNetwork(
+                            netId, message.sendingUid, hasCredentialChanged)) {
+                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                         replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                 WifiManager.NOT_AUTHORIZED);
                         break;
                     }
-
-                    mAutoRoaming = false;
-
-                    /* Tell network selection the user did try to connect to that network if from
-                    settings */
-                    boolean persist =
-                        mWifiConfigManager.checkConfigOverridePermission(message.sendingUid);
-
-
-                    mWifiConfigManager.setAndEnableLastSelectedConfiguration(netId);
-                    if (mWifiConnectivityManager != null) {
-                        mWifiConnectivityManager.connectToUserSelectNetwork(netId, persist);
-                    }
-                    didDisconnect = false;
-                    if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID
-                            && mLastNetworkId != netId) {
-                        /** Supplicant will ignore the reconnect if we are currently associated,
-                         * hence trigger a disconnect
-                         */
-                        didDisconnect = true;
-                        mWifiNative.disconnect();
-                    }
-
-                    //Start a new ConnectionEvent due to connect_network, this is always user
-                    //selected
-                    mWifiMetrics.startConnectionEvent(config, mTargetRoamBSSID,
-                            WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);
-                    if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true,
-                            message.sendingUid) && mWifiNative.reconnect()) {
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
-                        targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
-
-                        /* The state tracker handles enabling networks upon completion/failure */
-                        mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
-                        replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
-                        if (didDisconnect) {
-                            /* Expect a disconnection from the old connection */
-                            transitionTo(mDisconnectingState);
-                        } else if (updatedExisting && getCurrentState() == mConnectedState &&
-                                getCurrentWifiConfiguration().networkId == netId) {
-                            // Update the current set of network capabilities, but stay in the
-                            // current state.
-                            updateCapabilities(config);
-                        } else {
-                            /**
-                             * Directly go to disconnected state where we
-                             * process the connection events from supplicant
-                             */
-                            transitionTo(mDisconnectedState);
-                        }
-                    } else {
-                        loge("Failed to connect config: " + config + " netId: " + netId);
-                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        reportConnectionAttemptEnd(
-                                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
-                                WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                        break;
-                    }
+                    mWifiMetrics.logStaEvent(StaEvent.TYPE_CONNECT_NETWORK, config);
+                    broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+                    replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.SAVE_NETWORK:
-                    mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
-                    // Fall thru
-                case WifiStateMachine.CMD_AUTO_SAVE_NETWORK:
-                    // Only the current foreground user can modify networks.
-                    if (!mWifiConfigManager.isCurrentUserProfile(
-                            UserHandle.getUserId(message.sendingUid))) {
-                        loge("Only the current foreground user can modify networks "
-                                + " currentUserId=" + mWifiConfigManager.getCurrentUserId()
-                                + " sendingUserId=" + UserHandle.getUserId(message.sendingUid));
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.NOT_AUTHORIZED);
-                        break;
-                    }
-
-                    lastSavedConfigurationAttempt = null; // Used for debug
                     config = (WifiConfiguration) message.obj;
+                    mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
                     if (config == null) {
-                        loge("ERROR: SAVE_NETWORK with null configuration"
+                        loge("SAVE_NETWORK with null configuration"
                                 + mSupplicantStateTracker.getSupplicantStateName()
                                 + " my state " + getCurrentState().getName());
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
@@ -6124,144 +5093,106 @@
                                 WifiManager.ERROR);
                         break;
                     }
-                    lastSavedConfigurationAttempt = new WifiConfiguration(config);
-                    int nid = config.networkId;
-                    logd("SAVE_NETWORK id=" + Integer.toString(nid)
-                                + " config=" + config.SSID
-                                + " nid=" + config.networkId
-                                + " supstate=" + mSupplicantStateTracker.getSupplicantStateName()
-                                + " my state " + getCurrentState().getName());
-
-                    // Only record the uid if this is user initiated
-                    boolean checkUid = (message.what == WifiManager.SAVE_NETWORK);
-                    if (checkUid && !recordUidIfAuthorized(config, message.sendingUid,
-                            /* onlyAnnotate */ false)) {
-                        logw("Not authorized to update network "
-                             + " config=" + config.SSID
-                             + " cnid=" + config.networkId
-                             + " uid=" + message.sendingUid);
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                       WifiManager.NOT_AUTHORIZED);
-                        break;
-                    }
-
-                    result = mWifiConfigManager.saveNetwork(config, WifiConfiguration.UNKNOWN_UID);
-                    if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
-                        if (mWifiInfo.getNetworkId() == result.getNetworkId()) {
-                            if (result.hasIpChanged()) {
-                                // The currently connection configuration was changed
-                                // We switched from DHCP to static or from static to DHCP, or the
-                                // static IP address has changed.
-                                log("Reconfiguring IP on connection");
-                                // TODO: clear addresses and disable IPv6
-                                // to simplify obtainingIpState.
-                                transitionTo(mObtainingIpState);
-                            }
-                            if (result.hasProxyChanged()) {
-                                log("Reconfiguring proxy on connection");
-                                mIpManager.setHttpProxy(
-                                        mWifiConfigManager.getProxyProperties(mLastNetworkId));
-                            }
-                        }
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
-                        broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
-
-                        if (DBG) {
-                           logd("Success save network nid="
-                                    + Integer.toString(result.getNetworkId()));
-                        }
-
-                        /**
-                         * If the command comes from WifiManager, then
-                         * tell autojoin the user did try to modify and save that network,
-                         * and interpret the SAVE_NETWORK as a request to connect
-                         */
-                        boolean user = message.what == WifiManager.SAVE_NETWORK;
-
-                        // Did this connect come from settings
-                        boolean persistConnect =
-                                mWifiConfigManager.checkConfigOverridePermission(
-                                        message.sendingUid);
-
-                        if (user) {
-                            mWifiConfigManager.updateLastConnectUid(config, message.sendingUid);
-                            mWifiConfigManager.writeKnownNetworkHistory();
-                        }
-
-                        if (mWifiConnectivityManager != null) {
-                            mWifiConnectivityManager.connectToUserSelectNetwork(
-                                    result.getNetworkId(), persistConnect);
-                        }
-                    } else {
-                        loge("Failed to save network");
+                    result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
+                    if (!result.isSuccess()) {
+                        loge("SAVE_NETWORK adding/updating config=" + config + " failed");
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                         replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
                                 WifiManager.ERROR);
+                        break;
                     }
+                    if (!mWifiConfigManager.enableNetwork(
+                            result.getNetworkId(), false, message.sendingUid)) {
+                        loge("SAVE_NETWORK enabling config=" + config + " failed");
+                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                        break;
+                    }
+                    netId = result.getNetworkId();
+                    if (mWifiInfo.getNetworkId() == netId) {
+                        if (result.hasCredentialChanged()) {
+                            // The network credentials changed and we're connected to this network,
+                            // start a new connection with the updated credentials.
+                            logi("SAVE_NETWORK credential changed for config=" + config.configKey()
+                                    + ", Reconnecting.");
+                            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+                        } else {
+                            if (result.hasProxyChanged()) {
+                                log("Reconfiguring proxy on connection");
+                                mIpManager.setHttpProxy(
+                                        getCurrentWifiConfiguration().getHttpProxy());
+                            }
+                            if (result.hasIpChanged()) {
+                                // The current connection configuration was changed
+                                // We switched from DHCP to static or from static to DHCP, or the
+                                // static IP address has changed.
+                                log("Reconfiguring IP on connection");
+                                // TODO(b/36576642): clear addresses and disable IPv6
+                                // to simplify obtainingIpState.
+                                transitionTo(mObtainingIpState);
+                            }
+                        }
+                    }
+                    broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+                    replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.FORGET_NETWORK:
-                    // Only the current foreground user can modify networks.
-                    if (!mWifiConfigManager.isCurrentUserProfile(
-                            UserHandle.getUserId(message.sendingUid))) {
-                        loge("Only the current foreground user can modify networks "
-                                + " currentUserId=" + mWifiConfigManager.getCurrentUserId()
-                                + " sendingUserId=" + UserHandle.getUserId(message.sendingUid));
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
-                                WifiManager.NOT_AUTHORIZED);
+                    if (!deleteNetworkConfigAndSendReply(message, true)) {
+                        // Caller was notified of failure, nothing else to do
                         break;
                     }
-
-                    // Debug only, remember last configuration that was forgotten
-                    WifiConfiguration toRemove
-                            = mWifiConfigManager.getWifiConfiguration(message.arg1);
-                    if (toRemove == null) {
-                        lastForgetConfigurationAttempt = null;
-                    } else {
-                        lastForgetConfigurationAttempt = new WifiConfiguration(toRemove);
-                    }
-                    // check that the caller owns this network
+                    // the network was deleted
                     netId = message.arg1;
-
-                    if (!mWifiConfigManager.canModifyNetwork(message.sendingUid, netId,
-                            /* onlyAnnotate */ false)) {
-                        logw("Not authorized to forget network "
-                             + " cnid=" + netId
-                             + " uid=" + message.sendingUid);
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
-                                WifiManager.NOT_AUTHORIZED);
-                        break;
-                    }
-
-                    if (mWifiConfigManager.forgetNetwork(message.arg1)) {
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
-                        broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_FORGOT,
-                                (WifiConfiguration) message.obj);
-                    } else {
-                        loge("Failed to forget network");
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
-                                WifiManager.ERROR);
+                    if (netId == mTargetNetworkId || netId == mLastNetworkId) {
+                        // Disconnect and let autojoin reselect a new network
+                        sendMessage(CMD_DISCONNECT);
                     }
                     break;
                 case WifiManager.START_WPS:
                     WpsInfo wpsInfo = (WpsInfo) message.obj;
-                    WpsResult wpsResult;
+                    if (wpsInfo == null) {
+                        loge("Cannot start WPS with null WpsInfo object");
+                        replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
+                        break;
+                    }
+                    WpsResult wpsResult = new WpsResult();
+                    // TODO(b/32898136): Not needed when we start deleting networks from supplicant
+                    // on disconnect.
+                    if (!mWifiNative.removeAllNetworks()) {
+                        loge("Failed to remove networks before WPS");
+                    }
                     switch (wpsInfo.setup) {
                         case WpsInfo.PBC:
-                            wpsResult = mWifiConfigManager.startWpsPbc(wpsInfo);
+                            if (mWifiNative.startWpsPbc(wpsInfo.BSSID)) {
+                                wpsResult.status = WpsResult.Status.SUCCESS;
+                            } else {
+                                Log.e(TAG, "Failed to start WPS push button configuration");
+                                wpsResult.status = WpsResult.Status.FAILURE;
+                            }
                             break;
                         case WpsInfo.KEYPAD:
-                            wpsResult = mWifiConfigManager.startWpsWithPinFromAccessPoint(wpsInfo);
+                            if (mWifiNative.startWpsRegistrar(wpsInfo.BSSID, wpsInfo.pin)) {
+                                wpsResult.status = WpsResult.Status.SUCCESS;
+                            } else {
+                                Log.e(TAG, "Failed to start WPS push button configuration");
+                                wpsResult.status = WpsResult.Status.FAILURE;
+                            }
                             break;
                         case WpsInfo.DISPLAY:
-                            wpsResult = mWifiConfigManager.startWpsWithPinFromDevice(wpsInfo);
+                            wpsResult.pin = mWifiNative.startWpsPinDisplay(wpsInfo.BSSID);
+                            if (!TextUtils.isEmpty(wpsResult.pin)) {
+                                wpsResult.status = WpsResult.Status.SUCCESS;
+                            } else {
+                                Log.e(TAG, "Failed to start WPS pin method configuration");
+                                wpsResult.status = WpsResult.Status.FAILURE;
+                            }
                             break;
                         default:
                             wpsResult = new WpsResult(Status.FAILURE);
                             loge("Invalid setup for WPS");
                             break;
                     }
-                    mWifiConfigManager.setAndEnableLastSelectedConfiguration
-                            (WifiConfiguration.INVALID_NETWORK_ID);
                     if (wpsResult.status == Status.SUCCESS) {
                         replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
                         transitionTo(mWpsRunningState);
@@ -6275,12 +5206,9 @@
                     // right ScanDetail to populate metrics.
                     String someBssid = (String) message.obj;
                     if (someBssid != null) {
-                        //Get the config associated with this connection attempt
-                        WifiConfiguration someConf =
-                                mWifiConfigManager.getWifiConfiguration(mTargetNetworkId);
-                        // Get the ScanDetail associated with this BSSID
-                        ScanDetailCache scanDetailCache = mWifiConfigManager.getScanDetailCache(
-                                someConf);
+                        // Get the ScanDetail associated with this BSSID.
+                        ScanDetailCache scanDetailCache =
+                                mWifiConfigManager.getScanDetailCacheForNetwork(mTargetNetworkId);
                         if (scanDetailCache != null) {
                             mWifiMetrics.setConnectionScanDetail(scanDetailCache.getScanDetail(
                                     someBssid));
@@ -6288,16 +5216,42 @@
                     }
                     return NOT_HANDLED;
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                    if (DBG) log("Network connection established");
-                    mLastNetworkId = message.arg1;
+                    if (mVerboseLoggingEnabled) log("Network connection established");
+                    mLastNetworkId = lookupFrameworkNetworkId(message.arg1);
                     mLastBssid = (String) message.obj;
-
-                    mWifiInfo.setBSSID(mLastBssid);
-                    mWifiInfo.setNetworkId(mLastNetworkId);
-                    mWifiQualifiedNetworkSelector
-                            .enableBssidForQualityNetworkSelection(mLastBssid, true);
-                    sendNetworkStateChangeBroadcast(mLastBssid);
-                    transitionTo(mObtainingIpState);
+                    reasonCode = message.arg2;
+                    // TODO: This check should not be needed after WifiStateMachinePrime refactor.
+                    // Currently, the last connected network configuration is left in
+                    // wpa_supplicant, this may result in wpa_supplicant initiating connection
+                    // to it after a config store reload. Hence the old network Id lookups may not
+                    // work, so disconnect the network and let network selector reselect a new
+                    // network.
+                    config = getCurrentWifiConfiguration();
+                    if (config != null) {
+                        mWifiInfo.setBSSID(mLastBssid);
+                        mWifiInfo.setNetworkId(mLastNetworkId);
+                        mWifiConnectivityManager.trackBssid(mLastBssid, true, reasonCode);
+                        // We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA'
+                        if (config.enterpriseConfig != null
+                                && TelephonyUtil.isSimEapMethod(
+                                        config.enterpriseConfig.getEapMethod())) {
+                            String anonymousIdentity = mWifiNative.getEapAnonymousIdentity();
+                            if (anonymousIdentity != null) {
+                                config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
+                            } else {
+                                Log.d(TAG, "Failed to get updated anonymous identity"
+                                        + " from supplicant, reset it in WifiConfiguration.");
+                                config.enterpriseConfig.setAnonymousIdentity(null);
+                            }
+                            mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
+                        }
+                        sendNetworkStateChangeBroadcast(mLastBssid);
+                        transitionTo(mObtainingIpState);
+                    } else {
+                        logw("Connected to unknown networkId " + mLastNetworkId
+                                + ", disconnecting...");
+                        sendMessage(CMD_DISCONNECT);
+                    }
                     break;
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                     // Calling handleNetworkDisconnect here is redundant because we might already
@@ -6309,39 +5263,48 @@
                     // 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;
-                case CMD_ADD_PASSPOINT_MO:
-                    res = mWifiConfigManager.addPasspointManagementObject((String) message.obj);
-                    replyToMessage(message, message.what, res);
-                    break;
-                case CMD_MODIFY_PASSPOINT_MO:
-                    if (message.obj != null) {
-                        Bundle bundle = (Bundle) message.obj;
-                        ArrayList<PasspointManagementObjectDefinition> mos =
-                                bundle.getParcelableArrayList("MOS");
-                        res = mWifiConfigManager.modifyPasspointMo(bundle.getString("FQDN"), mos);
-                    } else {
-                        res = 0;
-                    }
-                    replyToMessage(message, message.what, res);
-
-                    break;
                 case CMD_QUERY_OSU_ICON:
-                    if (mWifiConfigManager.queryPasspointIcon(
-                            ((Bundle) message.obj).getLong("BSSID"),
-                            ((Bundle) message.obj).getString("FILENAME"))) {
-                        res = 1;
-                    } else {
-                        res = 0;
-                    }
-                    replyToMessage(message, message.what, res);
+                    mPasspointManager.queryPasspointIcon(
+                            ((Bundle) message.obj).getLong(EXTRA_OSU_ICON_QUERY_BSSID),
+                            ((Bundle) message.obj).getString(EXTRA_OSU_ICON_QUERY_FILENAME));
                     break;
                 case CMD_MATCH_PROVIDER_NETWORK:
-                    res = mWifiConfigManager.matchProviderWithCurrentNetwork((String) message.obj);
-                    replyToMessage(message, message.what, res);
+                    // TODO(b/31065385): Passpoint config management.
+                    replyToMessage(message, message.what, 0);
+                    break;
+                case CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG:
+                    PasspointConfiguration passpointConfig = (PasspointConfiguration) message.obj;
+                    if (mPasspointManager.addOrUpdateProvider(passpointConfig, message.arg1)) {
+                        String fqdn = passpointConfig.getHomeSp().getFqdn();
+                        if (isProviderOwnedNetwork(mTargetNetworkId, fqdn)
+                                || isProviderOwnedNetwork(mLastNetworkId, fqdn)) {
+                            logd("Disconnect from current network since its provider is updated");
+                            sendMessage(CMD_DISCONNECT);
+                        }
+                        replyToMessage(message, message.what, SUCCESS);
+                    } else {
+                        replyToMessage(message, message.what, FAILURE);
+                    }
+                    break;
+                case CMD_REMOVE_PASSPOINT_CONFIG:
+                    String fqdn = (String) message.obj;
+                    if (mPasspointManager.removeProvider(fqdn)) {
+                        if (isProviderOwnedNetwork(mTargetNetworkId, fqdn)
+                                || isProviderOwnedNetwork(mLastNetworkId, fqdn)) {
+                            logd("Disconnect from current network since its provider is removed");
+                            sendMessage(CMD_DISCONNECT);
+                        }
+                        replyToMessage(message, message.what, SUCCESS);
+                    } else {
+                        replyToMessage(message, message.what, FAILURE);
+                    }
+                    break;
+                case CMD_ENABLE_P2P:
+                    p2pSendMessage(WifiStateMachine.CMD_ENABLE_P2P);
                     break;
                 default:
                     return NOT_HANDLED;
@@ -6353,9 +5316,7 @@
     private void updateCapabilities(WifiConfiguration config) {
         NetworkCapabilities networkCapabilities = new NetworkCapabilities(mDfltNetworkCapabilities);
         if (config != null) {
-            Log.d(TAG, "updateCapabilities for config:" + config.getPrintableSsid() + config.ephemeral +
-                    "," + config.isCarrierNetwork);
-            if (config.ephemeral && !config.isCarrierNetwork) {
+            if (config.ephemeral) {
                 networkCapabilities.removeCapability(
                         NetworkCapabilities.NET_CAPABILITY_TRUSTED);
             } else {
@@ -6376,16 +5337,38 @@
         mNetworkAgent.sendNetworkCapabilities(networkCapabilities);
     }
 
+    /**
+     * Checks if the given network |networkdId| is provided by the given Passpoint provider with
+     * |providerFqdn|.
+     *
+     * @param networkId The ID of the network to check
+     * @param providerFqdn The FQDN of the Passpoint provider
+     * @return true if the given network is provided by the given Passpoint provider
+     */
+    private boolean isProviderOwnedNetwork(int networkId, String providerFqdn) {
+        if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
+            return false;
+        }
+        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        return TextUtils.equals(config.FQDN, providerFqdn);
+    }
+
     private class WifiNetworkAgent extends NetworkAgent {
         public WifiNetworkAgent(Looper l, Context c, String TAG, NetworkInfo ni,
                 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);
         }
 
@@ -6393,14 +5376,17 @@
         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));
                 }
+                mWifiMetrics.logStaEvent(StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK);
                 doNetworkStatus(status);
             }
         }
@@ -6526,24 +5512,26 @@
         // Full badn scans with exponential backoff for the purpose or extended roaming and
         // network switching are performed unconditionally.
         ScanDetailCache scanDetailCache =
-                mWifiConfigManager.getScanDetailCache(config);
+                mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
         if (scanDetailCache == null
                 || !config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
                 || scanDetailCache.size() > 6) {
             //return true but to not trigger the scan
             return true;
         }
-        HashSet<Integer> freqs = mWifiConfigManager.makeChannelList(config, ONE_HOUR_MILLI);
+        Set<Integer> freqs =
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        config.networkId, ONE_HOUR_MILLI, mWifiInfo.getFrequency());
         if (freqs != null && freqs.size() != 0) {
-            //if (DBG) {
+            //if (mVerboseLoggingEnabled) {
             logd("starting scan for " + config.configKey() + " with " + freqs);
             //}
-            Set<Integer> hiddenNetworkIds = new HashSet<>();
+            List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks = new ArrayList<>();
             if (config.hiddenSSID) {
-                hiddenNetworkIds.add(config.networkId);
+                hiddenNetworks.add(new WifiScanner.ScanSettings.HiddenNetwork(config.SSID));
             }
             // Call wifi native to start the scan
-            if (startScanNative(freqs, hiddenNetworkIds, WIFI_WORK_SOURCE)) {
+            if (startScanNative(freqs, hiddenNetworks, WIFI_WORK_SOURCE)) {
                 messageHandlingStatus = MESSAGE_HANDLING_STATUS_OK;
             } else {
                 // used for debug only, mark scan as failed
@@ -6551,33 +5539,11 @@
             }
             return true;
         } else {
-            if (DBG) logd("no channels for " + config.configKey());
+            if (mVerboseLoggingEnabled) logd("no channels for " + config.configKey());
             return false;
         }
     }
 
-    void clearCurrentConfigBSSID(String dbg) {
-        // Clear the bssid in the current config's network block
-        WifiConfiguration config = getCurrentWifiConfiguration();
-        if (config == null)
-            return;
-        clearConfigBSSID(config, dbg);
-    }
-    void clearConfigBSSID(WifiConfiguration config, String dbg) {
-        if (config == null)
-            return;
-        if (DBG) {
-            logd(dbg + " " + mTargetRoamBSSID + " config " + config.configKey()
-                    + " config.NetworkSelectionStatus.mNetworkSelectionBSSID "
-                    + config.getNetworkSelectionStatus().getNetworkSelectionBSSID());
-        }
-        if (DBG) {
-           logd(dbg + " " + config.SSID
-                    + " nid=" + Integer.toString(config.networkId));
-        }
-        mWifiConfigManager.saveWifiConfigBSSID(config, "any");
-    }
-
     class L2ConnectedState extends State {
         @Override
         public void enter() {
@@ -6598,7 +5564,7 @@
             // We must clear the config BSSID, as the wifi chipset may decide to roam
             // from this point on and having the BSSID specified in the network block would
             // cause the roam to faile and the device to disconnect
-            clearCurrentConfigBSSID("L2ConnectedState");
+            clearTargetBssid("L2ConnectedState");
             mCountryCode.setReadyForChange(false);
             mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
         }
@@ -6612,7 +5578,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) {
@@ -6666,21 +5632,32 @@
                     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);
-                    handleIpReachabilityLost();
-                    transitionTo(mDisconnectingState);
+                    if (mVerboseLoggingEnabled && message.obj != null) log((String) message.obj);
+                    if (mIpReachabilityDisconnectEnabled) {
+                        handleIpReachabilityLost();
+                        transitionTo(mDisconnectingState);
+                    } else {
+                        logd("CMD_IP_REACHABILITY_LOST but disconnect disabled -- ignore");
+                    }
                     break;
                 case CMD_DISCONNECT:
+                    mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                StaEvent.DISCONNECT_UNKNOWN);
                     mWifiNative.disconnect();
                     transitionTo(mDisconnectingState);
                     break;
                 case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
                     if (message.arg1 == 1) {
+                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST);
                         mWifiNative.disconnect();
                         mTemporarilyDisconnectWifi = true;
                         transitionTo(mDisconnectingState);
@@ -6690,35 +5667,32 @@
                     if (message.arg1 != CONNECT_MODE) {
                         sendMessage(CMD_DISCONNECT);
                         deferMessage(message);
-                        if (message.arg1 == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
-                            noteWifiDisabledWhileAssociated();
-                        }
                     }
-                    mWifiConfigManager.
-                                setAndEnableLastSelectedConfiguration(
-                                        WifiConfiguration.INVALID_NETWORK_ID);
                     break;
                     /* Ignore connection to same network */
                 case WifiManager.CONNECT_NETWORK:
                     int netId = message.arg1;
                     if (mWifiInfo.getNetworkId() == netId) {
+                        replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                         break;
                     }
                     return NOT_HANDLED;
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
                     mWifiInfo.setBSSID((String) message.obj);
-                    mLastNetworkId = message.arg1;
+                    mLastNetworkId = lookupFrameworkNetworkId(message.arg1);
                     mWifiInfo.setNetworkId(mLastNetworkId);
-                    if(!mLastBssid.equals((String) message.obj)) {
+                    if(!mLastBssid.equals(message.obj)) {
                         mLastBssid = (String) message.obj;
                         sendNetworkStateChangeBroadcast(mLastBssid);
                     }
                     break;
                 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 (mEnableChipWakeUpWhenAssociated) {
+                            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
@@ -6729,25 +5703,21 @@
                             }
                             // Get Info and continue polling
                             fetchRssiLinkSpeedAndFrequencyNative();
-                            mWifiScoreReport =
-                                    WifiScoreReport.calculateScore(mWifiInfo,
-                                                                   getCurrentWifiConfiguration(),
-                                                                   mWifiConfigManager,
-                                                                   mNetworkAgent,
-                                                                   mWifiScoreReport,
-                                                                   mAggressiveHandover,
-                                                                   mWifiMetrics);
+                            // Send the update score to network agent.
+                            mWifiScoreReport.calculateAndReportScore(
+                                    mWifiInfo, mNetworkAgent, mAggressiveHandover,
+                                    mWifiMetrics);
                         }
                         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
                     }
                     break;
                 case CMD_ENABLE_RSSI_POLL:
                     cleanWifiScore();
-                    if (mWifiConfigManager.mEnableRssiPollWhenAssociated.get()) {
+                    if (mEnableRssiPollWhenAssociated) {
                         mEnableRssiPolling = (message.arg1 == 1);
                     } else {
                         mEnableRssiPolling = false;
@@ -6764,11 +5734,18 @@
                     RssiPacketCountInfo info = new RssiPacketCountInfo();
                     fetchRssiLinkSpeedAndFrequencyNative();
                     info.rssi = mWifiInfo.getRssi();
-                    fetchPktcntNative(info);
-                    replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED, info);
+                    WifiNative.TxPacketCounters counters = mWifiNative.getTxPacketCounters();
+                    if (counters != null) {
+                        info.txgood = counters.txSucceeded;
+                        info.txbad = counters.txFailed;
+                        replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED, info);
+                    } else {
+                        replyToMessage(message,
+                                WifiManager.RSSI_PKTCNT_FETCH_FAILED, WifiManager.ERROR);
+                    }
                     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 "
@@ -6778,7 +5755,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
@@ -6811,8 +5788,10 @@
                     if (message.arg1 == 0 // sim was removed
                             && mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
                         WifiConfiguration config =
-                                mWifiConfigManager.getWifiConfiguration(mLastNetworkId);
+                                mWifiConfigManager.getConfiguredNetwork(mLastNetworkId);
                         if (TelephonyUtil.isSimConfig(config)) {
+                            mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                    StaEvent.DISCONNECT_RESET_SIM_NETWORKS);
                             mWifiNative.disconnect();
                             transitionTo(mDisconnectingState);
                         }
@@ -6830,21 +5809,23 @@
     class ObtainingIpState extends State {
         @Override
         public void enter() {
-            if (DBG) {
+            WifiConfiguration currentConfig = getCurrentWifiConfiguration();
+            boolean isUsingStaticIp =
+                    (currentConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC);
+            if (mVerboseLoggingEnabled) {
                 String key = "";
                 if (getCurrentWifiConfiguration() != null) {
                     key = getCurrentWifiConfiguration().configKey();
                 }
                 log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId)
                         + " " + key + " "
-                        + " roam=" + mAutoRoaming
-                        + " static=" + mWifiConfigManager.isUsingStaticIp(mLastNetworkId)
-                        + " watchdog= " + obtainingIpWatchdogCount);
+                        + " roam=" + mIsAutoRoaming
+                        + " static=" + isUsingStaticIp);
             }
 
             // 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);
@@ -6852,7 +5833,7 @@
             // We must clear the config BSSID, as the wifi chipset may decide to roam
             // from this point on and having the BSSID specified in the network block would
             // cause the roam to fail and the device to disconnect.
-            clearCurrentConfigBSSID("ObtainingIpAddress");
+            clearTargetBssid("ObtainingIpAddress");
 
             // Stop IpManager in case we're switching from DHCP to static
             // configuration or vice versa.
@@ -6866,39 +5847,26 @@
             // CONNECTED.
             stopIpManager();
 
-            mIpManager.setHttpProxy(mWifiConfigManager.getProxyProperties(mLastNetworkId));
+            mIpManager.setHttpProxy(currentConfig.getHttpProxy());
             if (!TextUtils.isEmpty(mTcpBufferSizes)) {
                 mIpManager.setTcpBufferSizes(mTcpBufferSizes);
             }
-
-            if (!mWifiConfigManager.isUsingStaticIp(mLastNetworkId)) {
-                final IpManager.ProvisioningConfiguration prov =
-                        mIpManager.buildProvisioningConfiguration()
+            final IpManager.ProvisioningConfiguration prov;
+            if (!isUsingStaticIp) {
+                prov = 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);
             } else {
-                StaticIpConfiguration config = mWifiConfigManager.getStaticIpConfiguration(
-                        mLastNetworkId);
-                if (config.ipAddress == null) {
-                    logd("Static IP lacks address");
-                    sendMessage(CMD_IPV4_PROVISIONING_FAILURE);
-                } else {
-                    final IpManager.ProvisioningConfiguration prov =
-                            mIpManager.buildProvisioningConfiguration()
-                                .withStaticConfiguration(config)
-                                .withApfCapabilities(mWifiNative.getApfCapabilities())
-                                .build();
-                    mIpManager.startProvisioning(prov);
-                }
+                StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration();
+                prov = IpManager.buildProvisioningConfiguration()
+                            .withStaticConfiguration(staticIpConfig)
+                            .withApfCapabilities(mWifiNative.getApfCapabilities())
+                            .build();
             }
+            mIpManager.startProvisioning(prov);
+            // Get Link layer stats so as we get fresh tx packet counters
+            getWifiLinkLayerStats();
         }
 
         @Override
@@ -6906,17 +5874,14 @@
             logStateAndMessage(message, this);
 
             switch(message.what) {
-                case CMD_AUTO_CONNECT:
-                case CMD_AUTO_ROAM:
+                case CMD_START_CONNECT:
+                case CMD_START_ROAM:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
                 case WifiManager.SAVE_NETWORK:
-                case WifiStateMachine.CMD_AUTO_SAVE_NETWORK:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
                     deferMessage(message);
                     break;
-                    /* Defer any power mode changes since we must keep active power mode at DHCP */
-
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
@@ -6931,16 +5896,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;
             }
@@ -6952,17 +5907,19 @@
         // If this network was explicitly selected by the user, evaluate whether to call
         // explicitlySelected() so the system can treat it appropriately.
         WifiConfiguration config = getCurrentWifiConfiguration();
-        if (mWifiConfigManager.isLastSelectedConfiguration(config)) {
+        if (config == null) {
+            Log.wtf(TAG, "Current WifiConfiguration is null, but IP provisioning just succeeded");
+        } else if (mWifiConfigManager.getLastSelectedNetwork() == config.networkId) {
             boolean prompt =
-                    mWifiConfigManager.checkConfigOverridePermission(config.lastConnectUid);
-            if (DBG) {
+                    mWifiPermissionsUtil.checkConfigOverridePermission(config.lastConnectUid);
+            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);
@@ -6970,7 +5927,7 @@
         }
 
         setNetworkDetailedState(DetailedState.CONNECTED);
-        mWifiConfigManager.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
+        mWifiConfigManager.updateNetworkAfterConnect(mLastNetworkId);
         sendNetworkStateChangeBroadcast(mLastBssid);
     }
 
@@ -6978,7 +5935,7 @@
         boolean mAssociated;
         @Override
         public void enter() {
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 log("RoamingState Enter"
                         + " mScreenOn=" + mScreenOn );
             }
@@ -6998,13 +5955,14 @@
                 case CMD_IP_CONFIGURATION_LOST:
                     config = getCurrentWifiConfiguration();
                     if (config != null) {
-                        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_AUTOROAM_FAILURE);
-                        mWifiConfigManager.noteRoamingFailure(config,
-                                WifiConfiguration.ROAMING_FAILURE_IP_CONFIG);
+                        mWifiDiagnostics.captureBugReportData(
+                                WifiDiagnostics.REPORT_REASON_AUTOROAM_FAILURE);
                     }
                     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) {
@@ -7023,7 +5981,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() );
                         }
@@ -7037,32 +5995,35 @@
                         // 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);
                         mRoamFailCount++;
                         handleNetworkDisconnect();
+                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                StaEvent.DISCONNECT_ROAM_WATCHDOG_TIMER);
                         mWifiNative.disconnect();
                         transitionTo(mDisconnectedState);
                     }
                     break;
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
                     if (mAssociated) {
-                        if (DBG) log("roaming and Network connection established");
-                        mLastNetworkId = message.arg1;
+                        if (mVerboseLoggingEnabled) {
+                            log("roaming and Network connection established");
+                        }
+                        mLastNetworkId = lookupFrameworkNetworkId(message.arg1);
                         mLastBssid = (String) message.obj;
                         mWifiInfo.setBSSID(mLastBssid);
                         mWifiInfo.setNetworkId(mLastNetworkId);
-                        if (mWifiConnectivityManager != null) {
-                            mWifiConnectivityManager.trackBssid(mLastBssid, true);
-                        }
+                        int reasonCode = message.arg2;
+                        mWifiConnectivityManager.trackBssid(mLastBssid, true, reasonCode);
                         sendNetworkStateChangeBroadcast(mLastBssid);
 
                         // Successful framework roam! (probably)
@@ -7076,7 +6037,7 @@
                         // When transition from RoamingState to DisconnectingState or
                         // DisconnectedState, the config BSSID is cleared by
                         // handleNetworkDisconnect().
-                        clearCurrentConfigBSSID("RoamingCompleted");
+                        clearTargetBssid("RoamingCompleted");
 
                         // We used to transition to ObtainingIpState in an
                         // attempt to do DHCPv4 RENEWs on framework roams.
@@ -7106,24 +6067,6 @@
                         transitionTo(mDisconnectedState);
                     }
                     break;
-                case WifiMonitor.SSID_TEMP_DISABLED:
-                    // Auth error while roaming
-                    logd("SSID_TEMP_DISABLED nid=" + Integer.toString(mLastNetworkId)
-                            + " id=" + Integer.toString(message.arg1)
-                            + " isRoaming=" + isRoaming()
-                            + " roam=" + mAutoRoaming);
-                    if (message.arg1 == mLastNetworkId) {
-                        config = getCurrentWifiConfiguration();
-                        if (config != null) {
-                            mWifiLogger.captureBugReportData(
-                                    WifiLogger.REPORT_REASON_AUTOROAM_FAILURE);
-                            mWifiConfigManager.noteRoamingFailure(config,
-                                    WifiConfiguration.ROAMING_FAILURE_AUTH_FAILURE);
-                        }
-                        handleNetworkDisconnect();
-                        transitionTo(mDisconnectingState);
-                    }
-                    return NOT_HANDLED;
                 case CMD_START_SCAN:
                     deferMessage(message);
                     break;
@@ -7142,25 +6085,22 @@
     class ConnectedState extends State {
         @Override
         public void enter() {
-            String address;
             updateDefaultRouteMacAddress(1000);
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 log("Enter ConnectedState "
                        + " mScreenOn=" + mScreenOn);
             }
 
-            if (mWifiConnectivityManager != null) {
-                mWifiConnectivityManager.handleConnectionStateChanged(
-                        WifiConnectivityManager.WIFI_STATE_CONNECTED);
-            }
+            mWifiConnectivityManager.handleConnectionStateChanged(
+                    WifiConnectivityManager.WIFI_STATE_CONNECTED);
             registerConnected();
             lastConnectAttemptTimestamp = 0;
             targetWificonfiguration = null;
             // Paranoia
-            linkDebouncing = false;
+            mIsLinkDebouncing = false;
 
             // Not roaming anymore
-            mAutoRoaming = false;
+            mIsAutoRoaming = false;
 
             if (testNetworkDisconnect) {
                 testNetworkDisconnectCounter++;
@@ -7170,12 +6110,10 @@
                         testNetworkDisconnectCounter, 0), 15000);
             }
 
-            // Reenable all networks, allow for hidden networks to be scanned
-            mWifiConfigManager.enableAllNetworks();
-
             mLastDriverRoamAttempt = 0;
             mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
-            mWifiLastResortWatchdog.connectedStateTransition(true);
+            mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(true);
+            mWifiStateTracker.updateState(WifiStateTracker.CONNECTED);
         }
         @Override
         public boolean processMessage(Message message) {
@@ -7183,11 +6121,10 @@
             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) {
+                        mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                                StaEvent.DISCONNECT_UNWANTED);
                         mWifiNative.disconnect();
                         transitionTo(mDisconnectingState);
                     } else if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN ||
@@ -7199,19 +6136,14 @@
                         if (config != null) {
                             // Disable autojoin
                             if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN) {
-                                config.validatedInternetAccess = false;
-                                // Clear last-selected status, as being last-selected also avoids
-                                // disabling auto-join.
-                                if (mWifiConfigManager.isLastSelectedConfiguration(config)) {
-                                    mWifiConfigManager.setAndEnableLastSelectedConfiguration(
-                                        WifiConfiguration.INVALID_NETWORK_ID);
-                                }
-                                mWifiConfigManager.updateNetworkSelectionStatus(config,
+                                mWifiConfigManager.setNetworkValidatedInternetAccess(
+                                        config.networkId, false);
+                                mWifiConfigManager.updateNetworkSelectionStatus(config.networkId,
                                         WifiConfiguration.NetworkSelectionStatus
                                         .DISABLED_NO_INTERNET);
                             }
-                            config.numNoInternetAccessReports += 1;
-                            mWifiConfigManager.writeKnownNetworkHistory();
+                            mWifiConfigManager.incrementNetworkNoInternetAccessReports(
+                                    config.networkId);
                         }
                     }
                     return HANDLED;
@@ -7220,19 +6152,14 @@
                         config = getCurrentWifiConfiguration();
                         if (config != null) {
                             // re-enable autojoin
-                            config.numNoInternetAccessReports = 0;
-                            config.validatedInternetAccess = true;
-                            mWifiConfigManager.writeKnownNetworkHistory();
+                            mWifiConfigManager.setNetworkValidatedInternetAccess(
+                                    config.networkId, true);
                         }
                     }
                     return HANDLED;
                 case CMD_ACCEPT_UNVALIDATED:
                     boolean accept = (message.arg1 != 0);
-                    config = getCurrentWifiConfiguration();
-                    if (config != null) {
-                        config.noInternetAccessExpected = accept;
-                        mWifiConfigManager.writeKnownNetworkHistory();
-                    }
+                    mWifiConfigManager.setNetworkNoInternetAccessExpected(mLastNetworkId, accept);
                     return HANDLED;
                 case CMD_TEST_NETWORK_DISCONNECT:
                     // Force a disconnect
@@ -7243,7 +6170,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;
@@ -7252,27 +6179,28 @@
                             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)) {
-                        mWifiLogger.captureBugReportData(
-                                WifiLogger.REPORT_REASON_UNEXPECTED_DISCONNECT);
+                        mWifiDiagnostics.captureBugReportData(
+                                WifiDiagnostics.REPORT_REASON_UNEXPECTED_DISCONNECT);
                     }
                     config = getCurrentWifiConfiguration();
-                    if (mScreenOn
-                            && !linkDebouncing
+                    if (mEnableLinkDebouncing
+                            && mScreenOn
+                            && !isLinkDebouncing()
                             && config != null
                             && config.getNetworkSelectionStatus().isNetworkEnabled()
-                            && !mWifiConfigManager.isLastSelectedConfiguration(config)
+                            && config.networkId != mWifiConfigManager.getLastSelectedNetwork()
                             && (message.arg2 != 3 /* reason cannot be 3, i.e. locally generated */
                                 || (lastRoam > 0 && lastRoam < 2000) /* unless driver is roaming */)
                             && ((ScanResult.is24GHz(mWifiInfo.getFrequency())
                                     && mWifiInfo.getRssi() >
-                                    WifiQualifiedNetworkSelector.QUALIFIED_RSSI_24G_BAND)
+                                     mThresholdQualifiedRssi5)
                                     || (ScanResult.is5GHz(mWifiInfo.getFrequency())
                                     && mWifiInfo.getRssi() >
-                                    mWifiConfigManager.mThresholdQualifiedRssi5.get()))) {
+                                    mThresholdQualifiedRssi5))) {
                         // Start de-bouncing the L2 disconnection:
                         // this L2 disconnection might be spurious.
                         // Hence we allow 4 seconds for the state machine to try
@@ -7280,11 +6208,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()
@@ -7294,137 +6222,85 @@
                         }
                         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()));
                         }
                     }
                     break;
-                case CMD_AUTO_ROAM:
+                case CMD_START_ROAM:
                     // Clear the driver roam indication since we are attempting a framework roam
                     mLastDriverRoamAttempt = 0;
 
-                    /*<TODO> 2016-02-24
-                        Fix CMD_AUTO_ROAM to use the candidate (message.arg1) networkID, rather than
-                        the old networkID.
-                        The current code only handles roaming between BSSIDs on the same networkID,
-                        and will break for roams between different (but linked) networkIDs. This
-                        case occurs for DBDC roaming, and the CMD_AUTO_ROAM sent due to it will
-                        fail.
-                    */
                     /* Connect command coming from auto-join */
+                    int netId = message.arg1;
                     ScanResult candidate = (ScanResult)message.obj;
-                    String bssid = "any";
+                    String bssid = SUPPLICANT_BSSID_ANY;
                     if (candidate != null) {
                         bssid = candidate.BSSID;
                     }
-                    int netId = message.arg1;
-                    if (netId == WifiConfiguration.INVALID_NETWORK_ID) {
-                        loge("AUTO_ROAM and no config, bail out...");
+                    config = mWifiConfigManager.getConfiguredNetworkWithPassword(netId);
+                    if (config == null) {
+                        loge("CMD_START_ROAM and no config, bail out...");
                         break;
-                    } else {
-                        config = mWifiConfigManager.getWifiConfiguration(netId);
-                        if (config == null) {
-                          loge("AUTO_ROAM and invalid netowrk ID, bail out...");
-                          break;
-                        }
                     }
 
                     setTargetBssid(config, bssid);
                     mTargetNetworkId = netId;
 
-                    logd("CMD_AUTO_ROAM sup state "
+                    logd("CMD_START_ROAM sup state "
                             + mSupplicantStateTracker.getSupplicantStateName()
                             + " my state " + getCurrentState().getName()
                             + " nid=" + Integer.toString(netId)
                             + " config " + config.configKey()
                             + " targetRoamBSSID " + mTargetRoamBSSID);
 
-                    /* Determine if this is a regular roam (between BSSIDs sharing the same SSID),
-                       or a DBDC roam (between 2.4 & 5GHz networks on different SSID's, but with
-                       matching 16 byte BSSID prefixes):
-                     */
-                    WifiConfiguration currentConfig = getCurrentWifiConfiguration();
-                    if (currentConfig != null && currentConfig.isLinked(config)) {
-                        // This is dual band roaming
-                        mWifiMetrics.startConnectionEvent(config, mTargetRoamBSSID,
-                                WifiMetricsProto.ConnectionEvent.ROAM_DBDC);
-                    } else {
-                        // This is regular roaming
-                        mWifiMetrics.startConnectionEvent(config, mTargetRoamBSSID,
-                                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
-                    }
-
-                    if (deferForUserInput(message, netId, false)) {
-                        reportConnectionAttemptEnd(
-                                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
-                                WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                        break;
-                    } else if (mWifiConfigManager.getWifiConfiguration(netId).userApproved ==
-                            WifiConfiguration.USER_BANNED) {
-                        replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
-                                WifiManager.NOT_AUTHORIZED);
-                        reportConnectionAttemptEnd(
-                                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
-                                WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                        break;
-                    }
-
-                    boolean ret = false;
-                    if (mLastNetworkId != netId) {
-                        if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ false,
-                                WifiConfiguration.UNKNOWN_UID) && mWifiNative.reconnect()) {
-                            ret = true;
-                        }
-                    } else {
-                        ret = mWifiNative.reassociate();
-                    }
-                    if (ret) {
-                        lastConnectAttemptTimestamp = System.currentTimeMillis();
-                        targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
-
-                        // replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
-                        mAutoRoaming = true;
+                    reportConnectionAttemptStart(config, mTargetRoamBSSID,
+                            WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+                    if (mWifiNative.roamToNetwork(config)) {
+                        lastConnectAttemptTimestamp = mClock.getWallClockMillis();
+                        targetWificonfiguration = config;
+                        mIsAutoRoaming = true;
+                        mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_ROAM, config);
                         transitionTo(mRoamingState);
-
                     } else {
-                        loge("Failed to connect config: " + config + " netId: " + netId);
+                        loge("CMD_START_ROAM Failed to start roaming to network " + config);
+                        reportConnectionAttemptEnd(
+                                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
+                                WifiMetricsProto.ConnectionEvent.HLF_NONE);
                         replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                 WifiManager.ERROR);
                         messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        reportConnectionAttemptEnd(
-                                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
-                                WifiMetricsProto.ConnectionEvent.HLF_NONE);
                         break;
                     }
                     break;
                 case CMD_START_IP_PACKET_OFFLOAD: {
-                        int slot = message.arg1;
-                        int intervalSeconds = message.arg2;
-                        KeepalivePacketData pkt = (KeepalivePacketData) message.obj;
-                        byte[] dstMac;
-                        try {
-                            InetAddress gateway = RouteInfo.selectBestRoute(
-                                    mLinkProperties.getRoutes(), pkt.dstAddress).getGateway();
-                            String dstMacStr = macAddressFromRoute(gateway.getHostAddress());
-                            dstMac = macAddressFromString(dstMacStr);
-                        } catch (NullPointerException|IllegalArgumentException e) {
-                            loge("Can't find MAC address for next hop to " + pkt.dstAddress);
-                            mNetworkAgent.onPacketKeepaliveEvent(slot,
-                                    ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
-                            break;
-                        }
-                        pkt.dstMac = dstMac;
-                        int result = startWifiIPPacketOffload(slot, pkt, intervalSeconds);
-                        mNetworkAgent.onPacketKeepaliveEvent(slot, result);
+                    int slot = message.arg1;
+                    int intervalSeconds = message.arg2;
+                    KeepalivePacketData pkt = (KeepalivePacketData) message.obj;
+                    byte[] dstMac;
+                    try {
+                        InetAddress gateway = RouteInfo.selectBestRoute(
+                                mLinkProperties.getRoutes(), pkt.dstAddress).getGateway();
+                        String dstMacStr = macAddressFromRoute(gateway.getHostAddress());
+                        dstMac = NativeUtil.macAddressToByteArray(dstMacStr);
+                    } catch (NullPointerException | IllegalArgumentException e) {
+                        loge("Can't find MAC address for next hop to " + pkt.dstAddress);
+                        mNetworkAgent.onPacketKeepaliveEvent(slot,
+                                ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
                         break;
                     }
+                    pkt.dstMac = dstMac;
+                    int result = startWifiIPPacketOffload(slot, pkt, intervalSeconds);
+                    mNetworkAgent.onPacketKeepaliveEvent(slot, result);
+                    break;
+                }
                 default:
                     return NOT_HANDLED;
             }
@@ -7434,14 +6310,11 @@
         @Override
         public void exit() {
             logd("WifiStateMachine: Leaving Connected state");
-            if (mWifiConnectivityManager != null) {
-                mWifiConnectivityManager.handleConnectionStateChanged(
-                         WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
-            }
+            mWifiConnectivityManager.handleConnectionStateChanged(
+                     WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
 
             mLastDriverRoamAttempt = 0;
-            mWhiteListedSsids = null;
-            mWifiLastResortWatchdog.connectedStateTransition(false);
+            mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(false);
         }
     }
 
@@ -7450,7 +6323,7 @@
         @Override
         public void enter() {
 
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 logd(" Enter DisconnectingState State screenOn=" + mScreenOn);
             }
 
@@ -7479,11 +6352,11 @@
                     deferMessage(message);
                     return HANDLED;
                 case CMD_DISCONNECT:
-                    if (DBG) log("Ignore CMD_DISCONNECT when already disconnecting.");
+                    if (mVerboseLoggingEnabled) log("Ignore CMD_DISCONNECT when already disconnecting.");
                     break;
                 case CMD_DISCONNECTING_WATCHDOG_TIMER:
                     if (disconnectingWatchdogCount == message.arg1) {
-                        if (DBG) log("disconnecting watchdog! -> disconnect");
+                        if (mVerboseLoggingEnabled) log("disconnecting watchdog! -> disconnect");
                         handleNetworkDisconnect();
                         transitionTo(mDisconnectedState);
                     }
@@ -7508,24 +6381,23 @@
     class DisconnectedState extends State {
         @Override
         public void enter() {
+            Log.i(TAG, "disconnectedstate enter");
             // 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);
             }
 
             /** clear the roaming state, if we were roaming, we failed */
-            mAutoRoaming = false;
+            mIsAutoRoaming = false;
 
-            if (mWifiConnectivityManager != null) {
-                mWifiConnectivityManager.handleConnectionStateChanged(
-                        WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
-            }
+            mWifiConnectivityManager.handleConnectionStateChanged(
+                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
             /**
              * If we have no networks saved, the supplicant stops doing the periodic scan.
@@ -7538,7 +6410,8 @@
                         ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
             }
 
-            mDisconnectedTimeStamp = System.currentTimeMillis();
+            mDisconnectedTimeStamp = mClock.getWallClockMillis();
+            mWifiStateTracker.updateState(WifiStateTracker.DISCONNECTED);
         }
         @Override
         public boolean processMessage(Message message) {
@@ -7570,28 +6443,20 @@
                 case CMD_SET_OPERATIONAL_MODE:
                     if (message.arg1 != CONNECT_MODE) {
                         mOperationalMode = message.arg1;
-                        mWifiConfigManager.disableAllNetworksNative();
-                        if (mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
-                            mWifiP2pChannel.sendMessage(CMD_DISABLE_P2P_REQ);
+                        if (mOperationalMode == DISABLED_MODE) {
+                            transitionTo(mSupplicantStoppingState);
+                        } else if (mOperationalMode == SCAN_ONLY_MODE
+                                || mOperationalMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+                            p2pSendMessage(CMD_DISABLE_P2P_REQ);
                             setWifiState(WIFI_STATE_DISABLED);
+                            transitionTo(mScanModeState);
                         }
-                        transitionTo(mScanModeState);
                     }
-                    mWifiConfigManager.
-                            setAndEnableLastSelectedConfiguration(
-                                    WifiConfiguration.INVALID_NETWORK_ID);
                     break;
                 case CMD_DISCONNECT:
-                    if (SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
-                        if (DBG) {
-                            log("CMD_DISCONNECT when supplicant is connecting - do not ignore");
-                        }
-                        mWifiConfigManager.setAndEnableLastSelectedConfiguration(
-                                WifiConfiguration.INVALID_NETWORK_ID);
-                        mWifiNative.disconnect();
-                        break;
-                    }
-                    if (DBG) log("Ignore CMD_DISCONNECT when already disconnected.");
+                    mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
+                            StaEvent.DISCONNECT_UNKNOWN);
+                    mWifiNative.disconnect();
                     break;
                 /* Ignore network disconnect */
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
@@ -7599,10 +6464,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 */
@@ -7620,15 +6485,8 @@
                 case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
                     NetworkInfo info = (NetworkInfo) message.obj;
                     mP2pConnected.set(info.isConnected());
-                    if (mP2pConnected.get()) {
-                        int defaultInterval = mContext.getResources().getInteger(
-                                R.integer.config_wifi_scan_interval_p2p_connected);
-                        long scanIntervalMs = mFacade.getLongSetting(mContext,
-                                Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
-                                defaultInterval);
-                        mWifiNative.setScanInterval((int) scanIntervalMs/1000);
-                    } else if (mWifiConfigManager.getSavedNetworks().size() == 0) {
-                        if (DBG) log("Turn on scanning after p2p disconnected");
+                    if (!mP2pConnected.get() && mWifiConfigManager.getSavedNetworks().size() == 0) {
+                        if (mVerboseLoggingEnabled) log("Turn on scanning after p2p disconnected");
                         sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
                                     ++mPeriodicScanToken, 0), mNoNetworksPeriodicScan);
                     }
@@ -7655,13 +6513,28 @@
 
         @Override
         public void exit() {
-            if (mWifiConnectivityManager != null) {
-                mWifiConnectivityManager.handleConnectionStateChanged(
-                         WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
-            }
+            mWifiConnectivityManager.handleConnectionStateChanged(
+                     WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
         }
     }
 
+    /**
+     * WPS connection flow:
+     * 1. WifiStateMachine receive WPS_START message from WifiManager API.
+     * 2. WifiStateMachine initiates the appropriate WPS operation using WifiNative methods:
+     * {@link WifiNative#startWpsPbc(String)}, {@link WifiNative#startWpsPinDisplay(String)}, etc.
+     * 3. WifiStateMachine then transitions to this WpsRunningState.
+     * 4a. Once WifiStateMachine receive the connected event:
+     * {@link WifiMonitor#NETWORK_CONNECTION_EVENT},
+     * 4a.1 Load the network params out of wpa_supplicant.
+     * 4a.2 Add the network with params to WifiConfigManager.
+     * 4a.3 Enable the network with |disableOthers| set to true.
+     * 4a.4 Send a response to the original source of WifiManager API using {@link #mSourceMessage}.
+     * 4b. Any failures are notified to the original source of WifiManager API
+     * using {@link #mSourceMessage}.
+     * 5. We then transition to disconnected state and let network selection reconnect to the newly
+     * added network.
+     */
     class WpsRunningState extends State {
         // Tracks the source to provide a reply
         private Message mSourceMessage;
@@ -7678,7 +6551,12 @@
                     // Ignore intermediate success, wait for full connection
                     break;
                 case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                    replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
+                    if (loadNetworksFromSupplicantAfterWps()) {
+                        replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
+                    } else {
+                        replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
+                                WifiManager.ERROR);
+                    }
                     mSourceMessage.recycle();
                     mSourceMessage = null;
                     deferMessage(message);
@@ -7699,7 +6577,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:
@@ -7724,35 +6604,35 @@
                  * Defer all commands that can cause connections to a different network
                  * or put the state machine out of connect mode
                  */
-                case CMD_STOP_DRIVER:
                 case CMD_SET_OPERATIONAL_MODE:
                 case WifiManager.CONNECT_NETWORK:
                 case CMD_ENABLE_NETWORK:
                 case CMD_RECONNECT:
                 case CMD_REASSOCIATE:
-                case CMD_ENABLE_ALL_NETWORKS:
                     deferMessage(message);
                     break;
-                case CMD_AUTO_CONNECT:
-                case CMD_AUTO_ROAM:
+                case CMD_START_CONNECT:
+                case CMD_START_ROAM:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     return HANDLED;
                 case CMD_START_SCAN:
                     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.
@@ -7765,15 +6645,41 @@
             return HANDLED;
         }
 
-        @Override
-        public void exit() {
-            mWifiConfigManager.enableAllNetworks();
-            mWifiConfigManager.loadConfiguredNetworks();
+        /**
+         * Load network config from wpa_supplicant after WPS is complete.
+         */
+        private boolean loadNetworksFromSupplicantAfterWps() {
+            Map<String, WifiConfiguration> configs = new HashMap<>();
+            SparseArray<Map<String, String>> extras = new SparseArray<>();
+            if (!mWifiNative.migrateNetworksFromSupplicant(configs, extras)) {
+                loge("Failed to load networks from wpa_supplicant after Wps");
+                return false;
+            }
+            for (Map.Entry<String, WifiConfiguration> entry : configs.entrySet()) {
+                WifiConfiguration config = entry.getValue();
+                // Reset the network ID retrieved from wpa_supplicant, since we want to treat
+                // this as a new network addition in framework.
+                config.networkId = WifiConfiguration.INVALID_NETWORK_ID;
+                NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
+                        config, mSourceMessage.sendingUid);
+                if (!result.isSuccess()) {
+                    loge("Failed to add network after WPS: " + entry.getValue());
+                    return false;
+                }
+                if (!mWifiConfigManager.enableNetwork(
+                        result.getNetworkId(), true, mSourceMessage.sendingUid)) {
+                    loge("Failed to enable network after WPS: " + entry.getValue());
+                    return false;
+                }
+            }
+            return true;
         }
     }
 
     class SoftApState extends State {
         private SoftApManager mSoftApManager;
+        private String mIfaceName;
+        private int mMode;
 
         private class SoftApListener implements SoftApManager.Listener {
             @Override
@@ -7784,42 +6690,53 @@
                     sendMessage(CMD_START_AP_FAILURE);
                 }
 
-                setWifiApState(state, reason);
+                setWifiApState(state, reason, mIfaceName, mMode);
             }
         }
 
         @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.getCountryCode(),
-                        mWifiApConfigStore.getAllowed2GChannel(),
-                        new SoftApListener());
-                mSoftApManager.start(config);
-            } else {
+            if (message.what != CMD_START_AP) {
                 throw new RuntimeException("Illegal transition to SoftApState: " + message);
             }
+            SoftApModeConfiguration config = (SoftApModeConfiguration) message.obj;
+            mMode = config.getTargetMode();
+
+            IApInterface apInterface = mWifiNative.setupForSoftApMode();
+            if (apInterface == null) {
+                setWifiApState(WIFI_AP_STATE_FAILED,
+                        WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
+                /**
+                 * Transition to InitialState to reset the
+                 * driver/HAL back to the initial state.
+                 */
+                transitionTo(mInitialState);
+                return;
+            }
+
+            try {
+                mIfaceName = apInterface.getInterfaceName();
+            } catch (RemoteException e) {
+                // Failed to get the interface name. The name will not be available for
+                // the enabled broadcast, but since we had an error getting the name, we most likely
+                // won't be able to fully start softap mode.
+            }
+
+            checkAndSetConnectivityInstance();
+            mSoftApManager = mWifiInjector.makeSoftApManager(mNwService,
+                                                             new SoftApListener(),
+                                                             apInterface,
+                                                             config.getWifiConfiguration());
+            mSoftApManager.start();
+            mWifiStateTracker.updateState(WifiStateTracker.SOFT_AP);
         }
 
         @Override
         public void exit() {
             mSoftApManager = null;
+            mIfaceName = null;
+            mMode = WifiManager.IFACE_IP_MODE_UNSPECIFIED;
         }
 
         @Override
@@ -7883,8 +6800,12 @@
     }
 
     /**
+     * Notify interested parties if a wifi config has been changed.
+     *
      * @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
+     * TODO: b/35258354 investigate if this can be removed.  Is the broadcast sent by
+     * WifiConfigManager sufficient?
      */
     private void broadcastWifiCredentialChanged(int wifiCredentialEventType,
             WifiConfiguration config) {
@@ -7898,294 +6819,41 @@
         }
     }
 
-    private static int parseHex(char ch) {
-        if ('0' <= ch && ch <= '9') {
-            return ch - '0';
-        } else if ('a' <= ch && ch <= 'f') {
-            return ch - 'a' + 10;
-        } else if ('A' <= ch && ch <= 'F') {
-            return ch - 'A' + 10;
-        } else {
-            throw new NumberFormatException("" + ch + " is not a valid hex digit");
-        }
-    }
-
-    private byte[] parseHex(String hex) {
-        /* This only works for good input; don't throw bad data at it */
-        if (hex == null) {
-            return new byte[0];
-        }
-
-        if (hex.length() % 2 != 0) {
-            throw new NumberFormatException(hex + " is not a valid hex string");
-        }
-
-        byte[] result = new byte[(hex.length())/2 + 1];
-        result[0] = (byte) ((hex.length())/2);
-        for (int i = 0, j = 1; i < hex.length(); i += 2, j++) {
-            int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i+1));
-            byte b = (byte) (val & 0xFF);
-            result[j] = b;
-        }
-
-        return result;
-    }
-
-    private static String makeHex(byte[] bytes) {
-        StringBuilder sb = new StringBuilder();
-        for (byte b : bytes) {
-            sb.append(String.format("%02x", b));
-        }
-        return sb.toString();
-    }
-
-    private static String makeHex(byte[] bytes, int from, int len) {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < len; i++) {
-            sb.append(String.format("%02x", bytes[from+i]));
-        }
-        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;
-
-        byte[] result = new byte[len];
-
-        int index = 0;
-        if (array1.length != 0) {
-            for (byte b : array1) {
-                result[index] = b;
-                index++;
-            }
-        }
-
-        if (array2.length != 0) {
-            for (byte b : array2) {
-                result[index] = b;
-                index++;
-            }
-        }
-
-        return result;
-    }
-
-    // TODO move to TelephonyUtil, same with utilities above
-    String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) {
-        StringBuilder sb = new StringBuilder();
-        for (String challenge : requestData) {
-            if (challenge == null || challenge.isEmpty()) {
-                continue;
-            }
-            logd("RAND = " + challenge);
-
-            byte[] rand = null;
-            try {
-                rand = parseHex(challenge);
-            } catch (NumberFormatException e) {
-                loge("malformed challenge");
-                continue;
-            }
-
-            String base64Challenge = android.util.Base64.encodeToString(
-                    rand, android.util.Base64.NO_WRAP);
-
-            // Try USIM first for authentication.
-            String tmResponse = tm.getIccAuthentication(tm.APPTYPE_USIM,
-                    tm.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);
-            }
-            logv("Raw Response - " + tmResponse);
-
-            if (tmResponse == null || tmResponse.length() <= 4) {
-                loge("bad response - " + tmResponse);
-                return null;
-            }
-
-            byte[] result = android.util.Base64.decode(tmResponse, android.util.Base64.DEFAULT);
-            logv("Hex Response -" + makeHex(result));
-            int sres_len = result[0];
-            if (sres_len >= result.length) {
-                loge("malfomed response - " + tmResponse);
-                return null;
-            }
-            String sres = makeHex(result, 1, sres_len);
-            int kc_offset = 1 + sres_len;
-            if (kc_offset >= result.length) {
-                loge("malfomed response - " + tmResponse);
-                return null;
-            }
-            int kc_len = result[kc_offset];
-            if (kc_offset + kc_len > result.length) {
-                loge("malfomed response - " + tmResponse);
-                return null;
-            }
-            String kc = makeHex(result, 1 + kc_offset, kc_len);
-            sb.append(":" + kc + ":" + sres);
-            logv("kc:" + kc + " sres:" + sres);
-        }
-
-        return sb.toString();
-    }
-
-    // TODO move to TelephonyUtil
     void handleGsmAuthRequest(SimAuthRequestData requestData) {
         if (targetWificonfiguration == null
-                || targetWificonfiguration.networkId == requestData.networkId) {
+                || targetWificonfiguration.networkId
+                == lookupFrameworkNetworkId(requestData.networkId)) {
             logd("id matches targetWifiConfiguration");
         } else {
             logd("id does not match targetWifiConfiguration");
             return;
         }
 
-        TelephonyManager tm = (TelephonyManager)
-                mContext.getSystemService(Context.TELEPHONY_SERVICE);
-
-        if (tm == null) {
-            loge("could not get telephony manager");
-            mWifiNative.simAuthFailedResponse(requestData.networkId);
-            return;
-        }
-
-        String response = getGsmSimAuthResponse(requestData.data, tm);
+        String response =
+                TelephonyUtil.getGsmSimAuthResponse(requestData.data, getTelephonyManager());
         if (response == null) {
             mWifiNative.simAuthFailedResponse(requestData.networkId);
         } else {
             logv("Supplicant Response -" + response);
-            mWifiNative.simAuthResponse(requestData.networkId, "GSM-AUTH", response);
+            mWifiNative.simAuthResponse(requestData.networkId,
+                    WifiNative.SIM_AUTH_RESP_TYPE_GSM_AUTH, response);
         }
     }
 
-    // TODO move to TelephonyUtil
     void handle3GAuthRequest(SimAuthRequestData requestData) {
-        StringBuilder sb = new StringBuilder();
-        byte[] rand = null;
-        byte[] authn = null;
-        String res_type = "UMTS-AUTH";
-
         if (targetWificonfiguration == null
-                || targetWificonfiguration.networkId == requestData.networkId) {
+                || targetWificonfiguration.networkId
+                == lookupFrameworkNetworkId(requestData.networkId)) {
             logd("id matches targetWifiConfiguration");
         } else {
             logd("id does not match targetWifiConfiguration");
             return;
         }
-        if (requestData.data.length == 2) {
-            try {
-                rand = parseHex(requestData.data[0]);
-                authn = parseHex(requestData.data[1]);
-            } catch (NumberFormatException e) {
-                loge("malformed challenge");
-            }
-        } else {
-               loge("malformed challenge");
-        }
 
-        String tmResponse = "";
-        if (rand != null && authn != null) {
-            String base64Challenge = android.util.Base64.encodeToString(
-                    concatHex(rand,authn), android.util.Base64.NO_WRAP);
-
-            TelephonyManager tm = (TelephonyManager)
-                    mContext.getSystemService(Context.TELEPHONY_SERVICE);
-            if (tm != null) {
-                tmResponse = tm.getIccAuthentication(tm.APPTYPE_USIM,
-                        tm.AUTHTYPE_EAP_AKA, base64Challenge);
-                logv("Raw Response - " + tmResponse);
-            } else {
-                loge("could not get telephony manager");
-            }
-        }
-
-        boolean good_response = false;
-        if (tmResponse != null && tmResponse.length() > 4) {
-            byte[] result = android.util.Base64.decode(tmResponse,
-                    android.util.Base64.DEFAULT);
-            loge("Hex Response - " + makeHex(result));
-            byte tag = result[0];
-            if (tag == (byte) 0xdb) {
-                logv("successful 3G authentication ");
-                int res_len = result[1];
-                String res = makeHex(result, 2, res_len);
-                int ck_len = result[res_len + 2];
-                String ck = makeHex(result, res_len + 3, ck_len);
-                int ik_len = result[res_len + ck_len + 3];
-                String ik = makeHex(result, res_len + ck_len + 4, ik_len);
-                sb.append(":" + ik + ":" + ck + ":" + res);
-                logv("ik:" + ik + "ck:" + ck + " res:" + res);
-                good_response = true;
-            } else if (tag == (byte) 0xdc) {
-                loge("synchronisation failure");
-                int auts_len = result[1];
-                String auts = makeHex(result, 2, auts_len);
-                res_type = "UMTS-AUTS";
-                sb.append(":" + auts);
-                logv("auts:" + auts);
-                good_response = true;
-            } else {
-                loge("bad response - unknown tag = " + tag);
-            }
-        } else {
-            loge("bad response - " + tmResponse);
-        }
-
-        if (good_response) {
-            String response = sb.toString();
-            logv("Supplicant Response -" + response);
-            mWifiNative.simAuthResponse(requestData.networkId, res_type, response);
+        SimAuthResponseData response =
+                TelephonyUtil.get3GAuthResponse(requestData, getTelephonyManager());
+        if (response != null) {
+            mWifiNative.simAuthResponse(requestData.networkId, response.type, response.response);
         } else {
             mWifiNative.umtsAuthFailedResponse(requestData.networkId);
         }
@@ -8197,10 +6865,10 @@
      * @param networkId ID of the network to connect to
      * @param bssid BSSID of the network
      */
-    public void autoConnectToNetwork(int networkId, String bssid) {
+    public void startConnectToNetwork(int networkId, String bssid) {
         synchronized (mWifiReqCountLock) {
             if (hasConnectionRequests()) {
-                sendMessage(CMD_AUTO_CONNECT, networkId, 0, bssid);
+                sendMessage(CMD_START_CONNECT, networkId, 0, bssid);
             }
         }
     }
@@ -8211,8 +6879,8 @@
      * @param networkId ID of the network to roam to
      * @param scanResult scan result which identifies the network to roam to
      */
-    public void autoRoamToNetwork(int networkId, ScanResult scanResult) {
-        sendMessage(CMD_AUTO_ROAM, networkId, 0, scanResult);
+    public void startRoamToNetwork(int networkId, ScanResult scanResult) {
+        sendMessage(CMD_START_ROAM, networkId, 0, scanResult);
     }
 
     /**
@@ -8246,47 +6914,48 @@
     /**
      * Update WifiMetrics before dumping
      */
-    void updateWifiMetrics() {
-        int numSavedNetworks = mWifiConfigManager.getConfiguredNetworksSize();
-        int numOpenNetworks = 0;
-        int numPersonalNetworks = 0;
-        int numEnterpriseNetworks = 0;
-        int numNetworksAddedByUser = 0;
-        int numNetworksAddedByApps = 0;
-        int numHiddenNetworks = 0;
-        int numPasspoint = 0;
-        for (WifiConfiguration config : mWifiConfigManager.getSavedNetworks()) {
-            if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
-                numOpenNetworks++;
-            } else if (config.isEnterprise()) {
-                numEnterpriseNetworks++;
-            } else {
-                numPersonalNetworks++;
-            }
-            if (config.selfAdded) {
-                numNetworksAddedByUser++;
-            } else {
-                numNetworksAddedByApps++;
-            }
-            if (config.hiddenSSID) {
-                numHiddenNetworks++;
-            }
-            if (config.isPasspoint()) {
-                numPasspoint++;
-            }
+    public void updateWifiMetrics() {
+        mWifiMetrics.updateSavedNetworks(mWifiConfigManager.getSavedNetworks());
+    }
+
+    /**
+     * Private method to handle calling WifiConfigManager to forget/remove network configs and reply
+     * to the message from the sender of the outcome.
+     *
+     * The current implementation requires that forget and remove be handled in different ways
+     * (responses are handled differently).  In the interests of organization, the handling is all
+     * now in this helper method.  TODO: b/35257965 is filed to track the possibility of merging
+     * the two call paths.
+     */
+    private boolean deleteNetworkConfigAndSendReply(Message message, boolean calledFromForget) {
+        boolean success = mWifiConfigManager.removeNetwork(message.arg1, message.sendingUid);
+        if (!success) {
+            loge("Failed to remove network");
         }
-        mWifiMetrics.setNumSavedNetworks(numSavedNetworks);
-        mWifiMetrics.setNumOpenNetworks(numOpenNetworks);
-        mWifiMetrics.setNumPersonalNetworks(numPersonalNetworks);
-        mWifiMetrics.setNumEnterpriseNetworks(numEnterpriseNetworks);
-        mWifiMetrics.setNumNetworksAddedByUser(numNetworksAddedByUser);
-        mWifiMetrics.setNumNetworksAddedByApps(numNetworksAddedByApps);
-        mWifiMetrics.setNumHiddenNetworks(numHiddenNetworks);
-        mWifiMetrics.setNumPasspointNetworks(numPasspoint);
+
+        if (calledFromForget) {
+            if (success) {
+                replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
+                broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_FORGOT,
+                                               (WifiConfiguration) message.obj);
+                return true;
+            }
+            replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED, WifiManager.ERROR);
+            return false;
+        } else {
+            // Remaining calls are from the removeNetwork path
+            if (success) {
+                replyToMessage(message, message.what, SUCCESS);
+                return true;
+            }
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, message.what, FAILURE);
+            return false;
+        }
     }
 
     private static String getLinkPropertiesSummary(LinkProperties lp) {
-        List<String> attributes = new ArrayList(6);
+        List<String> attributes = new ArrayList<>(6);
         if (lp.hasIPv4Address()) {
             attributes.add("v4");
         }
@@ -8309,36 +6978,12 @@
         return TextUtils.join(" ", attributes);
     }
 
-    private void wnmFrameReceived(WnmData event) {
-        // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url
-        // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url
-
-        Intent intent = new Intent(WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-
-        intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_BSSID, event.getBssid());
-        intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_URL, event.getUrl());
-
-        if (event.isDeauthEvent()) {
-            intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_ESS, event.isEss());
-            intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_DELAY, event.getDelay());
-        } else {
-            intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_METHOD, event.getMethod());
-            WifiConfiguration config = getCurrentWifiConfiguration();
-            if (config != null && config.FQDN != null) {
-                intent.putExtra(WifiManager.EXTRA_PASSPOINT_WNM_PPOINT_MATCH,
-                        mWifiConfigManager.matchProviderWithCurrentNetwork(config.FQDN));
-            }
-        }
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
     /**
      * Gets the SSID from the WifiConfiguration pointed at by 'mTargetNetworkId'
      * This should match the network config framework is attempting to connect to.
      */
     private String getTargetSsid() {
-        WifiConfiguration currentConfig = mWifiConfigManager.getWifiConfiguration(mTargetNetworkId);
+        WifiConfiguration currentConfig = mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
         if (currentConfig != null) {
             return currentConfig.SSID;
         }
@@ -8346,10 +6991,62 @@
     }
 
     /**
+     * Send message to WifiP2pServiceImpl.
+     * @return true if message is sent.
+     *         false if there is no channel configured for WifiP2pServiceImpl.
+     */
+    private boolean p2pSendMessage(int what) {
+        if (mWifiP2pChannel != null) {
+            mWifiP2pChannel.sendMessage(what);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Send message to WifiP2pServiceImpl with an additional param |arg1|.
+     * @return true if message is sent.
+     *         false if there is no channel configured for WifiP2pServiceImpl.
+     */
+    private boolean p2pSendMessage(int what, int arg1) {
+        if (mWifiP2pChannel != null) {
+            mWifiP2pChannel.sendMessage(what, arg1);
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Check if there is any connection request for WiFi network.
      * Note, caller of this helper function must acquire mWifiReqCountLock.
      */
     private boolean hasConnectionRequests() {
         return mConnectionReqCount > 0 || mUntrustedReqCount > 0;
     }
+
+    /**
+     * Returns whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.
+     */
+    public boolean getIpReachabilityDisconnectEnabled() {
+        return mIpReachabilityDisconnectEnabled;
+    }
+
+    /**
+     * Sets whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.
+     */
+    public void setIpReachabilityDisconnectEnabled(boolean enabled) {
+        mIpReachabilityDisconnectEnabled = enabled;
+    }
+
+    /**
+     * Sends a message to initialize the WifiStateMachine.
+     *
+     * @return true if succeeded, false otherwise.
+     */
+    public boolean syncInitialize(AsyncChannel channel) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_INITIALIZE);
+        boolean result = (resultMsg.arg1 != FAILURE);
+        resultMsg.recycle();
+        return result;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiStateMachinePrime.java b/service/java/com/android/server/wifi/WifiStateMachinePrime.java
new file mode 100644
index 0000000..2006884
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiStateMachinePrime.java
@@ -0,0 +1,450 @@
+/*
+ * 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.IApInterface;
+import android.net.wifi.IWificond;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+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.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * This class provides the implementation for different WiFi operating modes.
+ *
+ * NOTE: The class is a WIP and is in active development.  It is intended to replace the existing
+ * WifiStateMachine.java class when the rearchitecture is complete.
+ */
+public class WifiStateMachinePrime {
+    private static final String TAG = "WifiStateMachinePrime";
+
+    private ModeStateMachine mModeStateMachine;
+
+    private final WifiInjector mWifiInjector;
+    private final Looper mLooper;
+    private final INetworkManagementService mNMService;
+
+    private IWificond mWificond;
+
+    private Queue<WifiConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
+
+    /* The base for wifi message types */
+    static final int BASE = Protocol.BASE_WIFI;
+
+    /* Start the soft access point */
+    static final int CMD_START_AP                                       = BASE + 21;
+    /* Indicates soft ap start failed */
+    static final int CMD_START_AP_FAILURE                               = BASE + 22;
+    /* Stop the soft access point */
+    static final int CMD_STOP_AP                                        = BASE + 23;
+    /* Soft access point teardown is completed. */
+    static final int CMD_AP_STOPPED                                     = BASE + 24;
+
+    WifiStateMachinePrime(WifiInjector wifiInjector,
+                          Looper looper,
+                          INetworkManagementService nmService) {
+        mWifiInjector = wifiInjector;
+        mLooper = looper;
+        mNMService = nmService;
+
+        // Clean up existing interfaces in wificond.
+        // This ensures that the framework and wificond are in a consistent state after a framework
+        // restart.
+        try {
+            mWificond = mWifiInjector.makeWificond();
+            if (mWificond != null) {
+                mWificond.tearDownInterfaces();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "wificond died during framework startup");
+        }
+    }
+
+    /**
+     * Method to switch wifi into client mode where connections to configured networks will be
+     * attempted.
+     */
+    public void enterClientMode() {
+        changeMode(ModeStateMachine.CMD_START_CLIENT_MODE);
+    }
+
+    /**
+     * Method to switch wifi into scan only mode where network connection attempts will not be made.
+     *
+     * This mode is utilized by location scans.  If wifi is disabled by a user, but they have
+     * previously configured their device to perform location scans, this mode allows wifi to
+     * fulfill the location scan requests but will not be used for connectivity.
+     */
+    public void enterScanOnlyMode() {
+        changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE);
+    }
+
+    /**
+     * Method to enable soft ap for wifi hotspot.
+     *
+     * The WifiConfiguration is generally going to be null to indicate that the
+     * currently saved config in WifiApConfigManager should be used.  When the config is
+     * not null, it will be saved in the WifiApConfigManager. This save is performed in the
+     * constructor of SoftApManager.
+     *
+     * @param wifiConfig WifiConfiguration for the hostapd softap
+     */
+    public void enterSoftAPMode(WifiConfiguration wifiConfig) {
+        if (wifiConfig == null) {
+            wifiConfig = new WifiConfiguration();
+        }
+        mApConfigQueue.offer(wifiConfig);
+        changeMode(ModeStateMachine.CMD_START_SOFT_AP_MODE);
+    }
+
+    /**
+     * Method to fully disable wifi.
+     *
+     * This mode will completely shut down wifi and will not perform any network scans.
+     */
+    public void disableWifi() {
+        changeMode(ModeStateMachine.CMD_DISABLE_WIFI);
+    }
+
+    protected String getCurrentMode() {
+        if (mModeStateMachine != null) {
+            return mModeStateMachine.getCurrentMode();
+        }
+        return "WifiDisabledState";
+    }
+
+    private void changeMode(int newMode) {
+        if (mModeStateMachine == null) {
+            if (newMode == ModeStateMachine.CMD_DISABLE_WIFI) {
+                // command is to disable wifi, but it is already disabled.
+                Log.e(TAG, "Received call to disable wifi when it is already disabled.");
+                return;
+            }
+            // state machine was not initialized yet, we must be starting up.
+            mModeStateMachine = new ModeStateMachine();
+        }
+        mModeStateMachine.sendMessage(newMode);
+    }
+
+    private class ModeStateMachine extends StateMachine {
+        // Commands for the state machine.
+        public static final int CMD_START_CLIENT_MODE    = 0;
+        public static final int CMD_START_SCAN_ONLY_MODE = 1;
+        public static final int CMD_START_SOFT_AP_MODE   = 2;
+        public static final int CMD_DISABLE_WIFI         = 3;
+
+        // Create the base modes for WSM.
+        private final State mClientModeState = new ClientModeState();
+        private final State mScanOnlyModeState = new ScanOnlyModeState();
+        private final State mSoftAPModeState = new SoftAPModeState();
+        private final State mWifiDisabledState = new WifiDisabledState();
+
+        // Create the active versions of the modes for WSM.
+        private final State mClientModeActiveState = new ClientModeActiveState();
+        private final State mScanOnlyModeActiveState = new ScanOnlyModeActiveState();
+        private final State mSoftAPModeActiveState = new SoftAPModeActiveState();
+
+        ModeStateMachine() {
+            super(TAG, mLooper);
+
+            // CHECKSTYLE:OFF IndentationCheck
+            addState(mClientModeState);
+              addState(mClientModeActiveState, mClientModeState);
+            addState(mScanOnlyModeState);
+              addState(mScanOnlyModeActiveState, mScanOnlyModeState);
+            addState(mSoftAPModeState);
+              addState(mSoftAPModeActiveState, mSoftAPModeState);
+            addState(mWifiDisabledState);
+            // CHECKSTYLE:ON IndentationCheck
+
+            Log.d(TAG, "Starting Wifi in WifiDisabledState");
+            setInitialState(mWifiDisabledState);
+            start();
+        }
+
+        private String getCurrentMode() {
+            return getCurrentState().getName();
+        }
+
+        private boolean checkForAndHandleModeChange(Message message) {
+            switch(message.what) {
+                case ModeStateMachine.CMD_START_CLIENT_MODE:
+                    Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode");
+                    mModeStateMachine.transitionTo(mClientModeState);
+                    break;
+                case ModeStateMachine.CMD_START_SCAN_ONLY_MODE:
+                    Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode");
+                    mModeStateMachine.transitionTo(mScanOnlyModeState);
+                    break;
+                case ModeStateMachine.CMD_START_SOFT_AP_MODE:
+                    Log.d(TAG, "Switching from " + getCurrentMode() + " to SoftApMode");
+                    mModeStateMachine.transitionTo(mSoftAPModeState);
+                    break;
+                case ModeStateMachine.CMD_DISABLE_WIFI:
+                    Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled");
+                    mModeStateMachine.transitionTo(mWifiDisabledState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        private void tearDownInterfaces() {
+            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;
+            }
+            return;
+        }
+
+        class ClientModeState extends State {
+            @Override
+            public void enter() {
+                mWificond = mWifiInjector.makeWificond();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (checkForAndHandleModeChange(message)) {
+                    return HANDLED;
+                }
+                return NOT_HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                tearDownInterfaces();
+            }
+        }
+
+        class ScanOnlyModeState extends State {
+            @Override
+            public void enter() {
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (checkForAndHandleModeChange(message)) {
+                    return HANDLED;
+                }
+                return NOT_HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                // Do not tear down interfaces yet since this mode is not actively controlled or
+                // used in tests at this time.
+                // tearDownInterfaces();
+            }
+        }
+
+        class SoftAPModeState extends State {
+            IApInterface mApInterface = null;
+
+            @Override
+            public void enter() {
+                final Message message = mModeStateMachine.getCurrentMessage();
+                if (message.what != ModeStateMachine.CMD_START_SOFT_AP_MODE) {
+                    Log.d(TAG, "Entering SoftAPMode (idle)");
+                    return;
+                }
+
+                // Continue with setup since we are changing modes
+                mApInterface = null;
+                mWificond = mWifiInjector.makeWificond();
+                if (mWificond == null) {
+                    Log.e(TAG, "Failed to get reference to wificond");
+                    writeApConfigDueToStartFailure();
+                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
+                    return;
+                }
+
+                try {
+                    mApInterface = mWificond.createApInterface();
+                } catch (RemoteException e1) { }
+
+                if (mApInterface == null) {
+                    Log.e(TAG, "Could not get IApInterface instance from wificond");
+                    writeApConfigDueToStartFailure();
+                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
+                    return;
+                }
+                mModeStateMachine.transitionTo(mSoftAPModeActiveState);
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (checkForAndHandleModeChange(message)) {
+                    return HANDLED;
+                }
+
+                switch(message.what) {
+                    case CMD_START_AP:
+                        Log.d(TAG, "Received CMD_START_AP (now invalid message) - dropping");
+                        break;
+                    case CMD_STOP_AP:
+                        // not in active state, nothing to stop.
+                        break;
+                    case CMD_START_AP_FAILURE:
+                        Log.e(TAG, "Failed to start SoftApMode.  Wait for next mode command.");
+                        break;
+                    case CMD_AP_STOPPED:
+                        Log.d(TAG, "SoftApModeActiveState stopped.  Wait for next mode command.");
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                tearDownInterfaces();
+            }
+
+            protected IApInterface getInterface() {
+                return mApInterface;
+            }
+
+            private void writeApConfigDueToStartFailure() {
+                WifiConfiguration config = mApConfigQueue.poll();
+                if (config != null && config.SSID != null) {
+                    // Save valid configs for future calls.
+                    mWifiInjector.getWifiApConfigStore().setApConfiguration(config);
+                }
+            }
+        }
+
+        class WifiDisabledState extends State {
+            @Override
+            public void enter() {
+                // make sure everything is torn down
+                Log.d(TAG, "Entering WifiDisabledState");
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(TAG, "received a message in WifiDisabledState: " + message);
+                if (checkForAndHandleModeChange(message)) {
+                    return HANDLED;
+                }
+                return NOT_HANDLED;
+            }
+
+        }
+
+        class ModeActiveState extends State {
+            ActiveModeManager mActiveModeManager;
+
+            @Override
+            public boolean processMessage(Message message) {
+                // handle messages for changing modes here
+                return NOT_HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                // clean up objects from an active state - check with mode handlers to make sure
+                // they are stopping properly.
+                mActiveModeManager.stop();
+            }
+        }
+
+        class ClientModeActiveState extends ModeActiveState {
+            @Override
+            public void enter() {
+                this.mActiveModeManager = new ClientModeManager();
+            }
+        }
+
+        class ScanOnlyModeActiveState extends ModeActiveState {
+            @Override
+            public void enter() {
+                this.mActiveModeManager = new ScanOnlyModeManager();
+            }
+        }
+
+        class SoftAPModeActiveState extends ModeActiveState {
+            private class SoftApListener implements SoftApManager.Listener {
+                @Override
+                public void onStateChanged(int state, int reason) {
+                    if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
+                        mModeStateMachine.sendMessage(CMD_AP_STOPPED);
+                    } else if (state == WifiManager.WIFI_AP_STATE_FAILED) {
+                        mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
+                    }
+                }
+            }
+
+            @Override
+            public void enter() {
+                Log.d(TAG, "Entering SoftApModeActiveState");
+                WifiConfiguration config = mApConfigQueue.poll();
+                if (config != null && config.SSID != null) {
+                    Log.d(TAG, "Passing config to SoftApManager! " + config);
+                } else {
+                    config = null;
+                }
+
+                this.mActiveModeManager = mWifiInjector.makeSoftApManager(mNMService,
+                        new SoftApListener(), ((SoftAPModeState) mSoftAPModeState).getInterface(),
+                        config);
+                mActiveModeManager.start();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    case CMD_START_AP:
+                        Log.d(TAG, "Received CMD_START_AP when active - invalid message - drop");
+                        break;
+                    case CMD_STOP_AP:
+                        mActiveModeManager.stop();
+                        break;
+                    case CMD_START_AP_FAILURE:
+                        Log.d(TAG, "Failed to start SoftApMode.  Return to SoftApMode (inactive).");
+                        mModeStateMachine.transitionTo(mSoftAPModeState);
+                        break;
+                    case CMD_AP_STOPPED:
+                        Log.d(TAG, "SoftApModeActiveState stopped."
+                                + "  Return to SoftApMode (inactive).");
+                        mModeStateMachine.transitionTo(mSoftAPModeState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+    }  // class ModeStateMachine
+}
diff --git a/service/java/com/android/server/wifi/WifiStateTracker.java b/service/java/com/android/server/wifi/WifiStateTracker.java
new file mode 100644
index 0000000..317aa26
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiStateTracker.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.os.BatteryStats;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.app.IBatteryStats;
+
+/**
+ * This class is used to track WifiState to update BatteryStats
+ */
+public class WifiStateTracker {
+    private static final String TAG = "WifiStateTracker";
+
+    public static final int INVALID = 0;
+    public static final int SCAN_MODE = 1;
+    public static final int DISCONNECTED = 2;
+    public static final int CONNECTED = 3;
+    public static final int SOFT_AP = 4;
+    private int mWifiState;
+    private IBatteryStats mBatteryStats;
+
+    public WifiStateTracker(IBatteryStats stats) {
+        mWifiState = INVALID;
+        mBatteryStats = stats;
+    }
+
+    private void informWifiStateBatteryStats(int state) {
+        try {
+            mBatteryStats.noteWifiState(state, null);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Battery stats unreachable " + e.getMessage());
+        }
+    }
+
+    /**
+     * Inform the WifiState to this tracker to translate into the
+     * WifiState corresponding to BatteryStats.
+     * @param state state corresponding to the WifiStateMachine state
+     */
+    public void updateState(int state) {
+        int reportState = BatteryStats.WIFI_STATE_OFF;
+        if (state != mWifiState) {
+            switch(state) {
+                case SCAN_MODE:
+                    reportState = BatteryStats.WIFI_STATE_OFF_SCANNING;
+                    break;
+                case DISCONNECTED:
+                    reportState = BatteryStats.WIFI_STATE_ON_DISCONNECTED;
+                    break;
+                case CONNECTED:
+                    reportState = BatteryStats.WIFI_STATE_ON_CONNECTED_STA;
+                    break;
+                case SOFT_AP:
+                    reportState = BatteryStats.WIFI_STATE_SOFT_AP;
+                    break;
+                case INVALID:
+                    mWifiState = INVALID;
+                    /* Fall through */
+                default:
+                    return;
+            }
+            mWifiState = state;
+            informWifiStateBatteryStats(reportState);
+        }
+        return;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiTrafficPoller.java b/service/java/com/android/server/wifi/WifiTrafficPoller.java
index 336e0d7..2efbb2e 100644
--- a/service/java/com/android/server/wifi/WifiTrafficPoller.java
+++ b/service/java/com/android/server/wifi/WifiTrafficPoller.java
@@ -38,12 +38,12 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-/* Polls for traffic stats and notifies the clients */
-final class WifiTrafficPoller {
+/**
+ * Polls for traffic stats and notifies the clients
+ */
+public 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 +71,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 +112,10 @@
     }
 
     void enableVerboseLogging(int verbose) {
-        if (verbose > 0 ) {
-            DBG = true;
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
         } else {
-            DBG = false;
+            mVerboseLoggingEnabled = false;
         }
     }
 
@@ -126,8 +128,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 +141,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 +155,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 +189,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 +208,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/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java
new file mode 100644
index 0000000..88f1898
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiVendorHal.java
@@ -0,0 +1,2534 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import android.hardware.wifi.V1_0.IWifiApIface;
+import android.hardware.wifi.V1_0.IWifiChip;
+import android.hardware.wifi.V1_0.IWifiChipEventCallback;
+import android.hardware.wifi.V1_0.IWifiIface;
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
+import android.hardware.wifi.V1_0.IWifiStaIface;
+import android.hardware.wifi.V1_0.IWifiStaIfaceEventCallback;
+import android.hardware.wifi.V1_0.IfaceType;
+import android.hardware.wifi.V1_0.RttBw;
+import android.hardware.wifi.V1_0.RttConfig;
+import android.hardware.wifi.V1_0.RttPeerType;
+import android.hardware.wifi.V1_0.RttPreamble;
+import android.hardware.wifi.V1_0.RttResponder;
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttType;
+import android.hardware.wifi.V1_0.StaBackgroundScanBucketEventReportSchemeMask;
+import android.hardware.wifi.V1_0.StaBackgroundScanBucketParameters;
+import android.hardware.wifi.V1_0.StaBackgroundScanParameters;
+import android.hardware.wifi.V1_0.StaLinkLayerRadioStats;
+import android.hardware.wifi.V1_0.StaLinkLayerStats;
+import android.hardware.wifi.V1_0.StaRoamingConfig;
+import android.hardware.wifi.V1_0.StaRoamingState;
+import android.hardware.wifi.V1_0.StaScanData;
+import android.hardware.wifi.V1_0.StaScanDataFlagMask;
+import android.hardware.wifi.V1_0.StaScanResult;
+import android.hardware.wifi.V1_0.WifiBand;
+import android.hardware.wifi.V1_0.WifiChannelWidthInMhz;
+import android.hardware.wifi.V1_0.WifiDebugHostWakeReasonStats;
+import android.hardware.wifi.V1_0.WifiDebugPacketFateFrameType;
+import android.hardware.wifi.V1_0.WifiDebugRingBufferFlags;
+import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
+import android.hardware.wifi.V1_0.WifiDebugRxPacketFate;
+import android.hardware.wifi.V1_0.WifiDebugRxPacketFateReport;
+import android.hardware.wifi.V1_0.WifiDebugTxPacketFate;
+import android.hardware.wifi.V1_0.WifiDebugTxPacketFateReport;
+import android.hardware.wifi.V1_0.WifiInformationElement;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.apf.ApfCapabilities;
+import android.net.wifi.RttManager;
+import android.net.wifi.RttManager.ResponderConfig;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiLinkLayerStats;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.WifiWakeReasonAndCounts;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.MutableBoolean;
+import android.util.MutableInt;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.connectivity.KeepalivePacketData;
+import com.android.server.wifi.util.BitMask;
+import com.android.server.wifi.util.NativeUtil;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Vendor HAL via HIDL
+ */
+public class WifiVendorHal {
+
+    private static final WifiLog sNoLog = new FakeWifiLog();
+
+    /**
+     * Chatty logging should use mVerboseLog
+     */
+    @VisibleForTesting
+    WifiLog mVerboseLog = sNoLog;
+
+    /**
+     * Errors should use mLog
+     */
+    @VisibleForTesting
+    WifiLog mLog = new LogcatLog("WifiVendorHal");
+
+    /**
+     * Enables or disables verbose logging
+     *
+     * @param verbose - with the obvious interpretation
+     */
+    public void enableVerboseLogging(boolean verbose) {
+        synchronized (sLock) {
+            if (verbose) {
+                mVerboseLog = mLog;
+                enter("verbose=true").flush();
+            } else {
+                enter("verbose=false").flush();
+                mVerboseLog = sNoLog;
+            }
+        }
+    }
+
+    /**
+     * Checks for a successful status result.
+     *
+     * Failures are logged to mLog.
+     *
+     * @param status is the WifiStatus generated by a hal call
+     * @return true for success, false for failure
+     */
+    private boolean ok(WifiStatus status) {
+        if (status.code == WifiStatusCode.SUCCESS) return true;
+
+        Thread cur = Thread.currentThread();
+        StackTraceElement[] trace = cur.getStackTrace();
+
+        mLog.err("% failed %")
+                .c(niceMethodName(trace, 3))
+                .c(status.toString())
+                .flush();
+
+        return false;
+    }
+
+    /**
+     * Logs if the argument is false.
+     *
+     * Always returns its argument.
+     */
+    private boolean boolResult(boolean result) {
+        if (mVerboseLog == sNoLog) return result;
+        // Currently only seen if verbose logging is on
+
+        Thread cur = Thread.currentThread();
+        StackTraceElement[] trace = cur.getStackTrace();
+
+        mVerboseLog.err("% returns %")
+                .c(niceMethodName(trace, 3))
+                .c(result)
+                .flush();
+
+        return result;
+    }
+
+    /**
+     * Logs at method entry
+     *
+     * @param format string with % placeholders
+     * @return LogMessage formatter (remember to .flush())
+     */
+    private WifiLog.LogMessage enter(String format) {
+        if (mVerboseLog == sNoLog) return sNoLog.info(format);
+        Thread cur = Thread.currentThread();
+        StackTraceElement[] trace = cur.getStackTrace();
+        return mVerboseLog.trace("% " + format).c(trace[3].getMethodName());
+    }
+
+    /**
+     * Gets the method name and line number from a stack trace.
+     *
+     * Attempts to skip frames created by lambdas to get a human-sensible name.
+     *
+     * @param trace, fo example obtained by Thread.currentThread().getStackTrace()
+     * @param start  frame number to log, typically 3
+     * @return string cotaining the method name and line number
+     */
+    private static String niceMethodName(StackTraceElement[] trace, int start) {
+        if (start >= trace.length) return "";
+        StackTraceElement s = trace[start];
+        String name = s.getMethodName();
+        if (name.contains("lambda$")) {
+            // Try to find a friendlier method name
+            String myFile = s.getFileName();
+            if (myFile != null) {
+                for (int i = start + 1; i < trace.length; i++) {
+                    if (myFile.equals(trace[i].getFileName())) {
+                        name = trace[i].getMethodName();
+                        break;
+                    }
+                }
+            }
+        }
+        return (name + "(l." + s.getLineNumber() + ")");
+    }
+
+    // Vendor HAL HIDL interface objects.
+    private IWifiChip mIWifiChip;
+    private IWifiStaIface mIWifiStaIface;
+    private IWifiApIface mIWifiApIface;
+    private IWifiRttController mIWifiRttController;
+    private final HalDeviceManager mHalDeviceManager;
+    private final HalDeviceManagerStatusListener mHalDeviceManagerStatusCallbacks;
+    private final IWifiStaIfaceEventCallback mIWifiStaIfaceEventCallback;
+    private final IWifiChipEventCallback mIWifiChipEventCallback;
+    private final RttEventCallback mRttEventCallback;
+
+    // Plumbing for event handling.
+    //
+    // Being final fields, they can be accessed without synchronization under
+    // some reasonable assumptions. See
+    // https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
+    private final Looper mLooper;
+    private final Handler mHalEventHandler;
+
+    public WifiVendorHal(HalDeviceManager halDeviceManager,
+                         Looper looper) {
+        mHalDeviceManager = halDeviceManager;
+        mLooper = looper;
+        mHalEventHandler = new Handler(looper);
+        mHalDeviceManagerStatusCallbacks = new HalDeviceManagerStatusListener();
+        mIWifiStaIfaceEventCallback = new StaIfaceEventCallback();
+        mIWifiChipEventCallback = new ChipEventCallback();
+        mRttEventCallback = new RttEventCallback();
+    }
+
+    // TODO(mplass): figure out where we need locking in hidl world. b/33383725
+    public static final Object sLock = new Object();
+
+    private void handleRemoteException(RemoteException e) {
+        String methodName = niceMethodName(Thread.currentThread().getStackTrace(), 3);
+        mVerboseLog.err("% RemoteException in HIDL call %").c(methodName).c(e.toString()).flush();
+        clearState();
+    }
+
+    private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
+
+    /**
+     * Initialize the Hal device manager and register for status callbacks.
+     *
+     * @param handler Handler to notify if the vendor HAL dies.
+     * @return true on success, false otherwise.
+     */
+    public boolean initialize(WifiNative.VendorHalDeathEventHandler handler) {
+        synchronized (sLock) {
+            mHalDeviceManager.initialize();
+            mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks, mLooper);
+            mDeathEventHandler = handler;
+            return true;
+        }
+    }
+
+    /**
+     * Returns whether the vendor HAL is supported on this device or not.
+     */
+    public boolean isVendorHalSupported() {
+        synchronized (sLock) {
+            return mHalDeviceManager.isSupported();
+        }
+    }
+
+    /**
+     * Bring up the HIDL Vendor HAL and configure for AP (Access Point) mode
+     *
+     * @return true for success
+     */
+    public boolean startVendorHalAp() {
+        return startVendorHal(AP_MODE);
+    }
+
+    /**
+     * Bring up the HIDL Vendor HAL and configure for STA (Station) mode
+     *
+     * @return true for success
+     */
+    public boolean startVendorHalSta() {
+        return startVendorHal(STA_MODE);
+    }
+
+    public static final boolean STA_MODE = true;
+    public static final boolean AP_MODE = false;
+
+    /**
+     * Bring up the HIDL Vendor HAL and configure for STA mode or AP mode.
+     *
+     * @param isStaMode true to start HAL in STA mode, false to start in AP mode.
+     */
+    public boolean startVendorHal(boolean isStaMode) {
+        synchronized (sLock) {
+            if (mIWifiStaIface != null) return boolResult(false);
+            if (mIWifiApIface != null) return boolResult(false);
+            if (!mHalDeviceManager.start()) {
+                return startFailedTo("start the vendor HAL");
+            }
+            IWifiIface iface;
+            if (isStaMode) {
+                mIWifiStaIface = mHalDeviceManager.createStaIface(null, null);
+                if (mIWifiStaIface == null) {
+                    return startFailedTo("create STA Iface");
+                }
+                iface = (IWifiIface) mIWifiStaIface;
+                if (!registerStaIfaceCallback()) {
+                    return startFailedTo("register sta iface callback");
+                }
+                mIWifiRttController = mHalDeviceManager.createRttController(iface);
+                if (mIWifiRttController == null) {
+                    return startFailedTo("create RTT controller");
+                }
+                if (!registerRttEventCallback()) {
+                    return startFailedTo("register RTT iface callback");
+                }
+                enableLinkLayerStats();
+            } else {
+                mIWifiApIface = mHalDeviceManager.createApIface(null, null);
+                if (mIWifiApIface == null) {
+                    return startFailedTo("create AP Iface");
+                }
+                iface = (IWifiIface) mIWifiApIface;
+            }
+            mIWifiChip = mHalDeviceManager.getChip(iface);
+            if (mIWifiChip == null) {
+                return startFailedTo("get the chip created for the Iface");
+            }
+            if (!registerChipCallback()) {
+                return startFailedTo("register chip callback");
+            }
+            mLog.i("Vendor Hal started successfully");
+            return true;
+        }
+    }
+
+    /**
+     * Logs a message and cleans up after a failing start attempt
+     *
+     * The lock should be held.
+     * @param message describes what was being attempted
+     * @return false
+     */
+    private boolean startFailedTo(String message) {
+        mVerboseLog.err("Failed to %. Vendor Hal start failed").c(message).flush();
+        mHalDeviceManager.stop();
+        clearState();
+        return false;
+    }
+
+    /**
+     * Registers the sta iface callback.
+     */
+    private boolean registerStaIfaceCallback() {
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            if (mIWifiStaIfaceEventCallback == null) return boolResult(false);
+            try {
+                WifiStatus status =
+                        mIWifiStaIface.registerEventCallback(mIWifiStaIfaceEventCallback);
+                return ok(status);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Registers the sta iface callback.
+     */
+    private boolean registerChipCallback() {
+        synchronized (sLock) {
+            if (mIWifiChip == null) return boolResult(false);
+            if (mIWifiChipEventCallback == null) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiChip.registerEventCallback(mIWifiChipEventCallback);
+                return ok(status);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Registers RTT event callback. Returns whether the registration is successful.
+     */
+    private boolean registerRttEventCallback() {
+        synchronized (sLock) {
+            if (mIWifiRttController == null) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiRttController.registerEventCallback(mRttEventCallback);
+                return ok(status);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Stops the HAL
+     */
+    public void stopVendorHal() {
+        synchronized (sLock) {
+            mHalDeviceManager.stop();
+            clearState();
+            mLog.i("Vendor Hal stopped");
+        }
+    }
+
+    /**
+     * Clears the state associated with a started Iface
+     *
+     * Caller should hold the lock.
+     */
+    private void clearState() {
+        mIWifiChip = null;
+        mIWifiStaIface = null;
+        mIWifiApIface = null;
+        mIWifiRttController = null;
+        mDriverDescription = null;
+        mFirmwareDescription = null;
+        mChannelsForBandSupport = null;
+    }
+
+    /**
+     * Tests whether the HAL is running or not
+     */
+    public boolean isHalStarted() {
+        // For external use only. Methods in this class should test for null directly.
+        synchronized (sLock) {
+            return (mIWifiStaIface != null || mIWifiApIface != null);
+        }
+    }
+
+    /**
+     * Gets the scan capabilities
+     *
+     * @param capabilities object to be filled in
+     * @return true for success, false for failure
+     */
+    public boolean getBgScanCapabilities(WifiNative.ScanCapabilities capabilities) {
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                MutableBoolean ans = new MutableBoolean(false);
+                WifiNative.ScanCapabilities out = capabilities;
+                mIWifiStaIface.getBackgroundScanCapabilities((status, cap) -> {
+                            if (!ok(status)) return;
+                            mVerboseLog.info("scan capabilities %").c(cap.toString()).flush();
+                            out.max_scan_cache_size = cap.maxCacheSize;
+                            out.max_ap_cache_per_scan = cap.maxApCachePerScan;
+                            out.max_scan_buckets = cap.maxBuckets;
+                            out.max_rssi_sample_size = 0;
+                            out.max_scan_reporting_threshold = cap.maxReportingThreshold;
+                            ans.value = true;
+                        }
+                );
+                return ans.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Holds the current background scan state, to implement pause and restart
+     */
+    @VisibleForTesting
+    class CurrentBackgroundScan {
+        public int cmdId;
+        public StaBackgroundScanParameters param;
+        public WifiNative.ScanEventHandler eventHandler = null;
+        public boolean paused = false;
+        public WifiScanner.ScanData[] latestScanResults = null;
+
+        CurrentBackgroundScan(int id, WifiNative.ScanSettings settings) {
+            cmdId = id;
+            param = new StaBackgroundScanParameters();
+            param.basePeriodInMs = settings.base_period_ms;
+            param.maxApPerScan = settings.max_ap_per_scan;
+            param.reportThresholdPercent = settings.report_threshold_percent;
+            param.reportThresholdNumScans = settings.report_threshold_num_scans;
+            if (settings.buckets != null) {
+                for (WifiNative.BucketSettings bs : settings.buckets) {
+                    param.buckets.add(makeStaBackgroundScanBucketParametersFromBucketSettings(bs));
+                }
+            }
+        }
+    }
+
+    /**
+     * Makes the Hal flavor of WifiNative.BucketSettings
+     *
+     * @param bs WifiNative.BucketSettings
+     * @return Hal flavor of bs
+     * @throws IllegalArgumentException if band value is not recognized
+     */
+    private StaBackgroundScanBucketParameters
+            makeStaBackgroundScanBucketParametersFromBucketSettings(WifiNative.BucketSettings bs) {
+        StaBackgroundScanBucketParameters pa = new StaBackgroundScanBucketParameters();
+        pa.bucketIdx = bs.bucket;
+        pa.band = makeWifiBandFromFrameworkBand(bs.band);
+        if (bs.channels != null) {
+            for (WifiNative.ChannelSettings cs : bs.channels) {
+                pa.frequencies.add(cs.frequency);
+            }
+        }
+        pa.periodInMs = bs.period_ms;
+        pa.eventReportScheme = makeReportSchemeFromBucketSettingsReportEvents(bs.report_events);
+        pa.exponentialMaxPeriodInMs = bs.max_period_ms;
+        // Although HAL API allows configurable base value for the truncated
+        // exponential back off scan. Native API and above support only
+        // truncated binary exponential back off scan.
+        // Hard code value of base to 2 here.
+        pa.exponentialBase = 2;
+        pa.exponentialStepCount = bs.step_count;
+        return pa;
+    }
+
+    /**
+     * Makes the Hal flavor of WifiScanner's band indication
+     *
+     * @param frameworkBand one of WifiScanner.WIFI_BAND_*
+     * @return A WifiBand value
+     * @throws IllegalArgumentException if frameworkBand is not recognized
+     */
+    private int makeWifiBandFromFrameworkBand(int frameworkBand) {
+        switch (frameworkBand) {
+            case WifiScanner.WIFI_BAND_UNSPECIFIED:
+                return WifiBand.BAND_UNSPECIFIED;
+            case WifiScanner.WIFI_BAND_24_GHZ:
+                return WifiBand.BAND_24GHZ;
+            case WifiScanner.WIFI_BAND_5_GHZ:
+                return WifiBand.BAND_5GHZ;
+            case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+                return WifiBand.BAND_5GHZ_DFS;
+            case WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS:
+                return WifiBand.BAND_5GHZ_WITH_DFS;
+            case WifiScanner.WIFI_BAND_BOTH:
+                return WifiBand.BAND_24GHZ_5GHZ;
+            case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
+                return WifiBand.BAND_24GHZ_5GHZ_WITH_DFS;
+            default:
+                throw new IllegalArgumentException("bad band " + frameworkBand);
+        }
+    }
+
+    /**
+     * Makes the Hal flavor of WifiScanner's report event mask
+     *
+     * @param reportUnderscoreEvents is logical OR of WifiScanner.REPORT_EVENT_* values
+     * @return Corresponding StaBackgroundScanBucketEventReportSchemeMask value
+     * @throws IllegalArgumentException if a mask bit is not recognized
+     */
+    private int makeReportSchemeFromBucketSettingsReportEvents(int reportUnderscoreEvents) {
+        int ans = 0;
+        BitMask in = new BitMask(reportUnderscoreEvents);
+        if (in.testAndClear(WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN)) {
+            ans |= StaBackgroundScanBucketEventReportSchemeMask.EACH_SCAN;
+        }
+        if (in.testAndClear(WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)) {
+            ans |= StaBackgroundScanBucketEventReportSchemeMask.FULL_RESULTS;
+        }
+        if (in.testAndClear(WifiScanner.REPORT_EVENT_NO_BATCH)) {
+            ans |= StaBackgroundScanBucketEventReportSchemeMask.NO_BATCH;
+        }
+        if (in.value != 0) throw new IllegalArgumentException("bad " + reportUnderscoreEvents);
+        return ans;
+    }
+
+    private int mLastScanCmdId; // For assigning cmdIds to scans
+
+    @VisibleForTesting
+    CurrentBackgroundScan mScan = null;
+
+    /**
+     * Starts a background scan
+     *
+     * Any ongoing scan will be stopped first
+     *
+     * @param settings     to control the scan
+     * @param eventHandler to call with the results
+     * @return true for success
+     */
+    public boolean startBgScan(WifiNative.ScanSettings settings,
+                               WifiNative.ScanEventHandler eventHandler) {
+        WifiStatus status;
+        if (eventHandler == null) return boolResult(false);
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                if (mScan != null && !mScan.paused) {
+                    ok(mIWifiStaIface.stopBackgroundScan(mScan.cmdId));
+                    mScan = null;
+                }
+                mLastScanCmdId = (mLastScanCmdId % 9) + 1; // cycle through non-zero single digits
+                CurrentBackgroundScan scan = new CurrentBackgroundScan(mLastScanCmdId, settings);
+                status = mIWifiStaIface.startBackgroundScan(scan.cmdId, scan.param);
+                if (!ok(status)) return false;
+                scan.eventHandler = eventHandler;
+                mScan = scan;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+
+    /**
+     * Stops any ongoing backgound scan
+     */
+    public void stopBgScan() {
+        WifiStatus status;
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return;
+            try {
+                if (mScan != null) {
+                    ok(mIWifiStaIface.stopBackgroundScan(mScan.cmdId));
+                    mScan = null;
+                }
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+            }
+        }
+    }
+
+    /**
+     * Pauses an ongoing backgound scan
+     */
+    public void pauseBgScan() {
+        WifiStatus status;
+        synchronized (sLock) {
+            try {
+                if (mIWifiStaIface == null) return;
+                if (mScan != null && !mScan.paused) {
+                    status = mIWifiStaIface.stopBackgroundScan(mScan.cmdId);
+                    if (!ok(status)) return;
+                    mScan.paused = true;
+                }
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+            }
+        }
+    }
+
+    /**
+     * Restarts a paused background scan
+     */
+    public void restartBgScan() {
+        WifiStatus status;
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return;
+            try {
+                if (mScan != null && mScan.paused) {
+                    status = mIWifiStaIface.startBackgroundScan(mScan.cmdId, mScan.param);
+                    if (!ok(status)) return;
+                    mScan.paused = false;
+                }
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+            }
+        }
+    }
+
+    /**
+     * Gets the latest scan results received from the HIDL interface callback.
+     * TODO(b/35754840): This hop to fetch scan results after callback is unnecessary. Refactor
+     * WifiScanner to use the scan results from the callback.
+     */
+    public WifiScanner.ScanData[] getBgScanResults() {
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return null;
+            if (mScan == null) return null;
+            return mScan.latestScanResults;
+        }
+    }
+
+    /**
+     * Get the link layer statistics
+     *
+     * Note - we always enable link layer stats on a STA interface.
+     *
+     * @return the statistics, or null if unable to do so
+     */
+    public WifiLinkLayerStats getWifiLinkLayerStats() {
+        class AnswerBox {
+            public StaLinkLayerStats value = null;
+        }
+        AnswerBox answer = new AnswerBox();
+        synchronized (sLock) {
+            try {
+                if (mIWifiStaIface == null) return null;
+                mIWifiStaIface.getLinkLayerStats((status, stats) -> {
+                    if (!ok(status)) return;
+                    answer.value = stats;
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+        WifiLinkLayerStats stats = frameworkFromHalLinkLayerStats(answer.value);
+        return stats;
+    }
+
+    /**
+     * Makes the framework version of link layer stats from the hal version.
+     */
+    @VisibleForTesting
+    static WifiLinkLayerStats frameworkFromHalLinkLayerStats(StaLinkLayerStats stats) {
+        if (stats == null) return null;
+        WifiLinkLayerStats out = new WifiLinkLayerStats();
+        // unpopulated: out.status, out.SSID, out.BSSID
+        out.beacon_rx = stats.iface.beaconRx;
+        out.rssi_mgmt = stats.iface.avgRssiMgmt;
+        // Statistics are broken out by Wireless Multimedia Extensions categories
+        // WME Best Effort Access Category
+        out.rxmpdu_be = stats.iface.wmeBePktStats.rxMpdu;
+        out.txmpdu_be = stats.iface.wmeBePktStats.txMpdu;
+        out.lostmpdu_be = stats.iface.wmeBePktStats.lostMpdu;
+        out.retries_be = stats.iface.wmeBePktStats.retries;
+        // WME Background Access Category
+        out.rxmpdu_bk = stats.iface.wmeBkPktStats.rxMpdu;
+        out.txmpdu_bk = stats.iface.wmeBkPktStats.txMpdu;
+        out.lostmpdu_bk = stats.iface.wmeBkPktStats.lostMpdu;
+        out.retries_bk = stats.iface.wmeBkPktStats.retries;
+        // WME Video Access Category
+        out.rxmpdu_vi = stats.iface.wmeViPktStats.rxMpdu;
+        out.txmpdu_vi = stats.iface.wmeViPktStats.txMpdu;
+        out.lostmpdu_vi = stats.iface.wmeViPktStats.lostMpdu;
+        out.retries_vi = stats.iface.wmeViPktStats.retries;
+        // WME Voice Access Category
+        out.rxmpdu_vo = stats.iface.wmeVoPktStats.rxMpdu;
+        out.txmpdu_vo = stats.iface.wmeVoPktStats.txMpdu;
+        out.lostmpdu_vo = stats.iface.wmeVoPktStats.lostMpdu;
+        out.retries_vo = stats.iface.wmeVoPktStats.retries;
+        // TODO(b/36176141): Figure out how to coalesce this info for multi radio devices.
+        if (stats.radios.size() > 0) {
+            StaLinkLayerRadioStats radioStats = stats.radios.get(0);
+            out.on_time = radioStats.onTimeInMs;
+            out.tx_time = radioStats.txTimeInMs;
+            out.tx_time_per_level = new int[radioStats.txTimeInMsPerLevel.size()];
+            for (int i = 0; i < out.tx_time_per_level.length; i++) {
+                out.tx_time_per_level[i] = radioStats.txTimeInMsPerLevel.get(i);
+            }
+            out.rx_time = radioStats.rxTimeInMs;
+            out.on_time_scan = radioStats.onTimeInMsForScan;
+        }
+        // unused: stats.timeStampInMs;
+        return out;
+    }
+
+    @VisibleForTesting
+    boolean mLinkLayerStatsDebug = false;  // Passed to Hal
+
+    /**
+     * Enables the linkLayerStats in the Hal.
+     *
+     * This is called unconditionally whenever we create a STA interface.
+     */
+    private void enableLinkLayerStats() {
+        synchronized (sLock) {
+            try {
+                WifiStatus status;
+                status = mIWifiStaIface.enableLinkLayerStatsCollection(mLinkLayerStatsDebug);
+                if (!ok(status)) {
+                    mLog.e("unable to enable link layer stats collection");
+                }
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+            }
+        }
+    }
+
+    /**
+     * Translation table used by getSupportedFeatureSet for translating IWifiStaIface caps
+     */
+    private static final int[][] sFeatureCapabilityTranslation = {
+            {WifiManager.WIFI_FEATURE_INFRA_5G,
+                    IWifiStaIface.StaIfaceCapabilityMask.STA_5G
+            },
+            {WifiManager.WIFI_FEATURE_PASSPOINT,
+                    IWifiStaIface.StaIfaceCapabilityMask.HOTSPOT
+            },
+            {WifiManager.WIFI_FEATURE_SCANNER,
+                    IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN,
+            },
+            {WifiManager.WIFI_FEATURE_PNO,
+                    IWifiStaIface.StaIfaceCapabilityMask.PNO
+            },
+            {WifiManager.WIFI_FEATURE_TDLS,
+                    IWifiStaIface.StaIfaceCapabilityMask.TDLS
+            },
+            {WifiManager.WIFI_FEATURE_TDLS_OFFCHANNEL,
+                    IWifiStaIface.StaIfaceCapabilityMask.TDLS_OFFCHANNEL
+            },
+            {WifiManager.WIFI_FEATURE_LINK_LAYER_STATS,
+                    IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
+            },
+            {WifiManager.WIFI_FEATURE_RSSI_MONITOR,
+                    IWifiStaIface.StaIfaceCapabilityMask.RSSI_MONITOR
+            },
+            {WifiManager.WIFI_FEATURE_MKEEP_ALIVE,
+                    IWifiStaIface.StaIfaceCapabilityMask.KEEP_ALIVE
+            },
+            {WifiManager.WIFI_FEATURE_CONFIG_NDO,
+                    IWifiStaIface.StaIfaceCapabilityMask.ND_OFFLOAD
+            },
+            {WifiManager.WIFI_FEATURE_CONTROL_ROAMING,
+                    IWifiStaIface.StaIfaceCapabilityMask.CONTROL_ROAMING
+            },
+            {WifiManager.WIFI_FEATURE_IE_WHITELIST,
+                    IWifiStaIface.StaIfaceCapabilityMask.PROBE_IE_WHITELIST
+            },
+            {WifiManager.WIFI_FEATURE_SCAN_RAND,
+                    IWifiStaIface.StaIfaceCapabilityMask.SCAN_RAND
+            },
+    };
+
+    /**
+     * Feature bit mask translation for STAs
+     *
+     * @param capabilities bitmask defined IWifiStaIface.StaIfaceCapabilityMask
+     * @return bitmask defined by WifiManager.WIFI_FEATURE_*
+     */
+    @VisibleForTesting
+    int wifiFeatureMaskFromStaCapabilities(int capabilities) {
+        int features = 0;
+        for (int i = 0; i < sFeatureCapabilityTranslation.length; i++) {
+            if ((capabilities & sFeatureCapabilityTranslation[i][1]) != 0) {
+                features |= sFeatureCapabilityTranslation[i][0];
+            }
+        }
+        return features;
+    }
+
+    /**
+     * Get the supported features
+     *
+     * The result may differ depending on the mode (STA or AP)
+     *
+     * @return bitmask defined by WifiManager.WIFI_FEATURE_*
+     */
+    public int getSupportedFeatureSet() {
+        int featureSet = 0;
+        try {
+            final MutableInt feat = new MutableInt(0);
+            synchronized (sLock) {
+                if (mIWifiStaIface != null) {
+                    mIWifiStaIface.getCapabilities((status, capabilities) -> {
+                        if (!ok(status)) return;
+                        feat.value = wifiFeatureMaskFromStaCapabilities(capabilities);
+                    });
+                }
+            }
+            featureSet = feat.value;
+        } catch (RemoteException e) {
+            handleRemoteException(e);
+            return 0;
+        }
+
+        Set<Integer> supportedIfaceTypes = mHalDeviceManager.getSupportedIfaceTypes();
+        if (supportedIfaceTypes.contains(IfaceType.STA)) {
+            featureSet |= WifiManager.WIFI_FEATURE_INFRA;
+        }
+        if (supportedIfaceTypes.contains(IfaceType.AP)) {
+            featureSet |= WifiManager.WIFI_FEATURE_MOBILE_HOTSPOT;
+        }
+        if (supportedIfaceTypes.contains(IfaceType.P2P)) {
+            featureSet |= WifiManager.WIFI_FEATURE_P2P;
+        }
+        if (supportedIfaceTypes.contains(IfaceType.NAN)) {
+            featureSet |= WifiManager.WIFI_FEATURE_AWARE;
+        }
+
+        return featureSet;
+    }
+
+    /* RTT related commands/events */
+
+    /**
+     * RTT (Round Trip Time) measurement capabilities of the device.
+     */
+    public RttManager.RttCapabilities getRttCapabilities() {
+        class AnswerBox {
+            public RttManager.RttCapabilities value = null;
+        }
+        synchronized (sLock) {
+            if (mIWifiRttController == null) return null;
+            try {
+                AnswerBox box = new AnswerBox();
+                mIWifiRttController.getCapabilities((status, capabilities) -> {
+                    if (!ok(status)) return;
+                    mVerboseLog.info("rtt capabilites %").c(capabilities.toString()).flush();
+                    RttManager.RttCapabilities ans = new RttManager.RttCapabilities();
+                    ans.oneSidedRttSupported = capabilities.rttOneSidedSupported;
+                    ans.twoSided11McRttSupported = capabilities.rttFtmSupported;
+                    ans.lciSupported = capabilities.lciSupported;
+                    ans.lcrSupported = capabilities.lcrSupported;
+                    ans.preambleSupported = frameworkPreambleFromHalPreamble(
+                            capabilities.preambleSupport);
+                    ans.bwSupported = frameworkBwFromHalBw(capabilities.bwSupport);
+                    ans.responderSupported = capabilities.responderSupported;
+                    ans.secureRttSupported = false;
+                    ans.mcVersion = ((int) capabilities.mcVersion) & 0xff;
+                    box.value = ans;
+                });
+                return box.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+    }
+
+    private int mRttCmdIdNext = 1;              // used to generate new command ids
+    private int mRttCmdId;                      // id of currently active request
+    // Event handler for current active RTT request.
+    private WifiNative.RttEventHandler mRttEventHandler;
+
+    /**
+     * Receives a callback from the Hal and passes it along to our client using RttEventHandler
+     */
+    private class RttEventCallback extends IWifiRttControllerEventCallback.Stub {
+
+        @Override
+        public void onResults(int cmdId, java.util.ArrayList<RttResult> results) {
+            WifiNative.RttEventHandler eventHandler;
+            synchronized (sLock) {
+                if (cmdId != mRttCmdId || mRttEventHandler == null) return;
+                eventHandler = mRttEventHandler;
+                // Reset the command id for RTT operations in WifiVendorHal.
+                WifiVendorHal.this.mRttCmdId = 0;
+            }
+            RttManager.RttResult[] rtt = new RttManager.RttResult[results.size()];
+            for (int i = 0; i < rtt.length; i++) {
+                rtt[i] = frameworkRttResultFromHalRttResult(results.get(i));
+            }
+            eventHandler.onRttResults(rtt);
+        }
+    }
+
+    /**
+     * Converts a Hal RttResult to a RttManager.RttResult
+     */
+    @VisibleForTesting
+    static RttManager.RttResult frameworkRttResultFromHalRttResult(RttResult result) {
+        RttManager.RttResult ans = new RttManager.RttResult();
+        ans.bssid = NativeUtil.macAddressFromByteArray(result.addr);
+        ans.burstNumber = result.burstNum;
+        ans.measurementFrameNumber = result.measurementNumber;
+        ans.successMeasurementFrameNumber = result.successNumber;
+        ans.frameNumberPerBurstPeer = result.numberPerBurstPeer;
+        ans.status = result.status; //TODO(b/35138520) - don't assume identity translation
+        ans.retryAfterDuration = result.retryAfterDuration;
+        ans.measurementType = result.type;
+        ans.rssi = result.rssi;
+        ans.rssiSpread = result.rssiSpread;
+        //TODO(b/35138520) Fix HAL and framework to use the same units
+        ans.txRate = result.txRate.bitRateInKbps;
+        ans.rxRate = result.rxRate.bitRateInKbps;
+        ans.rtt = result.rtt;
+        ans.rttStandardDeviation = result.rttSd;
+        ans.rttSpread = result.rttSpread;
+        //TODO(b/35138520) These divide-by-10s were in the legacy Hal
+        ans.distance = result.distanceInMm / 10; // Convert cm to mm
+        ans.distanceStandardDeviation = result.distanceSdInMm / 10; // Convert cm to mm
+        ans.distanceSpread = result.distanceSpreadInMm / 10;
+
+        ans.ts = result.timeStampInUs;
+        ans.burstDuration = result.burstDurationInMs;
+        ans.negotiatedBurstNum = result.negotiatedBurstNum;
+        ans.LCI = ieFromHal(result.lci);
+        ans.LCR = ieFromHal(result.lcr);
+        ans.secure = false; // Not present in HIDL HAL
+        return ans;
+    }
+
+    /**
+     * Convert a Hal WifiInformationElement to its RttManager equivalent
+     */
+    @VisibleForTesting
+    static RttManager.WifiInformationElement ieFromHal(
+            android.hardware.wifi.V1_0.WifiInformationElement ie) {
+        if (ie == null) return null;
+        RttManager.WifiInformationElement ans = new RttManager.WifiInformationElement();
+        ans.id = ie.id;
+        ans.data = NativeUtil.byteArrayFromArrayList(ie.data);
+        return ans;
+    }
+
+    @VisibleForTesting
+    static RttConfig halRttConfigFromFrameworkRttParams(RttManager.RttParams params) {
+        RttConfig rttConfig = new RttConfig();
+        if (params.bssid != null) {
+            byte[] addr = NativeUtil.macAddressToByteArray(params.bssid);
+            for (int i = 0; i < rttConfig.addr.length; i++) {
+                rttConfig.addr[i] = addr[i];
+            }
+        }
+        rttConfig.type = halRttTypeFromFrameworkRttType(params.requestType);
+        rttConfig.peer = halPeerFromFrameworkPeer(params.deviceType);
+        rttConfig.channel.width = halChannelWidthFromFrameworkChannelWidth(params.channelWidth);
+        rttConfig.channel.centerFreq = params.frequency;
+        rttConfig.channel.centerFreq0 = params.centerFreq0;
+        rttConfig.channel.centerFreq1 = params.centerFreq1;
+        rttConfig.burstPeriod = params.interval; // In 100ms units, 0 means no specific
+        rttConfig.numBurst = params.numberBurst;
+        rttConfig.numFramesPerBurst = params.numSamplesPerBurst;
+        rttConfig.numRetriesPerRttFrame = params.numRetriesPerMeasurementFrame;
+        rttConfig.numRetriesPerFtmr = params.numRetriesPerFTMR;
+        rttConfig.mustRequestLci = params.LCIRequest;
+        rttConfig.mustRequestLcr = params.LCRRequest;
+        rttConfig.burstDuration = params.burstTimeout;
+        rttConfig.preamble = halPreambleFromFrameworkPreamble(params.preamble);
+        rttConfig.bw = halBwFromFrameworkBw(params.bandwidth);
+        return rttConfig;
+    }
+
+    @VisibleForTesting
+    static int halRttTypeFromFrameworkRttType(int frameworkRttType) {
+        switch (frameworkRttType) {
+            case RttManager.RTT_TYPE_ONE_SIDED:
+                return RttType.ONE_SIDED;
+            case RttManager.RTT_TYPE_TWO_SIDED:
+                return RttType.TWO_SIDED;
+            default:
+                throw new IllegalArgumentException("bad " + frameworkRttType);
+        }
+    }
+
+    @VisibleForTesting
+    static int frameworkRttTypeFromHalRttType(int halType) {
+        switch (halType) {
+            case RttType.ONE_SIDED:
+                return RttManager.RTT_TYPE_ONE_SIDED;
+            case RttType.TWO_SIDED:
+                return RttManager.RTT_TYPE_TWO_SIDED;
+            default:
+                throw new IllegalArgumentException("bad " + halType);
+        }
+    }
+
+    @VisibleForTesting
+    static int halPeerFromFrameworkPeer(int frameworkPeer) {
+        switch (frameworkPeer) {
+            case RttManager.RTT_PEER_TYPE_AP:
+                return RttPeerType.AP;
+            case RttManager.RTT_PEER_TYPE_STA:
+                return RttPeerType.STA;
+            case RttManager.RTT_PEER_P2P_GO:
+                return RttPeerType.P2P_GO;
+            case RttManager.RTT_PEER_P2P_CLIENT:
+                return RttPeerType.P2P_CLIENT;
+            case RttManager.RTT_PEER_NAN:
+                return RttPeerType.NAN;
+            default:
+                throw new IllegalArgumentException("bad " + frameworkPeer);
+        }
+    }
+
+    @VisibleForTesting
+    static int frameworkPeerFromHalPeer(int halPeer) {
+        switch (halPeer) {
+            case RttPeerType.AP:
+                return RttManager.RTT_PEER_TYPE_AP;
+            case RttPeerType.STA:
+                return RttManager.RTT_PEER_TYPE_STA;
+            case RttPeerType.P2P_GO:
+                return RttManager.RTT_PEER_P2P_GO;
+            case RttPeerType.P2P_CLIENT:
+                return RttManager.RTT_PEER_P2P_CLIENT;
+            case RttPeerType.NAN:
+                return RttManager.RTT_PEER_NAN;
+            default:
+                throw new IllegalArgumentException("bad " + halPeer);
+
+        }
+    }
+
+    @VisibleForTesting
+    static int halChannelWidthFromFrameworkChannelWidth(int frameworkChannelWidth) {
+        switch (frameworkChannelWidth) {
+            case ScanResult.CHANNEL_WIDTH_20MHZ:
+                return WifiChannelWidthInMhz.WIDTH_20;
+            case ScanResult.CHANNEL_WIDTH_40MHZ:
+                return WifiChannelWidthInMhz.WIDTH_40;
+            case ScanResult.CHANNEL_WIDTH_80MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80;
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                return WifiChannelWidthInMhz.WIDTH_160;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80P80;
+            default:
+                throw new IllegalArgumentException("bad " + frameworkChannelWidth);
+        }
+    }
+
+    @VisibleForTesting
+    static int frameworkChannelWidthFromHalChannelWidth(int halChannelWidth) {
+        switch (halChannelWidth) {
+            case WifiChannelWidthInMhz.WIDTH_20:
+                return ScanResult.CHANNEL_WIDTH_20MHZ;
+            case WifiChannelWidthInMhz.WIDTH_40:
+                return ScanResult.CHANNEL_WIDTH_40MHZ;
+            case WifiChannelWidthInMhz.WIDTH_80:
+                return ScanResult.CHANNEL_WIDTH_80MHZ;
+            case WifiChannelWidthInMhz.WIDTH_160:
+                return ScanResult.CHANNEL_WIDTH_160MHZ;
+            case WifiChannelWidthInMhz.WIDTH_80P80:
+                return ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
+            default:
+                throw new IllegalArgumentException("bad " + halChannelWidth);
+        }
+    }
+
+    @VisibleForTesting
+    static int halPreambleFromFrameworkPreamble(int rttManagerPreamble) {
+        BitMask checkoff = new BitMask(rttManagerPreamble);
+        int flags = 0;
+        if (checkoff.testAndClear(RttManager.PREAMBLE_LEGACY)) {
+            flags |= RttPreamble.LEGACY;
+        }
+        if (checkoff.testAndClear(RttManager.PREAMBLE_HT)) {
+            flags |= RttPreamble.HT;
+        }
+        if (checkoff.testAndClear(RttManager.PREAMBLE_VHT)) {
+            flags |= RttPreamble.VHT;
+        }
+        if (checkoff.value != 0) {
+            throw new IllegalArgumentException("bad " + rttManagerPreamble);
+        }
+        return flags;
+    }
+
+    @VisibleForTesting
+    static int frameworkPreambleFromHalPreamble(int halPreamble) {
+        BitMask checkoff = new BitMask(halPreamble);
+        int flags = 0;
+        if (checkoff.testAndClear(RttPreamble.LEGACY)) {
+            flags |= RttManager.PREAMBLE_LEGACY;
+        }
+        if (checkoff.testAndClear(RttPreamble.HT)) {
+            flags |= RttManager.PREAMBLE_HT;
+        }
+        if (checkoff.testAndClear(RttPreamble.VHT)) {
+            flags |= RttManager.PREAMBLE_VHT;
+        }
+        if (checkoff.value != 0) {
+            throw new IllegalArgumentException("bad " + halPreamble);
+        }
+        return flags;
+    }
+
+    @VisibleForTesting
+    static int halBwFromFrameworkBw(int rttManagerBandwidth) {
+        BitMask checkoff = new BitMask(rttManagerBandwidth);
+        int flags = 0;
+        if (checkoff.testAndClear(RttManager.RTT_BW_5_SUPPORT)) {
+            flags |= RttBw.BW_5MHZ;
+        }
+        if (checkoff.testAndClear(RttManager.RTT_BW_10_SUPPORT)) {
+            flags |= RttBw.BW_10MHZ;
+        }
+        if (checkoff.testAndClear(RttManager.RTT_BW_20_SUPPORT)) {
+            flags |= RttBw.BW_20MHZ;
+        }
+        if (checkoff.testAndClear(RttManager.RTT_BW_40_SUPPORT)) {
+            flags |= RttBw.BW_40MHZ;
+        }
+        if (checkoff.testAndClear(RttManager.RTT_BW_80_SUPPORT)) {
+            flags |= RttBw.BW_80MHZ;
+        }
+        if (checkoff.testAndClear(RttManager.RTT_BW_160_SUPPORT)) {
+            flags |= RttBw.BW_160MHZ;
+        }
+        if (checkoff.value != 0) {
+            throw new IllegalArgumentException("bad " + rttManagerBandwidth);
+        }
+        return flags;
+    }
+
+    @VisibleForTesting
+    static int frameworkBwFromHalBw(int rttBw) {
+        BitMask checkoff = new BitMask(rttBw);
+        int flags = 0;
+        if (checkoff.testAndClear(RttBw.BW_5MHZ)) {
+            flags |= RttManager.RTT_BW_5_SUPPORT;
+        }
+        if (checkoff.testAndClear(RttBw.BW_10MHZ)) {
+            flags |= RttManager.RTT_BW_10_SUPPORT;
+        }
+        if (checkoff.testAndClear(RttBw.BW_20MHZ)) {
+            flags |= RttManager.RTT_BW_20_SUPPORT;
+        }
+        if (checkoff.testAndClear(RttBw.BW_40MHZ)) {
+            flags |= RttManager.RTT_BW_40_SUPPORT;
+        }
+        if (checkoff.testAndClear(RttBw.BW_80MHZ)) {
+            flags |= RttManager.RTT_BW_80_SUPPORT;
+        }
+        if (checkoff.testAndClear(RttBw.BW_160MHZ)) {
+            flags |= RttManager.RTT_BW_160_SUPPORT;
+        }
+        if (checkoff.value != 0) {
+            throw new IllegalArgumentException("bad " + rttBw);
+        }
+        return flags;
+    }
+
+    @VisibleForTesting
+    static ArrayList<RttConfig> halRttConfigArrayFromFrameworkRttParamsArray(
+            RttManager.RttParams[] params) {
+        final int length = params.length;
+        ArrayList<RttConfig> configs = new ArrayList<RttConfig>(length);
+        for (int i = 0; i < length; i++) {
+            RttConfig config = halRttConfigFromFrameworkRttParams(params[i]);
+            if (config != null) {
+                configs.add(config);
+            }
+        }
+        return configs;
+    }
+
+    /**
+     * Starts a new rtt request
+     *
+     * @param params
+     * @param handler
+     * @return success indication
+     */
+    public boolean requestRtt(RttManager.RttParams[] params, WifiNative.RttEventHandler handler) {
+        ArrayList<RttConfig> rttConfigs;
+        try {
+            rttConfigs = halRttConfigArrayFromFrameworkRttParamsArray(params);
+        } catch (IllegalArgumentException e) {
+            mLog.err("Illegal argument for RTT request").c(e.toString()).flush();
+            return false;
+        }
+        synchronized (sLock) {
+            if (mIWifiRttController == null) return boolResult(false);
+            if (mRttCmdId != 0) return boolResult(false);
+            mRttCmdId = mRttCmdIdNext++;
+            mRttEventHandler = handler;
+            if (mRttCmdIdNext <= 0) mRttCmdIdNext = 1;
+            try {
+                WifiStatus status = mIWifiRttController.rangeRequest(mRttCmdId, rttConfigs);
+                if (ok(status)) return true;
+                mRttCmdId = 0;
+                return false;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Cancels an outstanding rtt request
+     *
+     * @param params
+     * @return true if there was an outstanding request and it was successfully cancelled
+     */
+    public boolean cancelRtt(RttManager.RttParams[] params) {
+        ArrayList<RttConfig> rttConfigs = halRttConfigArrayFromFrameworkRttParamsArray(params);
+        synchronized (sLock) {
+            if (mIWifiRttController == null) return boolResult(false);
+            if (mRttCmdId == 0) return boolResult(false);
+            ArrayList<byte[/* 6 */]> addrs = new ArrayList<byte[]>(rttConfigs.size());
+            for (RttConfig x : rttConfigs) addrs.add(x.addr);
+            try {
+                WifiStatus status = mIWifiRttController.rangeCancel(mRttCmdId, addrs);
+                mRttCmdId = 0;
+                if (!ok(status)) return false;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    private int mRttResponderCmdId = 0;
+
+    /**
+     * Get RTT responder information e.g. WiFi channel to enable responder on.
+     *
+     * @return info Instance of |RttResponder|, or null for error.
+     */
+    private RttResponder getRttResponder() {
+        class AnswerBox {
+            public RttResponder value = null;
+        }
+        synchronized (sLock) {
+            if (mIWifiRttController == null) return null;
+            AnswerBox answer = new AnswerBox();
+            try {
+                mIWifiRttController.getResponderInfo((status, info) -> {
+                    if (!ok(status)) return;
+                    answer.value = info;
+                });
+                return answer.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Convert Hal RttResponder to a framework ResponderConfig
+     *
+     * @param info Instance of |RttResponder|
+     * @return framework version of same
+     */
+    private ResponderConfig frameworkResponderConfigFromHalRttResponder(RttResponder info) {
+        ResponderConfig config = new ResponderConfig();
+        config.frequency = info.channel.centerFreq;
+        config.centerFreq0 = info.channel.centerFreq0;
+        config.centerFreq1 = info.channel.centerFreq1;
+        config.channelWidth = frameworkChannelWidthFromHalChannelWidth(info.channel.width);
+        config.preamble = frameworkPreambleFromHalPreamble(info.preamble);
+        return config;
+    }
+
+    /**
+     * Enables RTT responder role on the device.
+     *
+     * @return {@link ResponderConfig} if the responder role is successfully enabled,
+     * {@code null} otherwise.
+     */
+    public ResponderConfig enableRttResponder(int timeoutSeconds) {
+        RttResponder info = getRttResponder();
+        synchronized (sLock) {
+            if (mIWifiRttController == null) return null;
+            if (mRttResponderCmdId != 0) {
+                mLog.e("responder mode already enabled - this shouldn't happen");
+                return null;
+            }
+            ResponderConfig config = null;
+            int id = mRttCmdIdNext++;
+            if (mRttCmdIdNext <= 0) mRttCmdIdNext = 1;
+            try {
+                WifiStatus status = mIWifiRttController.enableResponder(
+                        /* cmdId */id,
+                        /* WifiChannelInfo channelHint */null,
+                        timeoutSeconds, info);
+                if (ok(status)) {
+                    mRttResponderCmdId = id;
+                    config = frameworkResponderConfigFromHalRttResponder(info);
+                    mVerboseLog.i("enabling rtt " + mRttResponderCmdId);
+                }
+                return config;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Disables RTT responder role.
+     *
+     * @return {@code true} if responder role is successfully disabled,
+     * {@code false} otherwise.
+     */
+    public boolean disableRttResponder() {
+        synchronized (sLock) {
+            if (mIWifiRttController == null) return boolResult(false);
+            if (mRttResponderCmdId == 0) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiRttController.disableResponder(mRttResponderCmdId);
+                mRttResponderCmdId = 0;
+                if (!ok(status)) return false;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set the MAC OUI during scanning.
+     * <p>
+     * An OUI {Organizationally Unique Identifier} is a 24-bit number that
+     * uniquely identifies a vendor or manufacturer.
+     *
+     * @param oui
+     * @return true for success
+     */
+    public boolean setScanningMacOui(byte[] oui) {
+        if (oui == null) return boolResult(false);
+        if (oui.length != 3) return boolResult(false);
+        synchronized (sLock) {
+            try {
+                if (mIWifiStaIface == null) return boolResult(false);
+                WifiStatus status = mIWifiStaIface.setScanningMacOui(oui);
+                if (!ok(status)) return false;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Query the list of valid frequencies for the provided band.
+     * <p>
+     * The result depends on the on the country code that has been set.
+     *
+     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+     * @return frequencies vector of valid frequencies (MHz), or null for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
+    public int[] getChannelsForBand(int band) {
+        enter("%").c(band).flush();
+        class AnswerBox {
+            public int[] value = null;
+        }
+        synchronized (sLock) {
+            try {
+                AnswerBox box = new AnswerBox();
+                int hb = makeWifiBandFromFrameworkBand(band);
+                if (mIWifiStaIface != null) {
+                    mIWifiStaIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
+                        if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
+                            mChannelsForBandSupport = false;
+                        }
+                        if (!ok(status)) return;
+                        mChannelsForBandSupport = true;
+                        box.value = intArrayFromArrayList(frequencies);
+                    });
+                } else if (mIWifiApIface != null) {
+                    mIWifiApIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
+                        if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
+                            mChannelsForBandSupport = false;
+                        }
+                        if (!ok(status)) return;
+                        mChannelsForBandSupport = true;
+                        box.value = intArrayFromArrayList(frequencies);
+                    });
+                }
+                return box.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+    }
+
+    private int[] intArrayFromArrayList(ArrayList<Integer> in) {
+        int[] ans = new int[in.size()];
+        int i = 0;
+        for (Integer e : in) ans[i++] = e;
+        return ans;
+    }
+
+    /**
+     * This holder is null until we know whether or not there is frequency-for-band support.
+     * <p>
+     * Set as a side-effect of getChannelsForBand.
+     */
+    @VisibleForTesting
+    Boolean mChannelsForBandSupport = null;
+
+    /**
+     * Indicates whether getChannelsForBand is supported.
+     *
+     * @return true if it is.
+     */
+    public boolean isGetChannelsForBandSupported() {
+        if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
+        getChannelsForBand(WifiBand.BAND_24GHZ);
+        if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
+        return false;
+    }
+
+    /**
+     * Get the APF (Android Packet Filter) capabilities of the device
+     */
+    public ApfCapabilities getApfCapabilities() {
+        class AnswerBox {
+            public ApfCapabilities value = sNoApfCapabilities;
+        }
+        synchronized (sLock) {
+            try {
+                if (mIWifiStaIface == null) return sNoApfCapabilities;
+                AnswerBox box = new AnswerBox();
+                mIWifiStaIface.getApfPacketFilterCapabilities((status, capabilities) -> {
+                    if (!ok(status)) return;
+                    box.value = new ApfCapabilities(
+                        /* apfVersionSupported */   capabilities.version,
+                        /* maximumApfProgramSize */ capabilities.maxLength,
+                        /* apfPacketFormat */       android.system.OsConstants.ARPHRD_ETHER);
+                });
+                return box.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return sNoApfCapabilities;
+            }
+        }
+    }
+
+    private static final ApfCapabilities sNoApfCapabilities = new ApfCapabilities(0, 0, 0);
+
+    /**
+     * Installs an APF program on this iface, replacing any existing program.
+     *
+     * @param filter is the android packet filter program
+     * @return true for success
+     */
+    public boolean installPacketFilter(byte[] filter) {
+        int cmdId = 0; // We only aspire to support one program at a time
+        if (filter == null) return boolResult(false);
+        // Copy the program before taking the lock.
+        ArrayList<Byte> program = NativeUtil.byteArrayToArrayList(filter);
+        enter("filter length %").c(filter.length).flush();
+        synchronized (sLock) {
+            try {
+                if (mIWifiStaIface == null) return boolResult(false);
+                WifiStatus status = mIWifiStaIface.installApfPacketFilter(cmdId, program);
+                if (!ok(status)) return false;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set country code for this AP iface.
+     *
+     * @param countryCode - two-letter country code (as ISO 3166)
+     * @return true for success
+     */
+    public boolean setCountryCodeHal(String countryCode) {
+        if (countryCode == null) return boolResult(false);
+        if (countryCode.length() != 2) return boolResult(false);
+        byte[] code;
+        try {
+            code = NativeUtil.stringToByteArray(countryCode);
+        } catch (IllegalArgumentException e) {
+            return boolResult(false);
+        }
+        synchronized (sLock) {
+            try {
+                if (mIWifiApIface == null) return boolResult(false);
+                WifiStatus status = mIWifiApIface.setCountryCode(code);
+                if (!ok(status)) return false;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    private WifiNative.WifiLoggerEventHandler mLogEventHandler = null;
+
+    /**
+     * Registers the logger callback and enables alerts.
+     * Ring buffer data collection is only triggered when |startLoggingRingBuffer| is invoked.
+     */
+    public boolean setLoggingEventHandler(WifiNative.WifiLoggerEventHandler handler) {
+        if (handler == null) return boolResult(false);
+        synchronized (sLock) {
+            if (mIWifiChip == null) return boolResult(false);
+            if (mLogEventHandler != null) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiChip.enableDebugErrorAlerts(true);
+                if (!ok(status)) return false;
+                mLogEventHandler = handler;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Stops all logging and resets the logger callback.
+     * This stops both the alerts and ring buffer data collection.
+     */
+    public boolean resetLogHandler() {
+        synchronized (sLock) {
+            if (mIWifiChip == null) return boolResult(false);
+            if (mLogEventHandler == null) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiChip.enableDebugErrorAlerts(false);
+                if (!ok(status)) return false;
+                status = mIWifiChip.stopLoggingToDebugRingBuffer();
+                if (!ok(status)) return false;
+                mLogEventHandler = null;
+                return true;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Control debug data collection
+     *
+     * @param verboseLevel       0 to 3, inclusive. 0 stops logging.
+     * @param flags              Ignored.
+     * @param maxIntervalInSec   Maximum interval between reports; ignore if 0.
+     * @param minDataSizeInBytes Minimum data size in buffer for report; ignore if 0.
+     * @param ringName           Name of the ring for which data collection is to start.
+     * @return true for success
+     */
+    public boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxIntervalInSec,
+                                          int minDataSizeInBytes, String ringName) {
+        enter("verboseLevel=%, flags=%, maxIntervalInSec=%, minDataSizeInBytes=%, ringName=%")
+                .c(verboseLevel).c(flags).c(maxIntervalInSec).c(minDataSizeInBytes).c(ringName)
+                .flush();
+        synchronized (sLock) {
+            if (mIWifiChip == null) return boolResult(false);
+            try {
+                // note - flags are not used
+                WifiStatus status = mIWifiChip.startLoggingToDebugRingBuffer(
+                        ringName,
+                        verboseLevel,
+                        maxIntervalInSec,
+                        minDataSizeInBytes
+                );
+                return ok(status);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Pointlessly fail
+     *
+     * @return -1
+     */
+    public int getSupportedLoggerFeatureSet() {
+        return -1;
+    }
+
+    private String mDriverDescription; // Cached value filled by requestChipDebugInfo()
+
+    /**
+     * Vendor-provided wifi driver version string
+     */
+    public String getDriverVersion() {
+        synchronized (sLock) {
+            if (mDriverDescription == null) requestChipDebugInfo();
+            return mDriverDescription;
+        }
+    }
+
+    private String mFirmwareDescription; // Cached value filled by requestChipDebugInfo()
+
+    /**
+     * Vendor-provided wifi firmware version string
+     */
+    public String getFirmwareVersion() {
+        synchronized (sLock) {
+            if (mFirmwareDescription == null) requestChipDebugInfo();
+            return mFirmwareDescription;
+        }
+    }
+
+    /**
+     * Refreshes our idea of the driver and firmware versions
+     */
+    private void requestChipDebugInfo() {
+        mDriverDescription = null;
+        mFirmwareDescription = null;
+        try {
+            if (mIWifiChip == null) return;
+            mIWifiChip.requestChipDebugInfo((status, chipDebugInfo) -> {
+                if (!ok(status)) return;
+                mDriverDescription = chipDebugInfo.driverDescription;
+                mFirmwareDescription = chipDebugInfo.firmwareDescription;
+            });
+        } catch (RemoteException e) {
+            handleRemoteException(e);
+            return;
+        }
+        mLog.info("Driver: % Firmware: %")
+                .c(mDriverDescription)
+                .c(mFirmwareDescription)
+                .flush();
+    }
+
+    /**
+     * Creates RingBufferStatus from the Hal version
+     */
+    private static WifiNative.RingBufferStatus ringBufferStatus(WifiDebugRingBufferStatus h) {
+        WifiNative.RingBufferStatus ans = new WifiNative.RingBufferStatus();
+        ans.name = h.ringName;
+        ans.flag = frameworkRingBufferFlagsFromHal(h.flags);
+        ans.ringBufferId = h.ringId;
+        ans.ringBufferByteSize = h.sizeInBytes;
+        ans.verboseLevel = h.verboseLevel;
+        // Remaining fields are unavailable
+        //  writtenBytes;
+        //  readBytes;
+        //  writtenRecords;
+        return ans;
+    }
+
+    /**
+     * Translates a hal wifiDebugRingBufferFlag to the WifiNative version
+     */
+    private static int frameworkRingBufferFlagsFromHal(int wifiDebugRingBufferFlag) {
+        BitMask checkoff = new BitMask(wifiDebugRingBufferFlag);
+        int flags = 0;
+        if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_BINARY_ENTRIES)) {
+            flags |= WifiNative.RingBufferStatus.HAS_BINARY_ENTRIES;
+        }
+        if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_ASCII_ENTRIES)) {
+            flags |= WifiNative.RingBufferStatus.HAS_ASCII_ENTRIES;
+        }
+        if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_PER_PACKET_ENTRIES)) {
+            flags |= WifiNative.RingBufferStatus.HAS_PER_PACKET_ENTRIES;
+        }
+        if (checkoff.value != 0) {
+            throw new IllegalArgumentException("Unknown WifiDebugRingBufferFlag " + checkoff.value);
+        }
+        return flags;
+    }
+
+    /**
+     * Creates array of RingBufferStatus from the Hal version
+     */
+    private static WifiNative.RingBufferStatus[] makeRingBufferStatusArray(
+            ArrayList<WifiDebugRingBufferStatus> ringBuffers) {
+        WifiNative.RingBufferStatus[] ans = new WifiNative.RingBufferStatus[ringBuffers.size()];
+        int i = 0;
+        for (WifiDebugRingBufferStatus b : ringBuffers) {
+            ans[i++] = ringBufferStatus(b);
+        }
+        return ans;
+    }
+
+    /**
+     * API to get the status of all ring buffers supported by driver
+     */
+    public WifiNative.RingBufferStatus[] getRingBufferStatus() {
+        class AnswerBox {
+            public WifiNative.RingBufferStatus[] value = null;
+        }
+        AnswerBox ans = new AnswerBox();
+        synchronized (sLock) {
+            if (mIWifiChip == null) return null;
+            try {
+                mIWifiChip.getDebugRingBuffersStatus((status, ringBuffers) -> {
+                    if (!ok(status)) return;
+                    ans.value = makeRingBufferStatusArray(ringBuffers);
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+        return ans.value;
+    }
+
+    /**
+     * Indicates to driver that all the data has to be uploaded urgently
+     */
+    public boolean getRingBufferData(String ringName) {
+        enter("ringName %").c(ringName).flush();
+        synchronized (sLock) {
+            if (mIWifiChip == null) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiChip.forceDumpToDebugRingBuffer(ringName);
+                return ok(status);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Request vendor debug info from the firmware
+     */
+    public byte[] getFwMemoryDump() {
+        class AnswerBox {
+            public byte[] value;
+        }
+        AnswerBox ans = new AnswerBox();
+        synchronized (sLock) {
+            if (mIWifiChip == null) return (null);
+            try {
+                mIWifiChip.requestFirmwareDebugDump((status, blob) -> {
+                    if (!ok(status)) return;
+                    ans.value = NativeUtil.byteArrayFromArrayList(blob);
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+        return ans.value;
+    }
+
+    /**
+     * Request vendor debug info from the driver
+     */
+    public byte[] getDriverStateDump() {
+        class AnswerBox {
+            public byte[] value;
+        }
+        AnswerBox ans = new AnswerBox();
+        synchronized (sLock) {
+            if (mIWifiChip == null) return (null);
+            try {
+                mIWifiChip.requestDriverDebugDump((status, blob) -> {
+                    if (!ok(status)) return;
+                    ans.value = NativeUtil.byteArrayFromArrayList(blob);
+                });
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+        return ans.value;
+    }
+
+    /**
+     * Start packet fate monitoring
+     * <p>
+     * Once started, monitoring remains active until HAL is unloaded.
+     *
+     * @return true for success
+     */
+    public boolean startPktFateMonitoring() {
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiStaIface.startDebugPacketFateMonitoring();
+                return ok(status);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    private byte halToFrameworkPktFateFrameType(int type) {
+        switch (type) {
+            case WifiDebugPacketFateFrameType.UNKNOWN:
+                return WifiLoggerHal.FRAME_TYPE_UNKNOWN;
+            case WifiDebugPacketFateFrameType.ETHERNET_II:
+                return WifiLoggerHal.FRAME_TYPE_ETHERNET_II;
+            case WifiDebugPacketFateFrameType.MGMT_80211:
+                return WifiLoggerHal.FRAME_TYPE_80211_MGMT;
+            default:
+                throw new IllegalArgumentException("bad " + type);
+        }
+    }
+
+    private byte halToFrameworkRxPktFate(int type) {
+        switch (type) {
+            case WifiDebugRxPacketFate.SUCCESS:
+                return WifiLoggerHal.RX_PKT_FATE_SUCCESS;
+            case WifiDebugRxPacketFate.FW_QUEUED:
+                return WifiLoggerHal.RX_PKT_FATE_FW_QUEUED;
+            case WifiDebugRxPacketFate.FW_DROP_FILTER:
+                return WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER;
+            case WifiDebugRxPacketFate.FW_DROP_INVALID:
+                return WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID;
+            case WifiDebugRxPacketFate.FW_DROP_NOBUFS:
+                return WifiLoggerHal.RX_PKT_FATE_FW_DROP_NOBUFS;
+            case WifiDebugRxPacketFate.FW_DROP_OTHER:
+                return WifiLoggerHal.RX_PKT_FATE_FW_DROP_OTHER;
+            case WifiDebugRxPacketFate.DRV_QUEUED:
+                return WifiLoggerHal.RX_PKT_FATE_DRV_QUEUED;
+            case WifiDebugRxPacketFate.DRV_DROP_FILTER:
+                return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_FILTER;
+            case WifiDebugRxPacketFate.DRV_DROP_INVALID:
+                return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_INVALID;
+            case WifiDebugRxPacketFate.DRV_DROP_NOBUFS:
+                return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_NOBUFS;
+            case WifiDebugRxPacketFate.DRV_DROP_OTHER:
+                return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER;
+            default:
+                throw new IllegalArgumentException("bad " + type);
+        }
+    }
+
+    private byte halToFrameworkTxPktFate(int type) {
+        switch (type) {
+            case WifiDebugTxPacketFate.ACKED:
+                return WifiLoggerHal.TX_PKT_FATE_ACKED;
+            case WifiDebugTxPacketFate.SENT:
+                return WifiLoggerHal.TX_PKT_FATE_SENT;
+            case WifiDebugTxPacketFate.FW_QUEUED:
+                return WifiLoggerHal.TX_PKT_FATE_FW_QUEUED;
+            case WifiDebugTxPacketFate.FW_DROP_INVALID:
+                return WifiLoggerHal.TX_PKT_FATE_FW_DROP_INVALID;
+            case WifiDebugTxPacketFate.FW_DROP_NOBUFS:
+                return WifiLoggerHal.TX_PKT_FATE_FW_DROP_NOBUFS;
+            case WifiDebugTxPacketFate.FW_DROP_OTHER:
+                return WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER;
+            case WifiDebugTxPacketFate.DRV_QUEUED:
+                return WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED;
+            case WifiDebugTxPacketFate.DRV_DROP_INVALID:
+                return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_INVALID;
+            case WifiDebugTxPacketFate.DRV_DROP_NOBUFS:
+                return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_NOBUFS;
+            case WifiDebugTxPacketFate.DRV_DROP_OTHER:
+                return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_OTHER;
+            default:
+                throw new IllegalArgumentException("bad " + type);
+        }
+    }
+
+    /**
+     * Retrieve fates of outbound packets
+     * <p>
+     * Reports the outbound frames for the most recent association (space allowing).
+     *
+     * @param reportBufs
+     * @return true for success
+     */
+    public boolean getTxPktFates(WifiNative.TxFateReport[] reportBufs) {
+        if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                MutableBoolean ok = new MutableBoolean(false);
+                mIWifiStaIface.getDebugTxPacketFates((status, fates) -> {
+                            if (!ok(status)) return;
+                            int i = 0;
+                            for (WifiDebugTxPacketFateReport fate : fates) {
+                                if (i >= reportBufs.length) break;
+                                byte code = halToFrameworkTxPktFate(fate.fate);
+                                long us = fate.frameInfo.driverTimestampUsec;
+                                byte type =
+                                        halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
+                                byte[] frame =
+                                        NativeUtil.byteArrayFromArrayList(
+                                                fate.frameInfo.frameContent);
+                                reportBufs[i++] =
+                                        new WifiNative.TxFateReport(code, us, type, frame);
+                            }
+                            ok.value = true;
+                        }
+                );
+                return ok.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Retrieve fates of inbound packets
+     * <p>
+     * Reports the inbound frames for the most recent association (space allowing).
+     *
+     * @param reportBufs
+     * @return true for success
+     */
+    public boolean getRxPktFates(WifiNative.RxFateReport[] reportBufs) {
+        if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                MutableBoolean ok = new MutableBoolean(false);
+                mIWifiStaIface.getDebugRxPacketFates((status, fates) -> {
+                            if (!ok(status)) return;
+                            int i = 0;
+                            for (WifiDebugRxPacketFateReport fate : fates) {
+                                if (i >= reportBufs.length) break;
+                                byte code = halToFrameworkRxPktFate(fate.fate);
+                                long us = fate.frameInfo.driverTimestampUsec;
+                                byte type =
+                                        halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
+                                byte[] frame =
+                                        NativeUtil.byteArrayFromArrayList(
+                                                fate.frameInfo.frameContent);
+                                reportBufs[i++] =
+                                        new WifiNative.RxFateReport(code, us, type, frame);
+                            }
+                            ok.value = true;
+                        }
+                );
+                return ok.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Start sending the specified keep alive packets periodically.
+     *
+     * @param slot
+     * @param srcMac
+     * @param keepAlivePacket
+     * @param periodInMs
+     * @return 0 for success, -1 for error
+     */
+    public int startSendingOffloadedPacket(
+            int slot, byte[] srcMac, KeepalivePacketData keepAlivePacket, int periodInMs) {
+        enter("slot=% periodInMs=%").c(slot).c(periodInMs).flush();
+
+        ArrayList<Byte> data = NativeUtil.byteArrayToArrayList(keepAlivePacket.data);
+        short protocol = (short) (keepAlivePacket.protocol);
+
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return -1;
+            try {
+                WifiStatus status = mIWifiStaIface.startSendingKeepAlivePackets(
+                        slot,
+                        data,
+                        protocol,
+                        srcMac,
+                        keepAlivePacket.dstMac,
+                        periodInMs);
+                if (!ok(status)) return -1;
+                return 0;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * Stop sending the specified keep alive packets.
+     *
+     * @param slot id - same as startSendingOffloadedPacket call.
+     * @return 0 for success, -1 for error
+     */
+    public int stopSendingOffloadedPacket(int slot) {
+        enter("slot=%").c(slot).flush();
+
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return -1;
+            try {
+                WifiStatus status = mIWifiStaIface.stopSendingKeepAlivePackets(slot);
+                if (!ok(status)) return -1;
+                return 0;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * A fixed cmdId for our RssiMonitoring (we only do one at a time)
+     */
+    @VisibleForTesting
+    static final int sRssiMonCmdId = 7551;
+
+    /**
+     * Our client's handler
+     */
+    private WifiNative.WifiRssiEventHandler mWifiRssiEventHandler;
+
+    /**
+     * Start RSSI monitoring on the currently connected access point.
+     *
+     * @param maxRssi          Maximum RSSI threshold.
+     * @param minRssi          Minimum RSSI threshold.
+     * @param rssiEventHandler Called when RSSI goes above maxRssi or below minRssi
+     * @return 0 for success, -1 for failure
+     */
+    public int startRssiMonitoring(byte maxRssi, byte minRssi,
+                                   WifiNative.WifiRssiEventHandler rssiEventHandler) {
+        enter("maxRssi=% minRssi=%").c(maxRssi).c(minRssi).flush();
+        if (maxRssi <= minRssi) return -1;
+        if (rssiEventHandler == null) return -1;
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return -1;
+            try {
+                mIWifiStaIface.stopRssiMonitoring(sRssiMonCmdId);
+                WifiStatus status;
+                status = mIWifiStaIface.startRssiMonitoring(sRssiMonCmdId, maxRssi, minRssi);
+                if (!ok(status)) return -1;
+                mWifiRssiEventHandler = rssiEventHandler;
+                return 0;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return -1;
+            }
+        }
+    }
+
+    /**
+     * Stop RSSI monitoring
+     *
+     * @return 0 for success, -1 for failure
+     */
+    public int stopRssiMonitoring() {
+        synchronized (sLock) {
+            mWifiRssiEventHandler = null;
+            if (mIWifiStaIface == null) return -1;
+            try {
+                mIWifiStaIface.stopRssiMonitoring(sRssiMonCmdId);
+                WifiStatus status = mIWifiStaIface.stopRssiMonitoring(sRssiMonCmdId);
+                if (!ok(status)) return -1;
+                return 0;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return -1;
+            }
+        }
+    }
+
+    //TODO - belongs in NativeUtil
+    private static int[] intsFromArrayList(ArrayList<Integer> a) {
+        if (a == null) return null;
+        int[] b = new int[a.size()];
+        int i = 0;
+        for (Integer e : a) b[i++] = e;
+        return b;
+    }
+
+    /**
+     * Translates from Hal version of wake reason stats to the framework version of same
+     *
+     * @param h - Hal version of wake reason stats
+     * @return framework version of same
+     */
+    private static WifiWakeReasonAndCounts halToFrameworkWakeReasons(
+            WifiDebugHostWakeReasonStats h) {
+        if (h == null) return null;
+        WifiWakeReasonAndCounts ans = new WifiWakeReasonAndCounts();
+        ans.totalCmdEventWake = h.totalCmdEventWakeCnt;
+        ans.totalDriverFwLocalWake = h.totalDriverFwLocalWakeCnt;
+        ans.totalRxDataWake = h.totalRxPacketWakeCnt;
+        ans.rxUnicast = h.rxPktWakeDetails.rxUnicastCnt;
+        ans.rxMulticast = h.rxPktWakeDetails.rxMulticastCnt;
+        ans.rxBroadcast = h.rxPktWakeDetails.rxBroadcastCnt;
+        ans.icmp = h.rxIcmpPkWakeDetails.icmpPkt;
+        ans.icmp6 = h.rxIcmpPkWakeDetails.icmp6Pkt;
+        ans.icmp6Ra = h.rxIcmpPkWakeDetails.icmp6Ra;
+        ans.icmp6Na = h.rxIcmpPkWakeDetails.icmp6Na;
+        ans.icmp6Ns = h.rxIcmpPkWakeDetails.icmp6Ns;
+        ans.ipv4RxMulticast = h.rxMulticastPkWakeDetails.ipv4RxMulticastAddrCnt;
+        ans.ipv6Multicast = h.rxMulticastPkWakeDetails.ipv6RxMulticastAddrCnt;
+        ans.otherRxMulticast = h.rxMulticastPkWakeDetails.otherRxMulticastAddrCnt;
+        ans.cmdEventWakeCntArray = intsFromArrayList(h.cmdEventWakeCntPerType);
+        ans.driverFWLocalWakeCntArray = intsFromArrayList(h.driverFwLocalWakeCntPerType);
+        return ans;
+    }
+
+    /**
+     * Fetch the host wakeup reasons stats from wlan driver.
+     *
+     * @return the |WifiWakeReasonAndCounts| from the wlan driver, or null on failure.
+     */
+    public WifiWakeReasonAndCounts getWlanWakeReasonCount() {
+        class AnswerBox {
+            public WifiDebugHostWakeReasonStats value = null;
+        }
+        AnswerBox ans = new AnswerBox();
+        synchronized (sLock) {
+            if (mIWifiChip == null) return null;
+            try {
+                mIWifiChip.getDebugHostWakeReasonStats((status, stats) -> {
+                    if (ok(status)) {
+                        ans.value = stats;
+                    }
+                });
+                return halToFrameworkWakeReasons(ans.value);
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Enable/Disable Neighbour discovery offload functionality in the firmware.
+     *
+     * @param enabled true to enable, false to disable.
+     */
+    public boolean configureNeighborDiscoveryOffload(boolean enabled) {
+        enter("enabled=%").c(enabled).flush();
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                WifiStatus status = mIWifiStaIface.enableNdOffload(enabled);
+                if (!ok(status)) return false;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // Firmware roaming control.
+
+    /**
+     * Query the firmware roaming capabilities.
+     *
+     * @param capabilities object to be filled in
+     * @return true for success; false for failure
+     */
+    public boolean getRoamingCapabilities(WifiNative.RoamingCapabilities capabilities) {
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                MutableBoolean ok = new MutableBoolean(false);
+                WifiNative.RoamingCapabilities out = capabilities;
+                mIWifiStaIface.getRoamingCapabilities((status, cap) -> {
+                    if (!ok(status)) return;
+                    out.maxBlacklistSize = cap.maxBlacklistSize;
+                    out.maxWhitelistSize = cap.maxWhitelistSize;
+                    ok.value = true;
+                });
+                return ok.value;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Enable/disable firmware roaming.
+     *
+     * @param state the intended roaming state
+     * @return SUCCESS, FAILURE, or BUSY
+     */
+    public int enableFirmwareRoaming(int state) {
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return WifiStatusCode.ERROR_NOT_STARTED;
+            try {
+                byte val;
+                switch (state) {
+                    case WifiNative.DISABLE_FIRMWARE_ROAMING:
+                        val = StaRoamingState.DISABLED;
+                        break;
+                    case WifiNative.ENABLE_FIRMWARE_ROAMING:
+                        val = StaRoamingState.ENABLED;
+                        break;
+                    default:
+                        mLog.e("enableFirmwareRoaming invalid argument " + state);
+                        return WifiStatusCode.ERROR_INVALID_ARGS;
+                }
+
+                WifiStatus status = mIWifiStaIface.setRoamingState(val);
+                mVerboseLog.d("setRoamingState returned " + status.code);
+                return status.code;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return WifiStatusCode.ERROR_UNKNOWN;
+            }
+        }
+    }
+
+    /**
+     * Set firmware roaming configurations.
+     *
+     * @param config new roaming configuration object
+     * @return true for success; false for failure
+     */
+    public boolean configureRoaming(WifiNative.RoamingConfig config) {
+        synchronized (sLock) {
+            if (mIWifiStaIface == null) return boolResult(false);
+            try {
+                StaRoamingConfig roamingConfig = new StaRoamingConfig();
+
+                // parse the blacklist BSSIDs if any
+                if (config.blacklistBssids != null) {
+                    for (String bssid : config.blacklistBssids) {
+                        byte[] mac = NativeUtil.macAddressToByteArray(bssid);
+                        roamingConfig.bssidBlacklist.add(mac);
+                    }
+                }
+
+                // parse the whitelist SSIDs if any
+                if (config.whitelistSsids != null) {
+                    for (String ssidStr : config.whitelistSsids) {
+                        String unquotedSsidStr = WifiInfo.removeDoubleQuotes(ssidStr);
+
+                        int len = unquotedSsidStr.length();
+                        if (len > 32) {
+                            mLog.err("configureRoaming: skip invalid SSID %")
+                                    .r(unquotedSsidStr).flush();
+                            continue;
+                        }
+                        byte[] ssid = new byte[len];
+                        for (int i = 0; i < len; i++) {
+                            ssid[i] = (byte) unquotedSsidStr.charAt(i);
+                        }
+                        roamingConfig.ssidWhitelist.add(ssid);
+                    }
+                }
+
+                WifiStatus status = mIWifiStaIface.configureRoaming(roamingConfig);
+                if (!ok(status)) return false;
+            } catch (RemoteException e) {
+                handleRemoteException(e);
+                return false;
+            } catch (IllegalArgumentException e) {
+                mLog.err("Illegal argument for roaming configuration").c(e.toString()).flush();
+                return false;
+            }
+            return true;
+        }
+    }
+
+    // This creates a blob of IE elements from the array received.
+    // TODO: This ugly conversion can be removed if we put IE elements in ScanResult.
+    private static byte[] hidlIeArrayToFrameworkIeBlob(ArrayList<WifiInformationElement> ies) {
+        if (ies == null || ies.isEmpty()) return new byte[0];
+        ArrayList<Byte> ieBlob = new ArrayList<>();
+        for (WifiInformationElement ie : ies) {
+            ieBlob.add(ie.id);
+            ieBlob.addAll(ie.data);
+        }
+        return NativeUtil.byteArrayFromArrayList(ieBlob);
+    }
+
+    // This is only filling up the fields of Scan Result used by Gscan clients.
+    private static ScanResult hidlToFrameworkScanResult(StaScanResult scanResult) {
+        if (scanResult == null) return null;
+        ScanResult frameworkScanResult = new ScanResult();
+        frameworkScanResult.SSID = NativeUtil.encodeSsid(scanResult.ssid);
+        frameworkScanResult.wifiSsid =
+                WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(scanResult.ssid));
+        frameworkScanResult.BSSID = NativeUtil.macAddressFromByteArray(scanResult.bssid);
+        frameworkScanResult.level = scanResult.rssi;
+        frameworkScanResult.frequency = scanResult.frequency;
+        frameworkScanResult.timestamp = scanResult.timeStampInUs;
+        frameworkScanResult.bytes = hidlIeArrayToFrameworkIeBlob(scanResult.informationElements);
+        return frameworkScanResult;
+    }
+
+    private static ScanResult[] hidlToFrameworkScanResults(ArrayList<StaScanResult> scanResults) {
+        if (scanResults == null || scanResults.isEmpty()) return new ScanResult[0];
+        ScanResult[] frameworkScanResults = new ScanResult[scanResults.size()];
+        int i = 0;
+        for (StaScanResult scanResult : scanResults) {
+            frameworkScanResults[i++] = hidlToFrameworkScanResult(scanResult);
+        }
+        return frameworkScanResults;
+    }
+
+    /**
+     * This just returns whether the scan was interrupted or not.
+     */
+    private static int hidlToFrameworkScanDataFlags(int flag) {
+        if (flag == StaScanDataFlagMask.INTERRUPTED) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+    private static WifiScanner.ScanData[] hidlToFrameworkScanDatas(
+            int cmdId, ArrayList<StaScanData> scanDatas) {
+        if (scanDatas == null || scanDatas.isEmpty()) return new WifiScanner.ScanData[0];
+        WifiScanner.ScanData[] frameworkScanDatas = new WifiScanner.ScanData[scanDatas.size()];
+        int i = 0;
+        for (StaScanData scanData : scanDatas) {
+            int flags = hidlToFrameworkScanDataFlags(scanData.flags);
+            ScanResult[] frameworkScanResults = hidlToFrameworkScanResults(scanData.results);
+            frameworkScanDatas[i++] =
+                    new WifiScanner.ScanData(cmdId, flags, scanData.bucketsScanned, false,
+                            frameworkScanResults);
+        }
+        return frameworkScanDatas;
+    }
+
+    /**
+     * Callback for events on the STA interface.
+     */
+    private class StaIfaceEventCallback extends IWifiStaIfaceEventCallback.Stub {
+        @Override
+        public void onBackgroundScanFailure(int cmdId) {
+            mVerboseLog.d("onBackgroundScanFailure " + cmdId);
+            WifiNative.ScanEventHandler eventHandler;
+            synchronized (sLock) {
+                if (mScan == null || cmdId != mScan.cmdId) return;
+                eventHandler = mScan.eventHandler;
+            }
+            eventHandler.onScanStatus(WifiNative.WIFI_SCAN_FAILED);
+        }
+
+        @Override
+        public void onBackgroundFullScanResult(
+                int cmdId, int bucketsScanned, StaScanResult result) {
+            mVerboseLog.d("onBackgroundFullScanResult " + cmdId);
+            WifiNative.ScanEventHandler eventHandler;
+            synchronized (sLock) {
+                if (mScan == null || cmdId != mScan.cmdId) return;
+                eventHandler = mScan.eventHandler;
+            }
+            eventHandler.onFullScanResult(hidlToFrameworkScanResult(result), bucketsScanned);
+        }
+
+        @Override
+        public void onBackgroundScanResults(int cmdId, ArrayList<StaScanData> scanDatas) {
+            mVerboseLog.d("onBackgroundScanResults " + cmdId);
+            WifiNative.ScanEventHandler eventHandler;
+            // WifiScanner currently uses the results callback to fetch the scan results.
+            // So, simulate that by sending out the notification and then caching the results
+            // locally. This will then be returned to WifiScanner via getScanResults.
+            synchronized (sLock) {
+                if (mScan == null || cmdId != mScan.cmdId) return;
+                eventHandler = mScan.eventHandler;
+                mScan.latestScanResults = hidlToFrameworkScanDatas(cmdId, scanDatas);
+            }
+            eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+        }
+
+        @Override
+        public void onRssiThresholdBreached(int cmdId, byte[/* 6 */] currBssid, int currRssi) {
+            mVerboseLog.d("onRssiThresholdBreached " + cmdId + "currRssi " + currRssi);
+            WifiNative.WifiRssiEventHandler eventHandler;
+            synchronized (sLock) {
+                if (mWifiRssiEventHandler == null || cmdId != sRssiMonCmdId) return;
+                eventHandler = mWifiRssiEventHandler;
+            }
+            eventHandler.onRssiThresholdBreached((byte) currRssi);
+        }
+    }
+
+    /**
+     * Callback for events on the STA interface.
+     */
+    private class ChipEventCallback extends IWifiChipEventCallback.Stub {
+        @Override
+        public void onChipReconfigured(int modeId) {
+            mVerboseLog.d("onChipReconfigured " + modeId);
+        }
+
+        @Override
+        public void onChipReconfigureFailure(WifiStatus status) {
+            mVerboseLog.d("onChipReconfigureFailure " + status);
+        }
+
+        public void onIfaceAdded(int type, String name) {
+            mVerboseLog.d("onIfaceAdded " + type + ", name: " + name);
+        }
+
+        @Override
+        public void onIfaceRemoved(int type, String name) {
+            mVerboseLog.d("onIfaceRemoved " + type + ", name: " + name);
+        }
+
+        @Override
+        public void onDebugRingBufferDataAvailable(
+                WifiDebugRingBufferStatus status, java.util.ArrayList<Byte> data) {
+            //TODO(b/35875078) Reinstate logging when execessive callbacks are fixed
+            // mVerboseLog.d("onDebugRingBufferDataAvailable " + status);
+            mHalEventHandler.post(() -> {
+                WifiNative.WifiLoggerEventHandler eventHandler;
+                synchronized (sLock) {
+                    if (mLogEventHandler == null || status == null || data == null) return;
+                    eventHandler = mLogEventHandler;
+                }
+                // Because |sLock| has been released, there is a chance that we'll execute
+                // a spurious callback (after someone has called resetLogHandler()).
+                //
+                // However, the alternative risks deadlock. Consider:
+                // [T1.1] WifiDiagnostics.captureBugReport()
+                // [T1.2] -- acquire WifiDiagnostics object's intrinsic lock
+                // [T1.3]    -> WifiVendorHal.getRingBufferData()
+                // [T1.4]       -- acquire WifiVendorHal.sLock
+                // [T2.1] <lambda>()
+                // [T2.2] -- acquire WifiVendorHal.sLock
+                // [T2.3]    -> WifiDiagnostics.onRingBufferData()
+                // [T2.4]       -- acquire WifiDiagnostics object's intrinsic lock
+                //
+                // The problem here is that the two threads acquire the locks in opposite order.
+                // If, for example, T2.2 executes between T1.2 and 1.4, then T1 and T2
+                // will be deadlocked.
+                eventHandler.onRingBufferData(
+                        ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+            });
+        }
+
+        @Override
+        public void onDebugErrorAlert(int errorCode, java.util.ArrayList<Byte> debugData) {
+            mVerboseLog.d("onDebugErrorAlert " + errorCode);
+            mHalEventHandler.post(() -> {
+                WifiNative.WifiLoggerEventHandler eventHandler;
+                synchronized (sLock) {
+                    if (mLogEventHandler == null || debugData == null) return;
+                    eventHandler = mLogEventHandler;
+                }
+                // See comment in onDebugRingBufferDataAvailable(), for an explanation
+                // of why this callback is invoked without |sLock| held.
+                eventHandler.onWifiAlert(
+                        errorCode, NativeUtil.byteArrayFromArrayList(debugData));
+            });
+        }
+    }
+
+    /**
+     * Hal Device Manager callbacks.
+     */
+    public class HalDeviceManagerStatusListener implements HalDeviceManager.ManagerStatusListener {
+        @Override
+        public void onStatusChanged() {
+            boolean isReady = mHalDeviceManager.isReady();
+            boolean isStarted = mHalDeviceManager.isStarted();
+
+            mVerboseLog.i("Device Manager onStatusChanged. isReady(): " + isReady
+                    + ", isStarted(): " + isStarted);
+            if (!isReady) {
+                // Probably something unpleasant, e.g. the server died
+                WifiNative.VendorHalDeathEventHandler handler;
+                synchronized (sLock) {
+                    clearState();
+                    handler = mDeathEventHandler;
+                }
+                if (handler != null) {
+                    handler.onDeath();
+                }
+            }
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WificondControl.java b/service/java/com/android/server/wifi/WificondControl.java
new file mode 100644
index 0000000..a877c09
--- /dev/null
+++ b/service/java/com/android/server/wifi/WificondControl.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.net.wifi.IApInterface;
+import android.net.wifi.IClientInterface;
+import android.net.wifi.IPnoScanEvent;
+import android.net.wifi.IScanEvent;
+import android.net.wifi.IWifiScannerImpl;
+import android.net.wifi.IWificond;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiSsid;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.util.InformationElementUtil;
+import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.wificond.ChannelSettings;
+import com.android.server.wifi.wificond.HiddenNetwork;
+import com.android.server.wifi.wificond.NativeScanResult;
+import com.android.server.wifi.wificond.PnoNetwork;
+import com.android.server.wifi.wificond.PnoSettings;
+import com.android.server.wifi.wificond.SingleScanSettings;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * This class provides methods for WifiNative to send control commands to wificond.
+ * NOTE: This class should only be used from WifiNative.
+ */
+public class WificondControl {
+    private boolean mVerboseLoggingEnabled = false;
+
+    private static final String TAG = "WificondControl";
+    private WifiInjector mWifiInjector;
+    private WifiMonitor mWifiMonitor;
+
+    // Cached wificond binder handlers.
+    private IWificond mWificond;
+    private IClientInterface mClientInterface;
+    private IApInterface mApInterface;
+    private IWifiScannerImpl mWificondScanner;
+    private IScanEvent mScanEventHandler;
+    private IPnoScanEvent mPnoScanEventHandler;
+
+    private String mClientInterfaceName;
+
+
+    private class ScanEventHandler extends IScanEvent.Stub {
+        @Override
+        public void OnScanResultReady() {
+            Log.d(TAG, "Scan result ready event");
+            mWifiMonitor.broadcastScanResultEvent(mClientInterfaceName);
+        }
+
+        @Override
+        public void OnScanFailed() {
+            Log.d(TAG, "Scan failed event");
+            mWifiMonitor.broadcastScanFailedEvent(mClientInterfaceName);
+        }
+    }
+
+    WificondControl(WifiInjector wifiInjector, WifiMonitor wifiMonitor) {
+        mWifiInjector = wifiInjector;
+        mWifiMonitor = wifiMonitor;
+    }
+
+    private class PnoScanEventHandler extends IPnoScanEvent.Stub {
+        @Override
+        public void OnPnoNetworkFound() {
+            Log.d(TAG, "Pno scan result event");
+            mWifiMonitor.broadcastPnoScanResultEvent(mClientInterfaceName);
+        }
+
+        @Override
+        public void OnPnoScanFailed() {
+            Log.d(TAG, "Pno Scan failed event");
+            // Nothing to do for now.
+        }
+    }
+
+    /** Enable or disable verbose logging of WificondControl.
+     *  @param enable True to enable verbose logging. False to disable verbose logging.
+     */
+    public void enableVerboseLogging(boolean enable) {
+        mVerboseLoggingEnabled = enable;
+    }
+
+    /**
+    * Setup driver for client mode via wificond.
+    * @return An IClientInterface as wificond client interface binder handler.
+    * Returns null on failure.
+    */
+    public IClientInterface setupDriverForClientMode() {
+        Log.d(TAG, "Setting up driver for client mode");
+        mWificond = mWifiInjector.makeWificond();
+        if (mWificond == null) {
+            Log.e(TAG, "Failed to get reference to wificond");
+            return null;
+        }
+
+        IClientInterface clientInterface = null;
+        try {
+            clientInterface = mWificond.createClientInterface();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to get IClientInterface due to remote exception");
+            return null;
+        }
+
+        if (clientInterface == null) {
+            Log.e(TAG, "Could not get IClientInterface instance from wificond");
+            return null;
+        }
+        Binder.allowBlocking(clientInterface.asBinder());
+
+        // Refresh Handlers
+        mClientInterface = clientInterface;
+        try {
+            mClientInterfaceName = clientInterface.getInterfaceName();
+            mWificondScanner = mClientInterface.getWifiScannerImpl();
+            if (mWificondScanner == null) {
+                Log.e(TAG, "Failed to get WificondScannerImpl");
+                return null;
+            }
+            Binder.allowBlocking(mWificondScanner.asBinder());
+            mScanEventHandler = new ScanEventHandler();
+            mWificondScanner.subscribeScanEvents(mScanEventHandler);
+            mPnoScanEventHandler = new PnoScanEventHandler();
+            mWificondScanner.subscribePnoScanEvents(mPnoScanEventHandler);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
+        }
+
+        return clientInterface;
+    }
+
+    /**
+    * Setup driver for softAp mode via wificond.
+    * @return An IApInterface as wificond Ap interface binder handler.
+    * Returns null on failure.
+    */
+    public IApInterface setupDriverForSoftApMode() {
+        Log.d(TAG, "Setting up driver for soft ap mode");
+        mWificond = mWifiInjector.makeWificond();
+        if (mWificond == null) {
+            Log.e(TAG, "Failed to get reference to wificond");
+            return null;
+        }
+
+        IApInterface apInterface = null;
+        try {
+            apInterface = mWificond.createApInterface();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to get IApInterface due to remote exception");
+            return null;
+        }
+
+        if (apInterface == null) {
+            Log.e(TAG, "Could not get IApInterface instance from wificond");
+            return null;
+        }
+        Binder.allowBlocking(apInterface.asBinder());
+
+        // Refresh Handlers
+        mApInterface = apInterface;
+
+        return apInterface;
+    }
+
+    /**
+    * Teardown all interfaces configured in wificond.
+    * @return Returns true on success.
+    */
+    public boolean tearDownInterfaces() {
+        Log.d(TAG, "tearing down interfaces in wificond");
+        // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
+        // could be used to cleanup before we setup any interfaces.
+        mWificond = mWifiInjector.makeWificond();
+        if (mWificond == null) {
+            Log.e(TAG, "Failed to get reference to wificond");
+            return false;
+        }
+
+        try {
+            if (mWificondScanner != null) {
+                mWificondScanner.unsubscribeScanEvents();
+                mWificondScanner.unsubscribePnoScanEvents();
+            }
+            mWificond.tearDownInterfaces();
+
+            // Refresh handlers
+            mClientInterface = null;
+            mWificondScanner = null;
+            mPnoScanEventHandler = null;
+            mScanEventHandler = null;
+            mApInterface = null;
+
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+        }
+
+        return false;
+    }
+
+    /**
+    * Disable wpa_supplicant via wificond.
+    * @return Returns true on success.
+    */
+    public boolean disableSupplicant() {
+        if (mClientInterface == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            return false;
+        }
+        try {
+            return mClientInterface.disableSupplicant();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to disable supplicant due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+    * Enable wpa_supplicant via wificond.
+    * @return Returns true on success.
+    */
+    public boolean enableSupplicant() {
+        if (mClientInterface == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            return false;
+        }
+
+        try {
+            return mClientInterface.enableSupplicant();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to enable supplicant due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+    * Request signal polling to wificond.
+    * Returns an SignalPollResult object.
+    * Returns null on failure.
+    */
+    public WifiNative.SignalPollResult signalPoll() {
+        if (mClientInterface == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            return null;
+        }
+
+        int[] resultArray;
+        try {
+            resultArray = mClientInterface.signalPoll();
+            if (resultArray == null || resultArray.length != 3) {
+                Log.e(TAG, "Invalid signal poll result from wificond");
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to do signal polling due to remote exception");
+            return null;
+        }
+        WifiNative.SignalPollResult pollResult = new WifiNative.SignalPollResult();
+        pollResult.currentRssi = resultArray[0];
+        pollResult.txBitrate = resultArray[1];
+        pollResult.associationFrequency = resultArray[2];
+        return pollResult;
+    }
+
+    /**
+    * Fetch TX packet counters on current connection from wificond.
+    * Returns an TxPacketCounters object.
+    * Returns null on failure.
+    */
+    public WifiNative.TxPacketCounters getTxPacketCounters() {
+        if (mClientInterface == null) {
+            Log.e(TAG, "No valid wificond client interface handler");
+            return null;
+        }
+
+        int[] resultArray;
+        try {
+            resultArray = mClientInterface.getPacketCounters();
+            if (resultArray == null || resultArray.length != 2) {
+                Log.e(TAG, "Invalid signal poll result from wificond");
+                return null;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to do signal polling due to remote exception");
+            return null;
+        }
+        WifiNative.TxPacketCounters counters = new WifiNative.TxPacketCounters();
+        counters.txSucceeded = resultArray[0];
+        counters.txFailed = resultArray[1];
+        return counters;
+    }
+
+    /**
+    * Fetch the latest scan result from kernel via wificond.
+    * @return Returns an ArrayList of ScanDetail.
+    * Returns an empty ArrayList on failure.
+    */
+    public ArrayList<ScanDetail> getScanResults() {
+        ArrayList<ScanDetail> results = new ArrayList<>();
+        if (mWificondScanner == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return results;
+        }
+        try {
+            NativeScanResult[] nativeResults = mWificondScanner.getScanResults();
+            for (NativeScanResult result : nativeResults) {
+                WifiSsid wifiSsid = WifiSsid.createFromByteArray(result.ssid);
+                String bssid;
+                try {
+                    bssid = NativeUtil.macAddressFromByteArray(result.bssid);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Illegal argument " + result.bssid, e);
+                    continue;
+                }
+                if (bssid == null) {
+                    Log.e(TAG, "Illegal null bssid");
+                    continue;
+                }
+                ScanResult.InformationElement[] ies =
+                        InformationElementUtil.parseInformationElements(result.infoElement);
+                InformationElementUtil.Capabilities capabilities =
+                        new InformationElementUtil.Capabilities();
+                capabilities.from(ies, result.capability);
+                String flags = capabilities.generateCapabilitiesString();
+                NetworkDetail networkDetail =
+                        new NetworkDetail(bssid, ies, null, result.frequency);
+
+                if (!wifiSsid.toString().equals(networkDetail.getTrimmedSSID())) {
+                    Log.e(TAG, "Inconsistent SSID on BSSID: " + bssid);
+                    continue;
+                }
+                ScanDetail scanDetail = new ScanDetail(networkDetail, wifiSsid, bssid, flags,
+                        result.signalMbm / 100, result.frequency, result.tsf, ies, null);
+                results.add(scanDetail);
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to create ScanDetail ArrayList");
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "get " + results.size() + " scan results from wificond");
+        }
+
+        return results;
+    }
+
+    /**
+     * Start a scan using wificond for the given parameters.
+     * @param freqs list of frequencies to scan for, if null scan all supported channels.
+     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
+     * @return Returns true on success.
+     */
+    public boolean scan(Set<Integer> freqs, Set<String> hiddenNetworkSSIDs) {
+        if (mWificondScanner == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return false;
+        }
+        SingleScanSettings settings = new SingleScanSettings();
+        settings.channelSettings  = new ArrayList<>();
+        settings.hiddenNetworks  = new ArrayList<>();
+
+        if (freqs != null) {
+            for (Integer freq : freqs) {
+                ChannelSettings channel = new ChannelSettings();
+                channel.frequency = freq;
+                settings.channelSettings.add(channel);
+            }
+        }
+        if (hiddenNetworkSSIDs != null) {
+            for (String ssid : hiddenNetworkSSIDs) {
+                HiddenNetwork network = new HiddenNetwork();
+                try {
+                    network.ssid = NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(ssid));
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Illegal argument " + ssid, e);
+                    continue;
+                }
+                settings.hiddenNetworks.add(network);
+            }
+        }
+
+        try {
+            return mWificondScanner.scan(settings);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Start PNO scan.
+     * @param pnoSettings Pno scan configuration.
+     * @return true on success.
+     */
+    public boolean startPnoScan(WifiNative.PnoSettings pnoSettings) {
+        if (mWificondScanner == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return false;
+        }
+        PnoSettings settings = new PnoSettings();
+        settings.pnoNetworks  = new ArrayList<>();
+        settings.intervalMs = pnoSettings.periodInMs;
+        settings.min2gRssi = pnoSettings.min24GHzRssi;
+        settings.min5gRssi = pnoSettings.min5GHzRssi;
+        if (pnoSettings.networkList != null) {
+            for (WifiNative.PnoNetwork network : pnoSettings.networkList) {
+                PnoNetwork condNetwork = new PnoNetwork();
+                condNetwork.isHidden = (network.flags
+                        & WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN) != 0;
+                try {
+                    condNetwork.ssid =
+                            NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(network.ssid));
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Illegal argument " + network.ssid, e);
+                    continue;
+                }
+                settings.pnoNetworks.add(condNetwork);
+            }
+        }
+
+        try {
+            return mWificondScanner.startPnoScan(settings);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to stop pno scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Stop PNO scan.
+     * @return true on success.
+     */
+    public boolean stopPnoScan() {
+        if (mWificondScanner == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return false;
+        }
+        try {
+            return mWificondScanner.stopPnoScan();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to stop pno scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Abort ongoing single scan.
+     */
+    public void abortScan() {
+        if (mWificondScanner == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return;
+        }
+        try {
+            mWificondScanner.abortScan();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request abortScan due to remote exception");
+        }
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/WnmData.java b/service/java/com/android/server/wifi/WnmData.java
deleted file mode 100644
index 861f6dd..0000000
--- a/service/java/com/android/server/wifi/WnmData.java
+++ /dev/null
@@ -1,112 +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 java.io.IOException;
-
-/**
- * This class carries the payload of a Hotspot 2.0 Wireless Network Management (WNM) frame,
- * described in the Hotspot 2.0 spec, section 3.2.
- */
-public class WnmData {
-    private static final int ESS = 1;   // HS2.0 spec section 3.2.1.2, table 4
-
-    private final long mBssid;
-    private final String mUrl;
-    private final boolean mDeauthEvent;
-    private final int mMethod;
-    private final boolean mEss;
-    private final int mDelay;
-
-    public static WnmData buildWnmData(String event) throws IOException {
-        // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url
-        // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url
-
-        String[] segments = event.split(" ");
-        if (segments.length < 2) {
-            throw new IOException("Short event");
-        }
-
-        switch (segments[1]) {
-            case WifiMonitor.HS20_SUB_REM_STR: {
-                if (segments.length != 4) {
-                    throw new IOException("Expected 4 segments");
-                }
-                return new WnmData(Long.parseLong(segments[0], 16),
-                        segments[3],
-                        Integer.parseInt(segments[2]));
-            }
-            case WifiMonitor.HS20_DEAUTH_STR: {
-                if (segments.length != 5) {
-                    throw new IOException("Expected 5 segments");
-                }
-                int codeID = Integer.parseInt(segments[2]);
-                if (codeID < 0 || codeID > ESS) {
-                    throw new IOException("Unknown code");
-                }
-                return new WnmData(Long.parseLong(segments[0], 16),
-                        segments[4],
-                        codeID == ESS,
-                        Integer.parseInt(segments[3]));
-            }
-            default:
-                throw new IOException("Unknown event type");
-        }
-    }
-
-    private WnmData(long bssid, String url, int method) {
-        mBssid = bssid;
-        mUrl = url;
-        mMethod = method;
-        mEss = false;
-        mDelay = -1;
-        mDeauthEvent = false;
-    }
-
-    private WnmData(long bssid, String url, boolean ess, int delay) {
-        mBssid = bssid;
-        mUrl = url;
-        mEss = ess;
-        mDelay = delay;
-        mMethod = -1;
-        mDeauthEvent = true;
-    }
-
-    public long getBssid() {
-        return mBssid;
-    }
-
-    public String getUrl() {
-        return mUrl;
-    }
-
-    public boolean isDeauthEvent() {
-        return mDeauthEvent;
-    }
-
-    public int getMethod() {
-        return mMethod;
-    }
-
-    public boolean isEss() {
-        return mEss;
-    }
-
-    public int getDelay() {
-        return mDelay;
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/ANQPFactory.java b/service/java/com/android/server/wifi/anqp/ANQPFactory.java
deleted file mode 100644
index 04cab55..0000000
--- a/service/java/com/android/server/wifi/anqp/ANQPFactory.java
+++ /dev/null
@@ -1,292 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import com.android.server.wifi.hotspot2.NetworkDetail;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
-
-/**
- * Factory to build a collection of 802.11u ANQP elements from a byte buffer.
- */
-public class ANQPFactory {
-
-    private static final List<Constants.ANQPElementType> BaseANQPSet1 = Arrays.asList(
-            Constants.ANQPElementType.ANQPVenueName,
-            Constants.ANQPElementType.ANQPNwkAuthType,
-            Constants.ANQPElementType.ANQPRoamingConsortium,
-            Constants.ANQPElementType.ANQPIPAddrAvailability,
-            Constants.ANQPElementType.ANQPNAIRealm,
-            Constants.ANQPElementType.ANQP3GPPNetwork,
-            Constants.ANQPElementType.ANQPDomName);
-
-    private static final List<Constants.ANQPElementType> BaseANQPSet2 = Arrays.asList(
-            Constants.ANQPElementType.ANQPVenueName,
-            Constants.ANQPElementType.ANQPNwkAuthType,
-            Constants.ANQPElementType.ANQPIPAddrAvailability,
-            Constants.ANQPElementType.ANQPNAIRealm,
-            Constants.ANQPElementType.ANQP3GPPNetwork,
-            Constants.ANQPElementType.ANQPDomName);
-
-    private static final List<Constants.ANQPElementType> HS20ANQPSet = Arrays.asList(
-            Constants.ANQPElementType.HSFriendlyName,
-            Constants.ANQPElementType.HSWANMetrics,
-            Constants.ANQPElementType.HSConnCapability);
-
-    private static final List<Constants.ANQPElementType> HS20ANQPSetwOSU = Arrays.asList(
-            Constants.ANQPElementType.HSFriendlyName,
-            Constants.ANQPElementType.HSWANMetrics,
-            Constants.ANQPElementType.HSConnCapability,
-            Constants.ANQPElementType.HSOSUProviders);
-
-    public static List<Constants.ANQPElementType> getBaseANQPSet(boolean includeRC) {
-        return includeRC ? BaseANQPSet1 : BaseANQPSet2;
-    }
-
-    public static List<Constants.ANQPElementType> getHS20ANQPSet(boolean includeOSU) {
-        return includeOSU ? HS20ANQPSetwOSU : HS20ANQPSet;
-    }
-
-    public static List<Constants.ANQPElementType> buildQueryList(NetworkDetail networkDetail,
-                                               boolean matchSet, boolean osu) {
-        List<Constants.ANQPElementType> querySet = new ArrayList<>();
-
-        if (matchSet) {
-            querySet.addAll(getBaseANQPSet(networkDetail.getAnqpOICount() > 0));
-        }
-
-        if (networkDetail.getHSRelease() != null) {
-            boolean includeOSU = osu && networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2;
-            if (matchSet) {
-                querySet.addAll(getHS20ANQPSet(includeOSU));
-            }
-            else if (includeOSU) {
-                querySet.add(Constants.ANQPElementType.HSOSUProviders);
-            }
-        }
-
-        return querySet;
-    }
-
-    public static ByteBuffer buildQueryRequest(Set<Constants.ANQPElementType> elements,
-                                               ByteBuffer target) {
-        List<Constants.ANQPElementType> list = new ArrayList<Constants.ANQPElementType>(elements);
-        Collections.sort(list);
-
-        ListIterator<Constants.ANQPElementType> elementIterator = list.listIterator();
-
-        target.order(ByteOrder.LITTLE_ENDIAN);
-        target.putShort((short) Constants.ANQP_QUERY_LIST);
-        int lenPos = target.position();
-        target.putShort((short) 0);
-
-        while (elementIterator.hasNext()) {
-            Integer id = Constants.getANQPElementID(elementIterator.next());
-            if (id != null) {
-                target.putShort(id.shortValue());
-            } else {
-                elementIterator.previous();
-                break;
-            }
-        }
-        target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
-
-        // Start a new vendor specific element for HS2.0 elements:
-        if (elementIterator.hasNext()) {
-            target.putShort((short) Constants.ANQP_VENDOR_SPEC);
-            int vsLenPos = target.position();
-            target.putShort((short) 0);
-
-            target.putInt(Constants.HS20_PREFIX);
-            target.put((byte) Constants.HS_QUERY_LIST);
-            target.put((byte) 0);
-
-            while (elementIterator.hasNext()) {
-                Constants.ANQPElementType elementType = elementIterator.next();
-                Integer id = Constants.getHS20ElementID(elementType);
-                if (id == null) {
-                    throw new RuntimeException("Unmapped ANQPElementType: " + elementType);
-                } else {
-                    target.put(id.byteValue());
-                }
-            }
-            target.putShort(vsLenPos,
-                    (short) (target.position() - vsLenPos - Constants.BYTES_IN_SHORT));
-        }
-
-        target.flip();
-        return target;
-    }
-
-    public static ByteBuffer buildHomeRealmRequest(List<String> realmNames, ByteBuffer target) {
-        target.order(ByteOrder.LITTLE_ENDIAN);
-        target.putShort((short) Constants.ANQP_VENDOR_SPEC);
-        int lenPos = target.position();
-        target.putShort((short) 0);
-
-        target.putInt(Constants.HS20_PREFIX);
-        target.put((byte) Constants.HS_NAI_HOME_REALM_QUERY);
-        target.put((byte) 0);
-
-        target.put((byte) realmNames.size());
-        for (String realmName : realmNames) {
-            target.put((byte) Constants.UTF8_INDICATOR);
-            byte[] octets = realmName.getBytes(StandardCharsets.UTF_8);
-            target.put((byte) octets.length);
-            target.put(octets);
-        }
-        target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
-
-        target.flip();
-        return target;
-    }
-
-    public static ByteBuffer buildIconRequest(String fileName, ByteBuffer target) {
-        target.order(ByteOrder.LITTLE_ENDIAN);
-        target.putShort((short) Constants.ANQP_VENDOR_SPEC);
-        int lenPos = target.position();
-        target.putShort((short) 0);
-
-        target.putInt(Constants.HS20_PREFIX);
-        target.put((byte) Constants.HS_ICON_REQUEST);
-        target.put((byte) 0);
-
-        target.put(fileName.getBytes(StandardCharsets.UTF_8));
-        target.putShort(lenPos, (short) (target.position() - lenPos - Constants.BYTES_IN_SHORT));
-
-        target.flip();
-        return target;
-    }
-
-    public static List<ANQPElement> parsePayload(ByteBuffer payload) throws ProtocolException {
-        payload.order(ByteOrder.LITTLE_ENDIAN);
-        List<ANQPElement> elements = new ArrayList<ANQPElement>();
-        while (payload.hasRemaining()) {
-            elements.add(buildElement(payload));
-        }
-        return elements;
-    }
-
-    private static ANQPElement buildElement(ByteBuffer payload) throws ProtocolException {
-        if (payload.remaining() < 4)
-            throw new ProtocolException("Runt payload: " + payload.remaining());
-
-        int infoIDNumber = payload.getShort() & Constants.SHORT_MASK;
-        Constants.ANQPElementType infoID = Constants.mapANQPElement(infoIDNumber);
-        if (infoID == null) {
-            throw new ProtocolException("Bad info ID: " + infoIDNumber);
-        }
-        int length = payload.getShort() & Constants.SHORT_MASK;
-
-        if (payload.remaining() < length) {
-            throw new ProtocolException("Truncated payload: " +
-                    payload.remaining() + " vs " + length);
-        }
-        return buildElement(payload, infoID, length);
-    }
-
-    public static ANQPElement buildElement(ByteBuffer payload, Constants.ANQPElementType infoID,
-                                            int length) throws ProtocolException {
-        try {
-            ByteBuffer elementPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
-            payload.position(payload.position() + length);
-            elementPayload.limit(elementPayload.position() + length);
-
-            switch (infoID) {
-                case ANQPCapabilityList:
-                    return new CapabilityListElement(infoID, elementPayload);
-                case ANQPVenueName:
-                    return new VenueNameElement(infoID, elementPayload);
-                case ANQPEmergencyNumber:
-                    return new EmergencyNumberElement(infoID, elementPayload);
-                case ANQPNwkAuthType:
-                    return new NetworkAuthenticationTypeElement(infoID, elementPayload);
-                case ANQPRoamingConsortium:
-                    return new RoamingConsortiumElement(infoID, elementPayload);
-                case ANQPIPAddrAvailability:
-                    return new IPAddressTypeAvailabilityElement(infoID, elementPayload);
-                case ANQPNAIRealm:
-                    return new NAIRealmElement(infoID, elementPayload);
-                case ANQP3GPPNetwork:
-                    return new ThreeGPPNetworkElement(infoID, elementPayload);
-                case ANQPGeoLoc:
-                    return new GEOLocationElement(infoID, elementPayload);
-                case ANQPCivicLoc:
-                    return new CivicLocationElement(infoID, elementPayload);
-                case ANQPLocURI:
-                    return new GenericStringElement(infoID, elementPayload);
-                case ANQPDomName:
-                    return new DomainNameElement(infoID, elementPayload);
-                case ANQPEmergencyAlert:
-                    return new GenericStringElement(infoID, elementPayload);
-                case ANQPTDLSCap:
-                    return new GenericBlobElement(infoID, elementPayload);
-                case ANQPEmergencyNAI:
-                    return new GenericStringElement(infoID, elementPayload);
-                case ANQPNeighborReport:
-                    return new GenericBlobElement(infoID, elementPayload);
-                case ANQPVendorSpec:
-                    if (elementPayload.remaining() > 5) {
-                        int oi = elementPayload.getInt();
-                        if (oi != Constants.HS20_PREFIX) {
-                            return null;
-                        }
-                        int subType = elementPayload.get() & Constants.BYTE_MASK;
-                        Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
-                        if (hs20ID == null) {
-                            throw new ProtocolException("Bad HS20 info ID: " + subType);
-                        }
-                        elementPayload.get();   // Skip the reserved octet
-                        return buildHS20Element(hs20ID, elementPayload);
-                    } else {
-                        return new GenericBlobElement(infoID, elementPayload);
-                    }
-                default:
-                    throw new ProtocolException("Unknown element ID: " + infoID);
-            }
-        } catch (ProtocolException e) {
-            throw e;
-        } catch (Exception e) {
-            // TODO: remove this catch-all for exceptions, once the element parsing code
-            // has been thoroughly unit tested. b/30562650
-            throw new ProtocolException("Unknown parsing error", e);
-        }
-    }
-
-    public static ANQPElement buildHS20Element(Constants.ANQPElementType infoID,
-                                                ByteBuffer payload) throws ProtocolException {
-        try {
-            switch (infoID) {
-                case HSCapabilityList:
-                    return new HSCapabilityListElement(infoID, payload);
-                case HSFriendlyName:
-                    return new HSFriendlyNameElement(infoID, payload);
-                case HSWANMetrics:
-                    return new HSWanMetricsElement(infoID, payload);
-                case HSConnCapability:
-                    return new HSConnectionCapabilityElement(infoID, payload);
-                case HSOperatingclass:
-                    return new GenericBlobElement(infoID, payload);
-                case HSOSUProviders:
-                    return new RawByteElement(infoID, payload);
-                case HSIconFile:
-                    return new HSIconFileElement(infoID, payload);
-                default:
-                    return null;
-            }
-        } catch (ProtocolException e) {
-            throw e;
-        } catch (Exception e) {
-            // TODO: remove this catch-all for exceptions, once the element parsing code
-            // has been thoroughly unit tested. b/30562650
-            throw new ProtocolException("Unknown parsing error", e);
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/CapabilityListElement.java b/service/java/com/android/server/wifi/anqp/CapabilityListElement.java
deleted file mode 100644
index 301d417..0000000
--- a/service/java/com/android/server/wifi/anqp/CapabilityListElement.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-/**
- * The ANQP Capability List element, 802.11-2012 section 8.4.4.3
- */
-public class CapabilityListElement extends ANQPElement {
-    private final Constants.ANQPElementType[] mCapabilities;
-
-    public CapabilityListElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-        if ((payload.remaining() & 1) == 1)
-            throw new ProtocolException("Odd length");
-        mCapabilities = new Constants.ANQPElementType[payload.remaining() / Constants.BYTES_IN_SHORT];
-
-        int index = 0;
-        while (payload.hasRemaining()) {
-            int capID = payload.getShort() & Constants.SHORT_MASK;
-            Constants.ANQPElementType capability = Constants.mapANQPElement(capID);
-            if (capability == null)
-                throw new ProtocolException("Unknown capability: " + capID);
-            mCapabilities[index++] = capability;
-        }
-    }
-
-    public Constants.ANQPElementType[] getCapabilities() {
-        return mCapabilities;
-    }
-
-    @Override
-    public String toString() {
-        return "CapabilityList{" +
-                "mCapabilities=" + Arrays.toString(mCapabilities) +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/CellularNetwork.java b/service/java/com/android/server/wifi/anqp/CellularNetwork.java
deleted file mode 100644
index 03d607e..0000000
--- a/service/java/com/android/server/wifi/anqp/CellularNetwork.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-
-public class CellularNetwork implements Iterable<String> {
-    private static final int PLMNListType = 0;
-
-    private final List<String> mMccMnc;
-
-    private CellularNetwork(int plmnCount, ByteBuffer payload) throws ProtocolException {
-        mMccMnc = new ArrayList<>(plmnCount);
-
-        while (plmnCount > 0) {
-            if (payload.remaining() < 3) {
-                throw new ProtocolException("Truncated PLMN info");
-            }
-            byte[] plmn = new byte[3];
-            payload.get(plmn);
-
-            int mcc = ((plmn[0] << 8) & 0xf00) |
-                    (plmn[0] & 0x0f0) |
-                    (plmn[1] & 0x00f);
-
-            int mnc = ((plmn[2] << 4) & 0xf0) |
-                    ((plmn[2] >> 4) & 0x0f);
-
-            int n2 = (plmn[1] >> 4) & 0x0f;
-            String mccMnc = n2 != 0xf ?
-                    String.format("%03x%03x", mcc, (mnc << 4) | n2) :
-                    String.format("%03x%02x", mcc, mnc);
-
-            mMccMnc.add(mccMnc);
-            plmnCount--;
-        }
-    }
-
-    public static CellularNetwork buildCellularNetwork(ByteBuffer payload)
-            throws ProtocolException {
-        int iei = payload.get() & BYTE_MASK;
-        int plmnLen = payload.get() & 0x7f;
-
-        if (iei != PLMNListType) {
-            payload.position(payload.position() + plmnLen);
-            return null;
-        }
-
-        int plmnCount = payload.get() & BYTE_MASK;
-        return new CellularNetwork(plmnCount, payload);
-    }
-
-    @Override
-    public Iterator<String> iterator() {
-        return mMccMnc.iterator();
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder("PLMN:");
-        for (String mccMnc : mMccMnc) {
-            sb.append(' ').append(mccMnc);
-        }
-        return sb.toString();
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/CivicLocationElement.java b/service/java/com/android/server/wifi/anqp/CivicLocationElement.java
deleted file mode 100644
index 7269336..0000000
--- a/service/java/com/android/server/wifi/anqp/CivicLocationElement.java
+++ /dev/null
@@ -1,199 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * The Civic Location ANQP Element, IEEE802.11-2012 section 8.4.4.13
- */
-public class CivicLocationElement extends ANQPElement {
-    public enum LocationType {DHCPServer, NwkElement, Client}
-
-    private static final int GEOCONF_CIVIC4 = 99;
-    private static final int RFC4776 = 0;       // Table 8-77, 1=vendor specific
-
-    private final LocationType mLocationType;
-    private final Locale mLocale;
-    private final Map<CAType, String> mValues;
-
-    public CivicLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        if (payload.remaining() < 6) {
-            throw new ProtocolException("Runt civic location:" + payload.remaining());
-        }
-
-        int locType = payload.get() & Constants.BYTE_MASK;
-        if (locType != RFC4776) {
-            throw new ProtocolException("Bad Civic location type: " + locType);
-        }
-
-        int locSubType = payload.get() & Constants.BYTE_MASK;
-        if (locSubType != GEOCONF_CIVIC4) {
-            throw new ProtocolException("Unexpected Civic location sub-type: " + locSubType +
-                    " (cannot handle sub elements)");
-        }
-
-        int length = payload.get() & Constants.BYTE_MASK;
-        if (length > payload.remaining()) {
-            throw new ProtocolException("Invalid CA type length: " + length);
-        }
-
-        int what = payload.get() & Constants.BYTE_MASK;
-        mLocationType = what < LocationType.values().length ? LocationType.values()[what] : null;
-
-        mLocale = Locale.forLanguageTag(Constants.getString(payload, 2, StandardCharsets.US_ASCII));
-
-        mValues = new HashMap<CAType, String>();
-        while (payload.hasRemaining()) {
-            int caTypeNumber = payload.get() & Constants.BYTE_MASK;
-            CAType caType = s_caTypes.get(caTypeNumber);
-
-            int caValLen = payload.get() & Constants.BYTE_MASK;
-            if (caValLen > payload.remaining()) {
-                throw new ProtocolException("Bad CA value length: " + caValLen);
-            }
-            byte[] caValOctets = new byte[caValLen];
-            payload.get(caValOctets);
-
-            if (caType != null) {
-                mValues.put(caType, new String(caValOctets, StandardCharsets.UTF_8));
-            }
-        }
-    }
-
-    public LocationType getLocationType() {
-        return mLocationType;
-    }
-
-    public Locale getLocale() {
-        return mLocale;
-    }
-
-    public Map<CAType, String> getValues() {
-        return Collections.unmodifiableMap(mValues);
-    }
-
-    @Override
-    public String toString() {
-        return "CivicLocation{" +
-                "mLocationType=" + mLocationType +
-                ", mLocale=" + mLocale +
-                ", mValues=" + mValues +
-                '}';
-    }
-
-    private static final Map<Integer, CAType> s_caTypes = new HashMap<Integer, CAType>();
-
-    public static final int LANGUAGE = 0;
-    public static final int STATE_PROVINCE = 1;
-    public static final int COUNTY_DISTRICT = 2;
-    public static final int CITY = 3;
-    public static final int DIVISION_BOROUGH = 4;
-    public static final int BLOCK = 5;
-    public static final int STREET_GROUP = 6;
-    public static final int STREET_DIRECTION = 16;
-    public static final int LEADING_STREET_SUFFIX = 17;
-    public static final int STREET_SUFFIX = 18;
-    public static final int HOUSE_NUMBER = 19;
-    public static final int HOUSE_NUMBER_SUFFIX = 20;
-    public static final int LANDMARK = 21;
-    public static final int ADDITIONAL_LOCATION = 22;
-    public static final int NAME = 23;
-    public static final int POSTAL_ZIP = 24;
-    public static final int BUILDING = 25;
-    public static final int UNIT = 26;
-    public static final int FLOOR = 27;
-    public static final int ROOM = 28;
-    public static final int TYPE = 29;
-    public static final int POSTAL_COMMUNITY = 30;
-    public static final int PO_BOX = 31;
-    public static final int ADDITIONAL_CODE = 32;
-    public static final int SEAT_DESK = 33;
-    public static final int PRIMARY_ROAD = 34;
-    public static final int ROAD_SECTION = 35;
-    public static final int BRANCH_ROAD = 36;
-    public static final int SUB_BRANCH_ROAD = 37;
-    public static final int STREET_NAME_PRE_MOD = 38;
-    public static final int STREET_NAME_POST_MOD = 39;
-    public static final int SCRIPT = 128;
-    public static final int RESERVED = 255;
-
-    public enum CAType {
-        Language,
-        StateProvince,
-        CountyDistrict,
-        City,
-        DivisionBorough,
-        Block,
-        StreetGroup,
-        StreetDirection,
-        LeadingStreetSuffix,
-        StreetSuffix,
-        HouseNumber,
-        HouseNumberSuffix,
-        Landmark,
-        AdditionalLocation,
-        Name,
-        PostalZIP,
-        Building,
-        Unit,
-        Floor,
-        Room,
-        Type,
-        PostalCommunity,
-        POBox,
-        AdditionalCode,
-        SeatDesk,
-        PrimaryRoad,
-        RoadSection,
-        BranchRoad,
-        SubBranchRoad,
-        StreetNamePreMod,
-        StreetNamePostMod,
-        Script,
-        Reserved
-    }
-
-    static {
-        s_caTypes.put(LANGUAGE, CAType.Language);
-        s_caTypes.put(STATE_PROVINCE, CAType.StateProvince);
-        s_caTypes.put(COUNTY_DISTRICT, CAType.CountyDistrict);
-        s_caTypes.put(CITY, CAType.City);
-        s_caTypes.put(DIVISION_BOROUGH, CAType.DivisionBorough);
-        s_caTypes.put(BLOCK, CAType.Block);
-        s_caTypes.put(STREET_GROUP, CAType.StreetGroup);
-        s_caTypes.put(STREET_DIRECTION, CAType.StreetDirection);
-        s_caTypes.put(LEADING_STREET_SUFFIX, CAType.LeadingStreetSuffix);
-        s_caTypes.put(STREET_SUFFIX, CAType.StreetSuffix);
-        s_caTypes.put(HOUSE_NUMBER, CAType.HouseNumber);
-        s_caTypes.put(HOUSE_NUMBER_SUFFIX, CAType.HouseNumberSuffix);
-        s_caTypes.put(LANDMARK, CAType.Landmark);
-        s_caTypes.put(ADDITIONAL_LOCATION, CAType.AdditionalLocation);
-        s_caTypes.put(NAME, CAType.Name);
-        s_caTypes.put(POSTAL_ZIP, CAType.PostalZIP);
-        s_caTypes.put(BUILDING, CAType.Building);
-        s_caTypes.put(UNIT, CAType.Unit);
-        s_caTypes.put(FLOOR, CAType.Floor);
-        s_caTypes.put(ROOM, CAType.Room);
-        s_caTypes.put(TYPE, CAType.Type);
-        s_caTypes.put(POSTAL_COMMUNITY, CAType.PostalCommunity);
-        s_caTypes.put(PO_BOX, CAType.POBox);
-        s_caTypes.put(ADDITIONAL_CODE, CAType.AdditionalCode);
-        s_caTypes.put(SEAT_DESK, CAType.SeatDesk);
-        s_caTypes.put(PRIMARY_ROAD, CAType.PrimaryRoad);
-        s_caTypes.put(ROAD_SECTION, CAType.RoadSection);
-        s_caTypes.put(BRANCH_ROAD, CAType.BranchRoad);
-        s_caTypes.put(SUB_BRANCH_ROAD, CAType.SubBranchRoad);
-        s_caTypes.put(STREET_NAME_PRE_MOD, CAType.StreetNamePreMod);
-        s_caTypes.put(STREET_NAME_POST_MOD, CAType.StreetNamePostMod);
-        s_caTypes.put(SCRIPT, CAType.Script);
-        s_caTypes.put(RESERVED, CAType.Reserved);
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/Constants.java b/service/java/com/android/server/wifi/anqp/Constants.java
deleted file mode 100644
index c4a10cd..0000000
--- a/service/java/com/android/server/wifi/anqp/Constants.java
+++ /dev/null
@@ -1,233 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-import java.util.Collection;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * ANQP related constants (802.11-2012)
- */
-public class Constants {
-
-    public static final int NIBBLE_MASK = 0x0f;
-    public static final int BYTE_MASK = 0xff;
-    public static final int SHORT_MASK = 0xffff;
-    public static final long INT_MASK = 0xffffffffL;
-    public static final int BYTES_IN_SHORT = 2;
-    public static final int BYTES_IN_INT = 4;
-    public static final int BYTES_IN_EUI48 = 6;
-    public static final long MILLIS_IN_A_SEC = 1000L;
-
-    public static final int HS20_PREFIX = 0x119a6f50;   // Note that this is represented as a LE int
-    public static final int HS20_FRAME_PREFIX = 0x109a6f50;
-    public static final int UTF8_INDICATOR = 1;
-
-    public static final int LANG_CODE_LENGTH = 3;
-
-    public static final int ANQP_QUERY_LIST = 256;
-    public static final int ANQP_CAPABILITY_LIST = 257;
-    public static final int ANQP_VENUE_NAME = 258;
-    public static final int ANQP_EMERGENCY_NUMBER = 259;
-    public static final int ANQP_NWK_AUTH_TYPE = 260;
-    public static final int ANQP_ROAMING_CONSORTIUM = 261;
-    public static final int ANQP_IP_ADDR_AVAILABILITY = 262;
-    public static final int ANQP_NAI_REALM = 263;
-    public static final int ANQP_3GPP_NETWORK = 264;
-    public static final int ANQP_GEO_LOC = 265;
-    public static final int ANQP_CIVIC_LOC = 266;
-    public static final int ANQP_LOC_URI = 267;
-    public static final int ANQP_DOM_NAME = 268;
-    public static final int ANQP_EMERGENCY_ALERT = 269;
-    public static final int ANQP_TDLS_CAP = 270;
-    public static final int ANQP_EMERGENCY_NAI = 271;
-    public static final int ANQP_NEIGHBOR_REPORT = 272;
-    public static final int ANQP_VENDOR_SPEC = 56797;
-
-    public static final int HS_QUERY_LIST = 1;
-    public static final int HS_CAPABILITY_LIST = 2;
-    public static final int HS_FRIENDLY_NAME = 3;
-    public static final int HS_WAN_METRICS = 4;
-    public static final int HS_CONN_CAPABILITY = 5;
-    public static final int HS_NAI_HOME_REALM_QUERY = 6;
-    public static final int HS_OPERATING_CLASS = 7;
-    public static final int HS_OSU_PROVIDERS = 8;
-    public static final int HS_ICON_REQUEST = 10;
-    public static final int HS_ICON_FILE = 11;
-
-    public enum ANQPElementType {
-        ANQPQueryList,
-        ANQPCapabilityList,
-        ANQPVenueName,
-        ANQPEmergencyNumber,
-        ANQPNwkAuthType,
-        ANQPRoamingConsortium,
-        ANQPIPAddrAvailability,
-        ANQPNAIRealm,
-        ANQP3GPPNetwork,
-        ANQPGeoLoc,
-        ANQPCivicLoc,
-        ANQPLocURI,
-        ANQPDomName,
-        ANQPEmergencyAlert,
-        ANQPTDLSCap,
-        ANQPEmergencyNAI,
-        ANQPNeighborReport,
-        ANQPVendorSpec,
-        HSQueryList,
-        HSCapabilityList,
-        HSFriendlyName,
-        HSWANMetrics,
-        HSConnCapability,
-        HSNAIHomeRealmQuery,
-        HSOperatingclass,
-        HSOSUProviders,
-        HSIconRequest,
-        HSIconFile
-    }
-
-    private static final Map<Integer, ANQPElementType> sAnqpMap = new HashMap<>();
-    private static final Map<Integer, ANQPElementType> sHs20Map = new HashMap<>();
-    private static final Map<ANQPElementType, Integer> sRevAnqpmap =
-            new EnumMap<>(ANQPElementType.class);
-    private static final Map<ANQPElementType, Integer> sRevHs20map =
-            new EnumMap<>(ANQPElementType.class);
-
-    static {
-        sAnqpMap.put(ANQP_QUERY_LIST, ANQPElementType.ANQPQueryList);
-        sAnqpMap.put(ANQP_CAPABILITY_LIST, ANQPElementType.ANQPCapabilityList);
-        sAnqpMap.put(ANQP_VENUE_NAME, ANQPElementType.ANQPVenueName);
-        sAnqpMap.put(ANQP_EMERGENCY_NUMBER, ANQPElementType.ANQPEmergencyNumber);
-        sAnqpMap.put(ANQP_NWK_AUTH_TYPE, ANQPElementType.ANQPNwkAuthType);
-        sAnqpMap.put(ANQP_ROAMING_CONSORTIUM, ANQPElementType.ANQPRoamingConsortium);
-        sAnqpMap.put(ANQP_IP_ADDR_AVAILABILITY, ANQPElementType.ANQPIPAddrAvailability);
-        sAnqpMap.put(ANQP_NAI_REALM, ANQPElementType.ANQPNAIRealm);
-        sAnqpMap.put(ANQP_3GPP_NETWORK, ANQPElementType.ANQP3GPPNetwork);
-        sAnqpMap.put(ANQP_GEO_LOC, ANQPElementType.ANQPGeoLoc);
-        sAnqpMap.put(ANQP_CIVIC_LOC, ANQPElementType.ANQPCivicLoc);
-        sAnqpMap.put(ANQP_LOC_URI, ANQPElementType.ANQPLocURI);
-        sAnqpMap.put(ANQP_DOM_NAME, ANQPElementType.ANQPDomName);
-        sAnqpMap.put(ANQP_EMERGENCY_ALERT, ANQPElementType.ANQPEmergencyAlert);
-        sAnqpMap.put(ANQP_TDLS_CAP, ANQPElementType.ANQPTDLSCap);
-        sAnqpMap.put(ANQP_EMERGENCY_NAI, ANQPElementType.ANQPEmergencyNAI);
-        sAnqpMap.put(ANQP_NEIGHBOR_REPORT, ANQPElementType.ANQPNeighborReport);
-        sAnqpMap.put(ANQP_VENDOR_SPEC, ANQPElementType.ANQPVendorSpec);
-
-        sHs20Map.put(HS_QUERY_LIST, ANQPElementType.HSQueryList);
-        sHs20Map.put(HS_CAPABILITY_LIST, ANQPElementType.HSCapabilityList);
-        sHs20Map.put(HS_FRIENDLY_NAME, ANQPElementType.HSFriendlyName);
-        sHs20Map.put(HS_WAN_METRICS, ANQPElementType.HSWANMetrics);
-        sHs20Map.put(HS_CONN_CAPABILITY, ANQPElementType.HSConnCapability);
-        sHs20Map.put(HS_NAI_HOME_REALM_QUERY, ANQPElementType.HSNAIHomeRealmQuery);
-        sHs20Map.put(HS_OPERATING_CLASS, ANQPElementType.HSOperatingclass);
-        sHs20Map.put(HS_OSU_PROVIDERS, ANQPElementType.HSOSUProviders);
-        sHs20Map.put(HS_ICON_REQUEST, ANQPElementType.HSIconRequest);
-        sHs20Map.put(HS_ICON_FILE, ANQPElementType.HSIconFile);
-
-        for (Map.Entry<Integer, ANQPElementType> entry : sAnqpMap.entrySet()) {
-            sRevAnqpmap.put(entry.getValue(), entry.getKey());
-        }
-        for (Map.Entry<Integer, ANQPElementType> entry : sHs20Map.entrySet()) {
-            sRevHs20map.put(entry.getValue(), entry.getKey());
-        }
-    }
-
-    public static ANQPElementType mapANQPElement(int id) {
-        return sAnqpMap.get(id);
-    }
-
-    public static ANQPElementType mapHS20Element(int id) {
-        return sHs20Map.get(id);
-    }
-
-    public static Integer getANQPElementID(ANQPElementType elementType) {
-        return sRevAnqpmap.get(elementType);
-    }
-
-    public static Integer getHS20ElementID(ANQPElementType elementType) {
-        return sRevHs20map.get(elementType);
-    }
-
-    public static boolean hasBaseANQPElements(Collection<ANQPElementType> elements) {
-        if (elements == null) {
-            return false;
-        }
-        for (ANQPElementType element : elements) {
-            if (sRevAnqpmap.containsKey(element)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static boolean hasR2Elements(List<ANQPElementType> elements) {
-        return elements.contains(ANQPElementType.HSOSUProviders);
-    }
-
-    public static long getInteger(ByteBuffer payload, ByteOrder bo, int size) {
-        byte[] octets = new byte[size];
-        payload.get(octets);
-        long value = 0;
-        if (bo == ByteOrder.LITTLE_ENDIAN) {
-            for (int n = octets.length - 1; n >= 0; n--) {
-                value = (value << Byte.SIZE) | (octets[n] & BYTE_MASK);
-            }
-        }
-        else {
-            for (byte octet : octets) {
-                value = (value << Byte.SIZE) | (octet & BYTE_MASK);
-            }
-        }
-        return value;
-    }
-
-    public static String getPrefixedString(ByteBuffer payload, int lengthLength, Charset charset)
-            throws ProtocolException {
-        return getPrefixedString(payload, lengthLength, charset, false);
-    }
-
-    public static String getPrefixedString(ByteBuffer payload, int lengthLength, Charset charset,
-                                           boolean useNull) throws ProtocolException {
-        if (payload.remaining() < lengthLength) {
-            throw new ProtocolException("Runt string: " + payload.remaining());
-        }
-        return getString(payload, (int) getInteger(payload, ByteOrder.LITTLE_ENDIAN,
-                lengthLength), charset, useNull);
-    }
-
-    public static String getTrimmedString(ByteBuffer payload, int length, Charset charset)
-            throws ProtocolException {
-        String s = getString(payload, length, charset, false);
-        int zero = length - 1;
-        while (zero >= 0) {
-            if (s.charAt(zero) != 0) {
-                break;
-            }
-            zero--;
-        }
-        return zero < length - 1 ? s.substring(0, zero + 1) : s;
-    }
-
-    public static String getString(ByteBuffer payload, int length, Charset charset)
-            throws ProtocolException {
-        return getString(payload, length, charset, false);
-    }
-
-    public static String getString(ByteBuffer payload, int length, Charset charset, boolean useNull)
-            throws ProtocolException {
-        if (length > payload.remaining()) {
-            throw new ProtocolException("Bad string length: " + length);
-        }
-        if (useNull && length == 0) {
-            return null;
-        }
-        byte[] octets = new byte[length];
-        payload.get(octets);
-        return new String(octets, charset);
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/DomainNameElement.java b/service/java/com/android/server/wifi/anqp/DomainNameElement.java
deleted file mode 100644
index 69edc90..0000000
--- a/service/java/com/android/server/wifi/anqp/DomainNameElement.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The Domain Name ANQP Element, IEEE802.11-2012 section 8.4.4.15
- */
-public class DomainNameElement extends ANQPElement {
-    private final List<String> mDomains;
-
-    public DomainNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-        mDomains = new ArrayList<>();
-
-        while (payload.hasRemaining()) {
-            // Use latin-1 to decode for now - safe for ASCII and retains encoding
-            mDomains.add(Constants.getPrefixedString(payload, 1, StandardCharsets.ISO_8859_1));
-        }
-    }
-
-    public List<String> getDomains() {
-        return Collections.unmodifiableList(mDomains);
-    }
-
-    @Override
-    public String toString() {
-        return "DomainName{" +
-                "mDomains=" + mDomains +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/EmergencyNumberElement.java b/service/java/com/android/server/wifi/anqp/EmergencyNumberElement.java
deleted file mode 100644
index 6954480..0000000
--- a/service/java/com/android/server/wifi/anqp/EmergencyNumberElement.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * The Emergency Number ANQP Element, IEEE802.11-2012 section 8.4.4.5
- */
-public class EmergencyNumberElement extends ANQPElement {
-    private final List<String> mNumbers;
-
-    public EmergencyNumberElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        mNumbers = new ArrayList<String>();
-
-        while (payload.hasRemaining()) {
-            mNumbers.add(Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8));
-        }
-    }
-
-    public List<String> getNumbers() {
-        return mNumbers;
-    }
-
-    @Override
-    public String toString() {
-        return "EmergencyNumber{" +
-                "mNumbers=" + mNumbers +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/GEOLocationElement.java b/service/java/com/android/server/wifi/anqp/GEOLocationElement.java
deleted file mode 100644
index d434c73..0000000
--- a/service/java/com/android/server/wifi/anqp/GEOLocationElement.java
+++ /dev/null
@@ -1,311 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-
-/**
- * Holds an AP Geospatial Location ANQP Element, as specified in IEEE802.11-2012 section
- * 8.4.4.12.
- * <p/>
- * <p>
- * Section 8.4.2.24.10 of the IEEE802.11-2012 specification refers to RFC-3825 for the format of the
- * Geospatial location information. RFC-3825 has subsequently been obsoleted by RFC-6225 which
- * defines the same basic binary format for the DHCPv4 payload except that a few unused bits of the
- * Datum field have been reserved for other uses.
- * </p>
- * <p/>
- * <p>
- * RFC-3825 defines a resolution field for each of latitude, longitude and altitude as "the number
- * of significant bits" of precision in the respective values and implies through examples and
- * otherwise that the non-significant bits should be simply disregarded and the range of values are
- * calculated as the numeric interval obtained by varying the range of "insignificant bits" between
- * its extremes. As a simple example, consider the value 33 as a simple 8-bit number with three
- * significant bits: 33 is 00100001 binary and the leading 001 are the significant bits. With the
- * above definition, the range of numbers are [32,63] with 33 asymmetrically located at the low end
- * of the interval. In a more realistic setting an instrument, such as a GPS, would most likely
- * deliver measurements with a gaussian distribution around the exact value, meaning it is more
- * reasonable to assume the value as a "center" value with a symmetric uncertainty interval.
- * RFC-6225 redefines the "resolution" from RFC-3825 with an "uncertainty" value with these
- * properties, which is also the definition suggested here.
- * </p>
- * <p/>
- * <p>
- * The res fields provides the resolution as the exponent to a power of two,
- * e.g. 8 means 2^8 = +/- 256, 0 means 2^0 = +/- 1 and -7 means 2^-7 +/- 0.00781250.
- * Unknown resolution is indicated by not setting the respective resolution field in the RealValue.
- * </p>
- */
-public class GEOLocationElement extends ANQPElement {
-    public enum AltitudeType {Unknown, Meters, Floors}
-
-    public enum Datum {Unknown, WGS84, NAD83Land, NAD83Water}
-
-    private static final int ELEMENT_ID = 123;       // ???
-    private static final int GEO_LOCATION_LENGTH = 16;
-
-    private static final int LL_FRACTION_SIZE = 25;
-    private static final int LL_WIDTH = 34;
-    private static final int ALT_FRACTION_SIZE = 8;
-    private static final int ALT_WIDTH = 30;
-    private static final int RES_WIDTH = 6;
-    private static final int ALT_TYPE_WIDTH = 4;
-    private static final int DATUM_WIDTH = 8;
-
-    private final RealValue mLatitude;
-    private final RealValue mLongitude;
-    private final RealValue mAltitude;
-    private final AltitudeType mAltitudeType;
-    private final Datum mDatum;
-
-    public static class RealValue {
-        private final double mValue;
-        private final boolean mResolutionSet;
-        private final int mResolution;
-
-        public RealValue(double value) {
-            mValue = value;
-            mResolution = Integer.MIN_VALUE;
-            mResolutionSet = false;
-        }
-
-        public RealValue(double value, int resolution) {
-            mValue = value;
-            mResolution = resolution;
-            mResolutionSet = true;
-        }
-
-        public double getValue() {
-            return mValue;
-        }
-
-        public boolean isResolutionSet() {
-            return mResolutionSet;
-        }
-
-        public int getResolution() {
-            return mResolution;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append(String.format("%f", mValue));
-            if (mResolutionSet) {
-                sb.append("+/-2^").append(mResolution);
-            }
-            return sb.toString();
-        }
-    }
-
-    public GEOLocationElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        payload.get();
-        int locLength = payload.get() & Constants.BYTE_MASK;
-
-        if (locLength != GEO_LOCATION_LENGTH) {
-            throw new ProtocolException("GeoLocation length field value " + locLength +
-                    " incorrect, expected 16");
-        }
-        if (payload.remaining() != GEO_LOCATION_LENGTH) {
-            throw new ProtocolException("Bad buffer length " + payload.remaining() +
-                    ", expected 16");
-        }
-
-        ReverseBitStream reverseBitStream = new ReverseBitStream(payload);
-
-        int rawLatRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
-        double latitude =
-                fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
-
-        mLatitude = rawLatRes != 0 ?
-                new RealValue(latitude, bitsToAbsResolution(rawLatRes, LL_WIDTH,
-                        LL_FRACTION_SIZE)) :
-                new RealValue(latitude);
-
-        int rawLonRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
-        double longitude =
-                fixToFloat(reverseBitStream.sliceOff(LL_WIDTH), LL_FRACTION_SIZE, LL_WIDTH);
-
-        mLongitude = rawLonRes != 0 ?
-                new RealValue(longitude, bitsToAbsResolution(rawLonRes, LL_WIDTH,
-                        LL_FRACTION_SIZE)) :
-                new RealValue(longitude);
-
-        int altType = (int) reverseBitStream.sliceOff(ALT_TYPE_WIDTH);
-        mAltitudeType = altType < AltitudeType.values().length ?
-                AltitudeType.values()[altType] :
-                AltitudeType.Unknown;
-
-        int rawAltRes = (int) reverseBitStream.sliceOff(RES_WIDTH);
-        double altitude = fixToFloat(reverseBitStream.sliceOff(ALT_WIDTH), ALT_FRACTION_SIZE,
-                ALT_WIDTH);
-
-        mAltitude = rawAltRes != 0 ?
-                new RealValue(altitude, bitsToAbsResolution(rawAltRes, ALT_WIDTH,
-                        ALT_FRACTION_SIZE)) :
-                new RealValue(altitude);
-
-        int datumValue = (int) reverseBitStream.sliceOff(DATUM_WIDTH);
-        mDatum = datumValue < Datum.values().length ? Datum.values()[datumValue] : Datum.Unknown;
-    }
-
-    public RealValue getLatitude() {
-        return mLatitude;
-    }
-
-    public RealValue getLongitude() {
-        return mLongitude;
-    }
-
-    public RealValue getAltitude() {
-        return mAltitude;
-    }
-
-    public AltitudeType getAltitudeType() {
-        return mAltitudeType;
-    }
-
-    public Datum getDatum() {
-        return mDatum;
-    }
-
-    @Override
-    public String toString() {
-        return "GEOLocation{" +
-                "mLatitude=" + mLatitude +
-                ", mLongitude=" + mLongitude +
-                ", mAltitude=" + mAltitude +
-                ", mAltitudeType=" + mAltitudeType +
-                ", mDatum=" + mDatum +
-                '}';
-    }
-
-    private static class ReverseBitStream {
-
-        private final byte[] mOctets;
-        private int mBitoffset;
-
-        private ReverseBitStream(ByteBuffer octets) {
-            mOctets = new byte[octets.remaining()];
-            octets.get(mOctets);
-        }
-
-        private long sliceOff(int bits) {
-            final int bn = mBitoffset + bits;
-            int remaining = bits;
-            long value = 0;
-
-            while (mBitoffset < bn) {
-                int sbit = mBitoffset & 0x7;        // Bit #0 is MSB, inclusive
-                int octet = mBitoffset >>> 3;
-
-                // Copy the minimum of what's to the right of sbit
-                // and how much more goes to the target
-                int width = Math.min(Byte.SIZE - sbit, remaining);
-
-                value = (value << width) | getBits(mOctets[octet], sbit, width);
-
-                mBitoffset += width;
-                remaining -= width;
-            }
-
-            return value;
-        }
-
-        private static int getBits(byte b, int b0, int width) {
-            int mask = (1 << width) - 1;
-            return (b >> (Byte.SIZE - b0 - width)) & mask;
-        }
-    }
-
-    private static class BitStream {
-
-        private final byte[] data;
-        private int bitOffset;              // bit 0 is MSB of data[0]
-
-        private BitStream(int octets) {
-            data = new byte[octets];
-        }
-
-        private void append(long value, int width) {
-            System.out.printf("Appending %x:%d\n", value, width);
-            for (int sbit = width - 1; sbit >= 0; ) {
-                int b0 = bitOffset >>> 3;
-                int dbit = bitOffset & 0x7;
-
-                int shr = sbit - 7 + dbit;
-                int dmask = 0xff >>> dbit;
-
-                if (shr >= 0) {
-                    data[b0] = (byte) ((data[b0] & ~dmask) | ((value >>> shr) & dmask));
-                    bitOffset += Byte.SIZE - dbit;
-                    sbit -= Byte.SIZE - dbit;
-                } else {
-                    data[b0] = (byte) ((data[b0] & ~dmask) | ((value << -shr) & dmask));
-                    bitOffset += sbit + 1;
-                    sbit = -1;
-                }
-            }
-        }
-
-        private byte[] getOctets() {
-            return data;
-        }
-    }
-
-    static double fixToFloat(long value, int fractionSize, int width) {
-        long sign = 1L << (width - 1);
-        if ((value & sign) != 0) {
-            value = -value;
-            return -(double) (value & (sign - 1)) / (double) (1L << fractionSize);
-        } else {
-            return (double) (value & (sign - 1)) / (double) (1L << fractionSize);
-        }
-    }
-
-    private static long floatToFix(double value, int fractionSize, int width) {
-        return Math.round(value * (1L << fractionSize)) & ((1L << width) - 1);
-    }
-
-    private static final double LOG2_FACTOR = 1.0 / Math.log(2.0);
-
-    /**
-     * Convert an absolute variance value into absolute resolution representation,
-     * where the variance = 2^resolution.
-     *
-     * @param variance The absolute variance
-     * @return the absolute resolution.
-     */
-    private static int getResolution(double variance) {
-        return (int) Math.ceil(Math.log(variance) * LOG2_FACTOR);
-    }
-
-    /**
-     * Convert an absolute resolution, into the "number of significant bits" for the given fixed
-     * point notation as defined in RFC-3825 and refined in RFC-6225.
-     *
-     * @param resolution   absolute resolution given as 2^resolution.
-     * @param fieldWidth   Full width of the fixed point number used to represent the value.
-     * @param fractionBits Number of fraction bits in the fixed point number used to represent the
-     *                     value.
-     * @return The number of "significant bits".
-     */
-    private static int absResolutionToBits(int resolution, int fieldWidth, int fractionBits) {
-        return fieldWidth - fractionBits - 1 - resolution;
-    }
-
-    /**
-     * Convert the protocol definition of "number of significant bits" into an absolute resolution.
-     *
-     * @param bits         The number of "significant bits" from the binary protocol.
-     * @param fieldWidth   Full width of the fixed point number used to represent the value.
-     * @param fractionBits Number of fraction bits in the fixed point number used to represent the
-     *                     value.
-     * @return The absolute resolution given as 2^resolution.
-     */
-    private static int bitsToAbsResolution(long bits, int fieldWidth, int fractionBits) {
-        return fieldWidth - fractionBits - 1 - (int) bits;
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/GenericStringElement.java b/service/java/com/android/server/wifi/anqp/GenericStringElement.java
deleted file mode 100644
index 6cf7b1a..0000000
--- a/service/java/com/android/server/wifi/anqp/GenericStringElement.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-
-/**
- * ANQP Element to hold a generic (UTF-8 decoded) character string
- */
-public class GenericStringElement extends ANQPElement {
-    private final String mText;
-
-    public GenericStringElement(Constants.ANQPElementType infoID, ByteBuffer payload) throws ProtocolException {
-        super(infoID);
-        mText = Constants.getString(payload, payload.remaining(), StandardCharsets.UTF_8);
-    }
-
-    public String getM_text() {
-        return mText;
-    }
-
-    @Override
-    public String toString() {
-        return "Element ID " + getID() + ": '" + mText + "'";
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/HSCapabilityListElement.java b/service/java/com/android/server/wifi/anqp/HSCapabilityListElement.java
deleted file mode 100644
index 8269324..0000000
--- a/service/java/com/android/server/wifi/anqp/HSCapabilityListElement.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-/**
- * The HS Capability list vendor specific ANQP Element,
- * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.2
- */
-public class HSCapabilityListElement extends ANQPElement {
-    private final Constants.ANQPElementType[] mCapabilities;
-
-    public HSCapabilityListElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        mCapabilities = new Constants.ANQPElementType[payload.remaining()];
-
-        int index = 0;
-        while (payload.hasRemaining()) {
-            int capID = payload.get() & Constants.BYTE_MASK;
-            Constants.ANQPElementType capability = Constants.mapHS20Element(capID);
-            if (capability == null) {
-                throw new ProtocolException("Unknown capability: " + capID);
-            }
-            mCapabilities[index++] = capability;
-        }
-    }
-
-    public Constants.ANQPElementType[] getCapabilities() {
-        return mCapabilities;
-    }
-
-    @Override
-    public String toString() {
-        return "HSCapabilityList{" +
-                "mCapabilities=" + Arrays.toString(mCapabilities) +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/HSConnectionCapabilityElement.java b/service/java/com/android/server/wifi/anqp/HSConnectionCapabilityElement.java
deleted file mode 100644
index 53f1051..0000000
--- a/service/java/com/android/server/wifi/anqp/HSConnectionCapabilityElement.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The Connection Capability vendor specific ANQP Element,
- * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.5
- */
-public class HSConnectionCapabilityElement extends ANQPElement {
-
-    public enum ProtoStatus {Closed, Open, Unknown}
-
-    private final List<ProtocolTuple> mStatusList;
-
-    public static class ProtocolTuple {
-        private final int mProtocol;
-        private final int mPort;
-        private final ProtoStatus mStatus;
-
-        private ProtocolTuple(ByteBuffer payload) throws ProtocolException {
-            if (payload.remaining() < 4) {
-                throw new ProtocolException("Runt protocol tuple: " + payload.remaining());
-            }
-            mProtocol = payload.get() & Constants.BYTE_MASK;
-            mPort = payload.getShort() & Constants.SHORT_MASK;
-            int statusNumber = payload.get() & Constants.BYTE_MASK;
-            mStatus = statusNumber < ProtoStatus.values().length ?
-                    ProtoStatus.values()[statusNumber] :
-                    null;
-        }
-
-        public int getProtocol() {
-            return mProtocol;
-        }
-
-        public int getPort() {
-            return mPort;
-        }
-
-        public ProtoStatus getStatus() {
-            return mStatus;
-        }
-
-        @Override
-        public String toString() {
-            return "ProtocolTuple{" +
-                    "mProtocol=" + mProtocol +
-                    ", mPort=" + mPort +
-                    ", mStatus=" + mStatus +
-                    '}';
-        }
-    }
-
-    public HSConnectionCapabilityElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        mStatusList = new ArrayList<>();
-        while (payload.hasRemaining()) {
-            mStatusList.add(new ProtocolTuple(payload));
-        }
-    }
-
-    public List<ProtocolTuple> getStatusList() {
-        return Collections.unmodifiableList(mStatusList);
-    }
-
-    @Override
-    public String toString() {
-        return "HSConnectionCapability{" +
-                "mStatusList=" + mStatusList +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/HSFriendlyNameElement.java b/service/java/com/android/server/wifi/anqp/HSFriendlyNameElement.java
deleted file mode 100644
index a15fc29..0000000
--- a/service/java/com/android/server/wifi/anqp/HSFriendlyNameElement.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The Operator Friendly Name vendor specific ANQP Element,
- * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.3
- */
-public class HSFriendlyNameElement extends ANQPElement {
-    private final List<I18Name> mNames;
-
-    public HSFriendlyNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        mNames = new ArrayList<I18Name>();
-
-        while (payload.hasRemaining()) {
-            mNames.add(new I18Name(payload));
-        }
-    }
-
-    public List<I18Name> getNames() {
-        return Collections.unmodifiableList(mNames);
-    }
-
-    @Override
-    public String toString() {
-        return "HSFriendlyName{" +
-                "mNames=" + mNames +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/HSIconFileElement.java b/service/java/com/android/server/wifi/anqp/HSIconFileElement.java
deleted file mode 100644
index f72ab10..0000000
--- a/service/java/com/android/server/wifi/anqp/HSIconFileElement.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
-
-/**
- * The Icon Binary File vendor specific ANQP Element,
- * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.11
- */
-public class HSIconFileElement extends ANQPElement {
-
-    public enum StatusCode {Success, FileNotFound, Unspecified}
-
-    private final StatusCode mStatusCode;
-    private final String mType;
-    private final byte[] mIconData;
-
-    public HSIconFileElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        if (payload.remaining() < 4) {
-            throw new ProtocolException("Truncated icon file: " + payload.remaining());
-        }
-
-        int statusID = payload.get() & BYTE_MASK;
-        mStatusCode = statusID < StatusCode.values().length ? StatusCode.values()[statusID] : null;
-        mType = Constants.getPrefixedString(payload, 1, StandardCharsets.US_ASCII);
-
-        int dataLength = payload.getShort() & SHORT_MASK;
-        mIconData = new byte[dataLength];
-        payload.get(mIconData);
-    }
-
-    public StatusCode getStatusCode() {
-        return mStatusCode;
-    }
-
-    public String getType() {
-        return mType;
-    }
-
-    public byte[] getIconData() {
-        return mIconData;
-    }
-
-    @Override
-    public String toString() {
-        return "HSIconFile{" +
-                "statusCode=" + mStatusCode +
-                ", type='" + mType + '\'' +
-                ", iconData=" + mIconData.length + " bytes }";
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/HSOsuProvidersElement.java b/service/java/com/android/server/wifi/anqp/HSOsuProvidersElement.java
deleted file mode 100644
index ee55517..0000000
--- a/service/java/com/android/server/wifi/anqp/HSOsuProvidersElement.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The OSU Providers List vendor specific ANQP Element,
- * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.8
- */
-public class HSOsuProvidersElement extends ANQPElement {
-    private final String mSSID;
-    private final List<OSUProvider> mProviders;
-
-    public HSOsuProvidersElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        mSSID = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
-        int providerCount = payload.get() & Constants.BYTE_MASK;
-
-        mProviders = new ArrayList<>(providerCount);
-
-        while (providerCount > 0) {
-            mProviders.add(new OSUProvider(payload));
-            providerCount--;
-        }
-    }
-
-    public String getSSID() {
-        return mSSID;
-    }
-
-    public List<OSUProvider> getProviders() {
-        return Collections.unmodifiableList(mProviders);
-    }
-
-    @Override
-    public String toString() {
-        return "HSOsuProviders{" +
-                "SSID='" + mSSID + '\'' +
-                ", providers=" + mProviders +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/HSWanMetricsElement.java b/service/java/com/android/server/wifi/anqp/HSWanMetricsElement.java
deleted file mode 100644
index a1e1ca9..0000000
--- a/service/java/com/android/server/wifi/anqp/HSWanMetricsElement.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-import static com.android.server.wifi.anqp.Constants.INT_MASK;
-import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
-
-/**
- * The WAN Metrics vendor specific ANQP Element,
- * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.4
- */
-public class HSWanMetricsElement extends ANQPElement {
-
-    public enum LinkStatus {Reserved, Up, Down, Test}
-
-    private final LinkStatus mStatus;
-    private final boolean mSymmetric;
-    private final boolean mCapped;
-    private final long mDlSpeed;
-    private final long mUlSpeed;
-    private final int mDlLoad;
-    private final int mUlLoad;
-    private final int mLMD;
-
-    public HSWanMetricsElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        if (payload.remaining() != 13) {
-            throw new ProtocolException("Bad WAN metrics length: " + payload.remaining());
-        }
-
-        int status = payload.get() & BYTE_MASK;
-        mStatus = LinkStatus.values()[status & 0x03];
-        mSymmetric = (status & 0x04) != 0;
-        mCapped = (status & 0x08) != 0;
-        mDlSpeed = payload.getInt() & INT_MASK;
-        mUlSpeed = payload.getInt() & INT_MASK;
-        mDlLoad = payload.get() & BYTE_MASK;
-        mUlLoad = payload.get() & BYTE_MASK;
-        mLMD = payload.getShort() & SHORT_MASK;
-    }
-
-    public LinkStatus getStatus() {
-        return mStatus;
-    }
-
-    public boolean isSymmetric() {
-        return mSymmetric;
-    }
-
-    public boolean isCapped() {
-        return mCapped;
-    }
-
-    public long getDlSpeed() {
-        return mDlSpeed;
-    }
-
-    public long getUlSpeed() {
-        return mUlSpeed;
-    }
-
-    public int getDlLoad() {
-        return mDlLoad;
-    }
-
-    public int getUlLoad() {
-        return mUlLoad;
-    }
-
-    public int getLMD() {
-        return mLMD;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("HSWanMetrics{mStatus=%s, mSymmetric=%s, mCapped=%s, " +
-                "mDlSpeed=%d, mUlSpeed=%d, mDlLoad=%f, mUlLoad=%f, mLMD=%d}",
-                mStatus, mSymmetric, mCapped,
-                mDlSpeed, mUlSpeed,
-                (double)mDlLoad * 100.0 / 256.0,
-                (double)mUlLoad * 100.0 / 256.0,
-                mLMD);
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/I18Name.java b/service/java/com/android/server/wifi/anqp/I18Name.java
deleted file mode 100644
index 6f247aa..0000000
--- a/service/java/com/android/server/wifi/anqp/I18Name.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-
-import java.io.IOException;
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.Locale;
-
-/**
- * A generic Internationalized name used in ANQP elements as specified in 802.11-2012 and
- * "Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00"
- */
-public class I18Name {
-    private final String mLanguage;
-    private final Locale mLocale;
-    private final String mText;
-
-    public I18Name(ByteBuffer payload) throws ProtocolException {
-        if (payload.remaining() < Constants.LANG_CODE_LENGTH + 1) {
-            throw new ProtocolException("Truncated I18Name: " + payload.remaining());
-        }
-        int nameLength = payload.get() & BYTE_MASK;
-        if (nameLength < Constants.LANG_CODE_LENGTH) {
-            throw new ProtocolException("Runt I18Name: " + nameLength);
-        }
-        mLanguage = Constants.getTrimmedString(payload,
-                Constants.LANG_CODE_LENGTH, StandardCharsets.US_ASCII);
-        mLocale = Locale.forLanguageTag(mLanguage);
-        mText = Constants.getString(payload, nameLength -
-                Constants.LANG_CODE_LENGTH, StandardCharsets.UTF_8);
-    }
-
-    public I18Name(String compoundString) throws IOException {
-        if (compoundString.length() < Constants.LANG_CODE_LENGTH) {
-            throw new IOException("I18String too short: '" + compoundString + "'");
-        }
-        mLanguage = compoundString.substring(0, Constants.LANG_CODE_LENGTH);
-        mText = compoundString.substring(Constants.LANG_CODE_LENGTH);
-        mLocale = Locale.forLanguageTag(mLanguage);
-    }
-
-    public String getLanguage() {
-        return mLanguage;
-    }
-
-    public Locale getLocale() {
-        return mLocale;
-    }
-
-    public String getText() {
-        return mText;
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (this == thatObject) {
-            return true;
-        }
-        if (thatObject == null || getClass() != thatObject.getClass()) {
-            return false;
-        }
-
-        I18Name that = (I18Name) thatObject;
-        return mLanguage.equals(that.mLanguage) && mText.equals(that.mText);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mLanguage.hashCode();
-        result = 31 * result + mText.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return mText + ':' + mLocale.getLanguage();
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/IPAddressTypeAvailabilityElement.java b/service/java/com/android/server/wifi/anqp/IPAddressTypeAvailabilityElement.java
deleted file mode 100644
index 8d71a3b..0000000
--- a/service/java/com/android/server/wifi/anqp/IPAddressTypeAvailabilityElement.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-
-/**
- * The IP Address Type availability ANQP Element, IEEE802.11-2012 section 8.4.4.9
- */
-public class IPAddressTypeAvailabilityElement extends ANQPElement {
-    public enum IPv4Availability {
-        NotAvailable, Public, PortRestricted, SingleNAT, DoubleNAT,
-        PortRestrictedAndSingleNAT, PortRestrictedAndDoubleNAT, Unknown
-    }
-
-    public enum IPv6Availability {NotAvailable, Available, Unknown, Reserved}
-
-    private final IPv4Availability mV4Availability;
-    private final IPv6Availability mV6Availability;
-
-    public IPAddressTypeAvailabilityElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        if (payload.remaining() != 1)
-            throw new ProtocolException("Bad IP Address Type Availability length: " +
-                    payload.remaining());
-
-        int ipField = payload.get();
-        mV6Availability = IPv6Availability.values()[ipField & 0x3];
-
-        ipField = (ipField >> 2) & 0x3f;
-        mV4Availability = ipField < IPv4Availability.values().length ?
-                IPv4Availability.values()[ipField] :
-                IPv4Availability.Unknown;
-    }
-
-    public IPv4Availability getV4Availability() {
-        return mV4Availability;
-    }
-
-    public IPv6Availability getV6Availability() {
-        return mV6Availability;
-    }
-
-    @Override
-    public String toString() {
-        return "IPAddressTypeAvailability{" +
-                "mV4Availability=" + mV4Availability +
-                ", mV6Availability=" + mV6Availability +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/NAIRealmData.java b/service/java/com/android/server/wifi/anqp/NAIRealmData.java
deleted file mode 100644
index 36323f7..0000000
--- a/service/java/com/android/server/wifi/anqp/NAIRealmData.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import com.android.server.wifi.anqp.eap.EAPMethod;
-import com.android.server.wifi.hotspot2.AuthMatch;
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.pps.Credential;
-import com.android.server.wifi.hotspot2.pps.DomainMatcher;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The NAI Realm Data ANQP sub-element, IEEE802.11-2012 section 8.4.4.10 figure 8-418
- */
-public class NAIRealmData {
-    private final List<String> mRealms;
-    private final List<EAPMethod> mEAPMethods;
-
-    public NAIRealmData(ByteBuffer payload) throws ProtocolException {
-        if (payload.remaining() < 5) {
-            throw new ProtocolException("Runt payload: " + payload.remaining());
-        }
-
-        int length = payload.getShort() & Constants.SHORT_MASK;
-        if (length > payload.remaining()) {
-            throw new ProtocolException("Invalid data length: " + length);
-        }
-        boolean utf8 = (payload.get() & 1) == Constants.UTF8_INDICATOR;
-
-        String realm = Constants.getPrefixedString(payload, 1, utf8 ?
-                StandardCharsets.UTF_8 :
-                StandardCharsets.US_ASCII);
-        String[] realms = realm.split(";");
-        mRealms = new ArrayList<>();
-        for (String realmElement : realms) {
-            if (realmElement.length() > 0) {
-                mRealms.add(realmElement);
-            }
-        }
-
-        int methodCount = payload.get() & Constants.BYTE_MASK;
-        mEAPMethods = new ArrayList<>(methodCount);
-        while (methodCount > 0) {
-            mEAPMethods.add(new EAPMethod(payload));
-            methodCount--;
-        }
-    }
-
-    public List<String> getRealms() {
-        return Collections.unmodifiableList(mRealms);
-    }
-
-    public List<EAPMethod> getEAPMethods() {
-        return Collections.unmodifiableList(mEAPMethods);
-    }
-
-    public int match(List<String> credLabels, Credential credential) {
-        int realmMatch = AuthMatch.None;
-        if (!mRealms.isEmpty()) {
-            for (String realm : mRealms) {
-                List<String> labels = Utils.splitDomain(realm);
-                if (DomainMatcher.arg2SubdomainOfArg1(credLabels, labels)) {
-                    realmMatch = AuthMatch.Realm;
-                    break;
-                }
-            }
-            if (realmMatch == AuthMatch.None || mEAPMethods.isEmpty()) {
-                return realmMatch;
-            }
-            // else there is a realm match and one or more EAP methods - check them.
-        }
-        else if (mEAPMethods.isEmpty()) {
-            return AuthMatch.Indeterminate;
-        }
-
-        int best = AuthMatch.None;
-        for (EAPMethod eapMethod : mEAPMethods) {
-            int match = eapMethod.match(credential) | realmMatch;
-            if (match > best) {
-                best = match;
-                if (best == AuthMatch.Exact) {
-                    return best;
-                }
-            }
-        }
-        return best;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-
-        sb.append("  NAI Realm(s)");
-        for (String realm : mRealms) {
-            sb.append(' ').append(realm);
-        }
-        sb.append('\n');
-
-        for (EAPMethod eapMethod : mEAPMethods) {
-            sb.append( "    " ).append(eapMethod.toString());
-        }
-        return sb.toString();
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/NAIRealmElement.java b/service/java/com/android/server/wifi/anqp/NAIRealmElement.java
deleted file mode 100644
index c21dbee..0000000
--- a/service/java/com/android/server/wifi/anqp/NAIRealmElement.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import com.android.server.wifi.SIMAccessor;
-import com.android.server.wifi.hotspot2.AuthMatch;
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.pps.Credential;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static com.android.server.wifi.anqp.Constants.BYTES_IN_SHORT;
-import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
-
-/**
- * The NAI Realm ANQP Element, IEEE802.11-2012 section 8.4.4.10
- */
-public class NAIRealmElement extends ANQPElement {
-    private final List<NAIRealmData> mRealmData;
-
-    public NAIRealmElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        if (!payload.hasRemaining()) {
-            mRealmData = Collections.emptyList();
-            return;
-        }
-
-        if (payload.remaining() < BYTES_IN_SHORT) {
-            throw new ProtocolException("Runt NAI Realm: " + payload.remaining());
-        }
-
-        int count = payload.getShort() & SHORT_MASK;
-        mRealmData = new ArrayList<>(count);
-        while (count > 0) {
-            mRealmData.add(new NAIRealmData(payload));
-            count--;
-        }
-    }
-
-    public List<NAIRealmData> getRealmData() {
-        return Collections.unmodifiableList(mRealmData);
-    }
-
-    public int match(Credential credential) {
-        if (mRealmData.isEmpty())
-            return AuthMatch.Indeterminate;
-
-        List<String> credLabels = Utils.splitDomain(credential.getRealm());
-        int best = AuthMatch.None;
-        for (NAIRealmData realmData : mRealmData) {
-            int match = realmData.match(credLabels, credential);
-            if (match > best) {
-                best = match;
-                if (best == AuthMatch.Exact) {
-                    return best;
-                }
-            }
-        }
-        return best;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("NAI Realm:\n");
-        for (NAIRealmData data : mRealmData) {
-            sb.append(data);
-        }
-        return sb.toString();
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/NetworkAuthenticationTypeElement.java b/service/java/com/android/server/wifi/anqp/NetworkAuthenticationTypeElement.java
deleted file mode 100644
index b496e3e..0000000
--- a/service/java/com/android/server/wifi/anqp/NetworkAuthenticationTypeElement.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-
-/**
- * The Network Authentication Type ANQP Element, IEEE802.11-2012 section 8.4.4.6
- */
-public class NetworkAuthenticationTypeElement extends ANQPElement {
-
-    private final List<NetworkAuthentication> m_authenticationTypes;
-
-    public enum NwkAuthTypeEnum {
-        TermsAndConditions,
-        OnLineEnrollment,
-        HTTPRedirection,
-        DNSRedirection,
-        Reserved
-    }
-
-    public static class NetworkAuthentication {
-        private final NwkAuthTypeEnum m_type;
-        private final String m_url;
-
-        private NetworkAuthentication(NwkAuthTypeEnum type, String url) {
-            m_type = type;
-            m_url = url;
-        }
-
-        public NwkAuthTypeEnum getType() {
-            return m_type;
-        }
-
-        public String getURL() {
-            return m_url;
-        }
-
-        @Override
-        public String toString() {
-            return "NetworkAuthentication{" +
-                    "m_type=" + m_type +
-                    ", m_url='" + m_url + '\'' +
-                    '}';
-        }
-    }
-
-    public NetworkAuthenticationTypeElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-
-        super(infoID);
-
-        m_authenticationTypes = new ArrayList<NetworkAuthentication>();
-
-        while (payload.hasRemaining()) {
-            int typeNumber = payload.get() & BYTE_MASK;
-            NwkAuthTypeEnum type;
-            type = typeNumber >= NwkAuthTypeEnum.values().length ?
-                    NwkAuthTypeEnum.Reserved :
-                    NwkAuthTypeEnum.values()[typeNumber];
-
-            m_authenticationTypes.add(new NetworkAuthentication(type,
-                    Constants.getPrefixedString(payload, 2, StandardCharsets.UTF_8)));
-        }
-    }
-
-    public List<NetworkAuthentication> getAuthenticationTypes() {
-        return Collections.unmodifiableList(m_authenticationTypes);
-    }
-
-    @Override
-    public String toString() {
-        return "NetworkAuthenticationType{" +
-                "m_authenticationTypes=" + m_authenticationTypes +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/OSUProvider.java b/service/java/com/android/server/wifi/anqp/OSUProvider.java
deleted file mode 100644
index e325ca0..0000000
--- a/service/java/com/android/server/wifi/anqp/OSUProvider.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
-
-/**
- * An OSU Provider, as specified in
- * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
- * section 4.8.1
- */
-public class OSUProvider {
-
-    public enum OSUMethod {OmaDm, SoapXml}
-
-    private final List<I18Name> mNames;
-    private final String mOSUServer;
-    private final List<OSUMethod> mOSUMethods;
-    private final List<IconInfo> mIcons;
-    private final String mOsuNai;
-    private final List<I18Name> mServiceDescriptions;
-    private final int mHashCode;
-
-    public OSUProvider(ByteBuffer payload) throws ProtocolException {
-        if (payload.remaining() < 11) {
-            throw new ProtocolException("Truncated OSU provider: " + payload.remaining());
-        }
-
-        int length = payload.getShort() & SHORT_MASK;
-        int namesLength = payload.getShort() & SHORT_MASK;
-
-        ByteBuffer namesBuffer = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
-        namesBuffer.limit(namesBuffer.position() + namesLength);
-        payload.position(payload.position() + namesLength);
-
-        mNames = new ArrayList<>();
-
-        while (namesBuffer.hasRemaining()) {
-            mNames.add(new I18Name(namesBuffer));
-        }
-
-        mOSUServer = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
-        int methodLength = payload.get() & BYTE_MASK;
-        mOSUMethods = new ArrayList<>(methodLength);
-        while (methodLength > 0) {
-            int methodID = payload.get() & BYTE_MASK;
-            mOSUMethods.add(methodID < OSUMethod.values().length ?
-                    OSUMethod.values()[methodID] :
-                    null);
-            methodLength--;
-        }
-
-        int iconsLength = payload.getShort() & SHORT_MASK;
-        ByteBuffer iconsBuffer = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
-        iconsBuffer.limit(iconsBuffer.position() + iconsLength);
-        payload.position(payload.position() + iconsLength);
-
-        mIcons = new ArrayList<>();
-
-        while (iconsBuffer.hasRemaining()) {
-            mIcons.add(new IconInfo(iconsBuffer));
-        }
-
-        mOsuNai = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8, true);
-
-        int descriptionsLength = payload.getShort() & SHORT_MASK;
-        ByteBuffer descriptionsBuffer = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
-        descriptionsBuffer.limit(descriptionsBuffer.position() + descriptionsLength);
-        payload.position(payload.position() + descriptionsLength);
-
-        mServiceDescriptions = new ArrayList<>();
-
-        while (descriptionsBuffer.hasRemaining()) {
-            mServiceDescriptions.add(new I18Name(descriptionsBuffer));
-        }
-
-        int result = mNames.hashCode();
-        result = 31 * result + mOSUServer.hashCode();
-        result = 31 * result + mOSUMethods.hashCode();
-        result = 31 * result + mIcons.hashCode();
-        result = 31 * result + (mOsuNai != null ? mOsuNai.hashCode() : 0);
-        result = 31 * result + mServiceDescriptions.hashCode();
-        mHashCode = result;
-    }
-
-    public List<I18Name> getNames() {
-        return mNames;
-    }
-
-    public String getOSUServer() {
-        return mOSUServer;
-    }
-
-    public List<OSUMethod> getOSUMethods() {
-        return mOSUMethods;
-    }
-
-    public List<IconInfo> getIcons() {
-        return mIcons;
-    }
-
-    public String getOsuNai() {
-        return mOsuNai;
-    }
-
-    public List<I18Name> getServiceDescriptions() {
-        return mServiceDescriptions;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        OSUProvider that = (OSUProvider) o;
-
-        if (!mOSUServer.equals(that.mOSUServer)) return false;
-        if (!mNames.equals(that.mNames)) return false;
-        if (!mServiceDescriptions.equals(that.mServiceDescriptions)) return false;
-        if (!mIcons.equals(that.mIcons)) return false;
-        if (!mOSUMethods.equals(that.mOSUMethods)) return false;
-        if (mOsuNai != null ? !mOsuNai.equals(that.mOsuNai) : that.mOsuNai != null) return false;
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return mHashCode;
-    }
-
-    @Override
-    public String toString() {
-        return "OSUProvider{" +
-                "names=" + mNames +
-                ", OSUServer='" + mOSUServer + '\'' +
-                ", OSUMethods=" + mOSUMethods +
-                ", icons=" + mIcons +
-                ", NAI='" + mOsuNai + '\'' +
-                ", serviceDescriptions=" + mServiceDescriptions +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/RawByteElement.java b/service/java/com/android/server/wifi/anqp/RawByteElement.java
deleted file mode 100644
index 42a0cdc..0000000
--- a/service/java/com/android/server/wifi/anqp/RawByteElement.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.nio.ByteBuffer;
-
-/**
- * An object holding the raw octets of an ANQP element as provided by the wpa_supplicant.
- */
-public class RawByteElement extends ANQPElement {
-    private final byte[] mPayload;
-
-    public RawByteElement(Constants.ANQPElementType infoID, ByteBuffer payload) {
-        super(infoID);
-        mPayload = new byte[payload.remaining()];
-        payload.get(mPayload);
-    }
-
-    public byte[] getPayload() {
-        return mPayload;
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/RoamingConsortiumElement.java b/service/java/com/android/server/wifi/anqp/RoamingConsortiumElement.java
deleted file mode 100644
index 80d9b72..0000000
--- a/service/java/com/android/server/wifi/anqp/RoamingConsortiumElement.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import com.android.server.wifi.hotspot2.Utils;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-import static com.android.server.wifi.anqp.Constants.getInteger;
-
-/**
- * The Roaming Consortium ANQP Element, IEEE802.11-2012 section 8.4.4.7
- */
-public class RoamingConsortiumElement extends ANQPElement {
-
-    private final List<Long> mOis;
-
-    public RoamingConsortiumElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        mOis = new ArrayList<Long>();
-
-        while (payload.hasRemaining()) {
-            int length = payload.get() & BYTE_MASK;
-            if (length > payload.remaining()) {
-                throw new ProtocolException("Bad OI length: " + length);
-            }
-            mOis.add(getInteger(payload, ByteOrder.BIG_ENDIAN, length));
-        }
-    }
-
-    public List<Long> getOIs() {
-        return Collections.unmodifiableList(mOis);
-    }
-
-    @Override
-    public String toString() {
-        return "RoamingConsortium{mOis=[" + Utils.roamingConsortiumsToString(mOis) + "]}";
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/ThreeGPPNetworkElement.java b/service/java/com/android/server/wifi/anqp/ThreeGPPNetworkElement.java
deleted file mode 100644
index 083d280..0000000
--- a/service/java/com/android/server/wifi/anqp/ThreeGPPNetworkElement.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-
-
-/**
- * The 3GPP Cellular Network ANQP Element, IEEE802.11-2012 section 8.4.4.11
- */
-public class ThreeGPPNetworkElement extends ANQPElement {
-    private final int mUserData;
-    private final List<CellularNetwork> mPlmns;
-
-    public ThreeGPPNetworkElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        mPlmns = new ArrayList<CellularNetwork>();
-        mUserData = payload.get() & BYTE_MASK;
-        int length = payload.get() & BYTE_MASK;
-        if (length > payload.remaining()) {
-            throw new ProtocolException("Runt payload");
-        }
-
-        while (payload.hasRemaining()) {
-            CellularNetwork network = CellularNetwork.buildCellularNetwork(payload);
-            if (network != null) {
-                mPlmns.add(network);
-            }
-        }
-    }
-
-    public int getUserData() {
-        return mUserData;
-    }
-
-    public List<CellularNetwork> getPlmns() {
-        return Collections.unmodifiableList(mPlmns);
-    }
-
-    @Override
-    public String toString() {
-        return "ThreeGPPNetwork{" +
-                "mUserData=" + mUserData +
-                ", mPlmns=" + mPlmns +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/VenueNameElement.java b/service/java/com/android/server/wifi/anqp/VenueNameElement.java
deleted file mode 100644
index f944c40..0000000
--- a/service/java/com/android/server/wifi/anqp/VenueNameElement.java
+++ /dev/null
@@ -1,192 +0,0 @@
-package com.android.server.wifi.anqp;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * The Venue Name ANQP Element, IEEE802.11-2012 section 8.4.4.4
- */
-public class VenueNameElement extends ANQPElement {
-    private final VenueGroup mGroup;
-    private final VenueType mType;
-    private final List<I18Name> mNames;
-
-    private static final Map<VenueGroup, Integer> sGroupBases =
-            new EnumMap<VenueGroup, Integer>(VenueGroup.class);
-
-    public VenueNameElement(Constants.ANQPElementType infoID, ByteBuffer payload)
-            throws ProtocolException {
-        super(infoID);
-
-        if (payload.remaining() < 2)
-            throw new ProtocolException("Runt Venue Name");
-
-        int group = payload.get() & Constants.BYTE_MASK;
-        int type = payload.get() & Constants.BYTE_MASK;
-
-        if (group >= VenueGroup.Reserved.ordinal()) {
-            mGroup = VenueGroup.Reserved;
-            mType = VenueType.Reserved;
-        } else {
-            mGroup = VenueGroup.values()[group];
-            type += sGroupBases.get(mGroup);
-            if (type >= VenueType.Reserved.ordinal()) {
-                mType = VenueType.Reserved;
-            } else {
-                mType = VenueType.values()[type];
-            }
-        }
-
-        mNames = new ArrayList<I18Name>();
-        while (payload.hasRemaining()) {
-            mNames.add(new I18Name(payload));
-        }
-    }
-
-    public VenueGroup getGroup() {
-        return mGroup;
-    }
-
-    public VenueType getType() {
-        return mType;
-    }
-
-    public List<I18Name> getNames() {
-        return Collections.unmodifiableList(mNames);
-    }
-
-    @Override
-    public String toString() {
-        return "VenueName{" +
-                "m_group=" + mGroup +
-                ", m_type=" + mType +
-                ", m_names=" + mNames +
-                '}';
-    }
-
-    public enum VenueGroup {
-        Unspecified,
-        Assembly,
-        Business,
-        Educational,
-        FactoryIndustrial,
-        Institutional,
-        Mercantile,
-        Residential,
-        Storage,
-        UtilityMiscellaneous,
-        Vehicular,
-        Outdoor,
-        Reserved  // Note: this must be the last enum constant
-    }
-
-    public enum VenueType {
-        Unspecified,
-
-        UnspecifiedAssembly,
-        Arena,
-        Stadium,
-        PassengerTerminal,
-        Amphitheater,
-        AmusementPark,
-        PlaceOfWorship,
-        ConventionCenter,
-        Library,
-        Museum,
-        Restaurant,
-        Theater,
-        Bar,
-        CoffeeShop,
-        ZooOrAquarium,
-        EmergencyCoordinationCenter,
-
-        UnspecifiedBusiness,
-        DoctorDentistoffice,
-        Bank,
-        FireStation,
-        PoliceStation,
-        PostOffice,
-        ProfessionalOffice,
-        ResearchDevelopmentFacility,
-        AttorneyOffice,
-
-        UnspecifiedEducational,
-        SchoolPrimary,
-        SchoolSecondary,
-        UniversityCollege,
-
-        UnspecifiedFactoryIndustrial,
-        Factory,
-
-        UnspecifiedInstitutional,
-        Hospital,
-        LongTermCareFacility,
-        AlcoholAndDrugRehabilitationCenter,
-        GroupHome,
-        PrisonJail,
-
-        UnspecifiedMercantile,
-        RetailStore,
-        GroceryMarket,
-        AutomotiveServiceStation,
-        ShoppingMall,
-        GasStation,
-
-        UnspecifiedResidential,
-        PrivateResidence,
-        HotelMotel,
-        Dormitory,
-        BoardingHouse,
-
-        UnspecifiedStorage,
-
-        UnspecifiedUtilityMiscellaneous,
-
-        UnspecifiedVehicular,
-        AutomobileOrTruck,
-        Airplane,
-        Bus,
-        Ferry,
-        ShipOrBoat,
-        Train,
-        MotorBike,
-
-        UnspecifiedOutdoor,
-        MuniMeshNetwork,
-        CityPark,
-        RestArea,
-        TrafficControl,
-        BusStop,
-        Kiosk,
-
-        Reserved  // Note: this must be the last enum constant
-    }
-
-    private static final VenueType[] PerGroup =
-            {
-                    VenueType.Unspecified,
-                    VenueType.UnspecifiedAssembly,
-                    VenueType.UnspecifiedBusiness,
-                    VenueType.UnspecifiedEducational,
-                    VenueType.UnspecifiedFactoryIndustrial,
-                    VenueType.UnspecifiedInstitutional,
-                    VenueType.UnspecifiedMercantile,
-                    VenueType.UnspecifiedResidential,
-                    VenueType.UnspecifiedStorage,
-                    VenueType.UnspecifiedUtilityMiscellaneous,
-                    VenueType.UnspecifiedVehicular,
-                    VenueType.UnspecifiedOutdoor
-            };
-
-    static {
-        int index = 0;
-        for (VenueType venue : PerGroup) {
-            sGroupBases.put(VenueGroup.values()[index++], venue.ordinal());
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/AuthParam.java b/service/java/com/android/server/wifi/anqp/eap/AuthParam.java
deleted file mode 100644
index f7c877a..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/AuthParam.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-/**
- * An Authentication parameter, part of the NAI Realm ANQP element, specified in
- * IEEE802.11-2012 section 8.4.4.10, table 8-188
- */
-public interface AuthParam {
-    public EAP.AuthInfoID getAuthInfoID();
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/Credential.java b/service/java/com/android/server/wifi/anqp/eap/Credential.java
deleted file mode 100644
index d3aca07..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/Credential.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-
-/**
- * An EAP authentication parameter, IEEE802.11-2012, table 8-188
- */
-public class Credential implements AuthParam {
-
-    public enum CredType {
-        Reserved,
-        SIM,
-        USIM,
-        NFC,
-        HWToken,
-        Softoken,
-        Certificate,
-        Username,
-        None,
-        Anonymous,
-        VendorSpecific}
-
-    private final EAP.AuthInfoID mAuthInfoID;
-    private final CredType mCredType;
-
-    public Credential(EAP.AuthInfoID infoID, int length, ByteBuffer payload)
-            throws ProtocolException {
-        if (length != 1) {
-            throw new ProtocolException("Bad length: " + length);
-        }
-
-        mAuthInfoID = infoID;
-        int typeID = payload.get() & BYTE_MASK;
-
-        mCredType = typeID < CredType.values().length ?
-                CredType.values()[typeID] :
-                CredType.Reserved;
-    }
-
-    @Override
-    public EAP.AuthInfoID getAuthInfoID() {
-        return mAuthInfoID;
-    }
-
-    @Override
-    public int hashCode() {
-        return mAuthInfoID.hashCode() * 31 + mCredType.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (thatObject == this) {
-            return true;
-        } else if (thatObject == null || thatObject.getClass() != Credential.class) {
-            return false;
-        } else {
-            return ((Credential) thatObject).getCredType() == getCredType();
-        }
-    }
-
-    public CredType getCredType() {
-        return mCredType;
-    }
-
-    @Override
-    public String toString() {
-        return "Auth method " + mAuthInfoID + " = " + mCredType + "\n";
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/EAP.java b/service/java/com/android/server/wifi/anqp/eap/EAP.java
deleted file mode 100644
index bdb88e4..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/EAP.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * EAP Related constants for the ANQP NAIRealm element, IEEE802.11-2012 section 8.4.4.10
- */
-public abstract class EAP {
-
-    private static final Map<Integer, EAPMethodID> sEapIds = new HashMap<>();
-    private static final Map<EAPMethodID, Integer> sRevEapIds = new HashMap<>();
-    private static final Map<Integer, AuthInfoID> sAuthIds = new HashMap<>();
-
-    public static final int EAP_MD5 = 4;
-    public static final int EAP_OTP = 5;
-    public static final int EAP_RSA = 9;
-    public static final int EAP_KEA = 11;
-    public static final int EAP_KEA_VALIDATE = 12;
-    public static final int EAP_TLS = 13;
-    public static final int EAP_LEAP = 17;
-    public static final int EAP_SIM = 18;
-    public static final int EAP_TTLS = 21;
-    public static final int EAP_AKA = 23;
-    public static final int EAP_3Com = 24;
-    public static final int EAP_MSCHAPv2 = 26;
-    public static final int EAP_PEAP = 29;
-    public static final int EAP_POTP = 32;
-    public static final int EAP_ActiontecWireless = 35;
-    public static final int EAP_HTTPDigest = 38;
-    public static final int EAP_SPEKE = 41;
-    public static final int EAP_MOBAC = 42;
-    public static final int EAP_FAST = 43;
-    public static final int EAP_ZLXEAP = 44;
-    public static final int EAP_Link = 45;
-    public static final int EAP_PAX = 46;
-    public static final int EAP_PSK = 47;
-    public static final int EAP_SAKE = 48;
-    public static final int EAP_IKEv2 = 49;
-    public static final int EAP_AKAPrim = 50;
-    public static final int EAP_GPSK = 51;
-    public static final int EAP_PWD = 52;
-    public static final int EAP_EKE = 53;
-    public static final int EAP_TEAP = 55;
-
-    public enum EAPMethodID {
-        EAP_MD5,
-        EAP_OTP,
-        EAP_RSA,
-        EAP_KEA,
-        EAP_KEA_VALIDATE,
-        EAP_TLS,
-        EAP_LEAP,
-        EAP_SIM,
-        EAP_TTLS,
-        EAP_AKA,
-        EAP_3Com,
-        EAP_MSCHAPv2,
-        EAP_PEAP,
-        EAP_POTP,
-        EAP_ActiontecWireless,
-        EAP_HTTPDigest,
-        EAP_SPEKE,
-        EAP_MOBAC,
-        EAP_FAST,
-        EAP_ZLXEAP,
-        EAP_Link,
-        EAP_PAX,
-        EAP_PSK,
-        EAP_SAKE,
-        EAP_IKEv2,
-        EAP_AKAPrim,
-        EAP_GPSK,
-        EAP_PWD,
-        EAP_EKE,
-        EAP_TEAP
-    }
-
-    public static final int ExpandedEAPMethod = 1;
-    public static final int NonEAPInnerAuthType = 2;
-    public static final int InnerAuthEAPMethodType = 3;
-    public static final int ExpandedInnerEAPMethod = 4;
-    public static final int CredentialType = 5;
-    public static final int TunneledEAPMethodCredType = 6;
-    public static final int VendorSpecific = 221;
-
-    public enum AuthInfoID {
-        Undefined,
-        ExpandedEAPMethod,
-        NonEAPInnerAuthType,
-        InnerAuthEAPMethodType,
-        ExpandedInnerEAPMethod,
-        CredentialType,
-        TunneledEAPMethodCredType,
-        VendorSpecific
-    }
-
-    static {
-        sEapIds.put(EAP_MD5, EAPMethodID.EAP_MD5);
-        sEapIds.put(EAP_OTP, EAPMethodID.EAP_OTP);
-        sEapIds.put(EAP_RSA, EAPMethodID.EAP_RSA);
-        sEapIds.put(EAP_KEA, EAPMethodID.EAP_KEA);
-        sEapIds.put(EAP_KEA_VALIDATE, EAPMethodID.EAP_KEA_VALIDATE);
-        sEapIds.put(EAP_TLS, EAPMethodID.EAP_TLS);
-        sEapIds.put(EAP_LEAP, EAPMethodID.EAP_LEAP);
-        sEapIds.put(EAP_SIM, EAPMethodID.EAP_SIM);
-        sEapIds.put(EAP_TTLS, EAPMethodID.EAP_TTLS);
-        sEapIds.put(EAP_AKA, EAPMethodID.EAP_AKA);
-        sEapIds.put(EAP_3Com, EAPMethodID.EAP_3Com);
-        sEapIds.put(EAP_MSCHAPv2, EAPMethodID.EAP_MSCHAPv2);
-        sEapIds.put(EAP_PEAP, EAPMethodID.EAP_PEAP);
-        sEapIds.put(EAP_POTP, EAPMethodID.EAP_POTP);
-        sEapIds.put(EAP_ActiontecWireless, EAPMethodID.EAP_ActiontecWireless);
-        sEapIds.put(EAP_HTTPDigest, EAPMethodID.EAP_HTTPDigest);
-        sEapIds.put(EAP_SPEKE, EAPMethodID.EAP_SPEKE);
-        sEapIds.put(EAP_MOBAC, EAPMethodID.EAP_MOBAC);
-        sEapIds.put(EAP_FAST, EAPMethodID.EAP_FAST);
-        sEapIds.put(EAP_ZLXEAP, EAPMethodID.EAP_ZLXEAP);
-        sEapIds.put(EAP_Link, EAPMethodID.EAP_Link);
-        sEapIds.put(EAP_PAX, EAPMethodID.EAP_PAX);
-        sEapIds.put(EAP_PSK, EAPMethodID.EAP_PSK);
-        sEapIds.put(EAP_SAKE, EAPMethodID.EAP_SAKE);
-        sEapIds.put(EAP_IKEv2, EAPMethodID.EAP_IKEv2);
-        sEapIds.put(EAP_AKAPrim, EAPMethodID.EAP_AKAPrim);
-        sEapIds.put(EAP_GPSK, EAPMethodID.EAP_GPSK);
-        sEapIds.put(EAP_PWD, EAPMethodID.EAP_PWD);
-        sEapIds.put(EAP_EKE, EAPMethodID.EAP_EKE);
-        sEapIds.put(EAP_TEAP, EAPMethodID.EAP_TEAP);
-
-        for (Map.Entry<Integer, EAPMethodID> entry : sEapIds.entrySet()) {
-            sRevEapIds.put(entry.getValue(), entry.getKey());
-        }
-
-        sAuthIds.put(ExpandedEAPMethod, AuthInfoID.ExpandedEAPMethod);
-        sAuthIds.put(NonEAPInnerAuthType, AuthInfoID.NonEAPInnerAuthType);
-        sAuthIds.put(InnerAuthEAPMethodType, AuthInfoID.InnerAuthEAPMethodType);
-        sAuthIds.put(ExpandedInnerEAPMethod, AuthInfoID.ExpandedInnerEAPMethod);
-        sAuthIds.put(CredentialType, AuthInfoID.CredentialType);
-        sAuthIds.put(TunneledEAPMethodCredType, AuthInfoID.TunneledEAPMethodCredType);
-        sAuthIds.put(VendorSpecific, AuthInfoID.VendorSpecific);
-    }
-
-    public static EAPMethodID mapEAPMethod(int methodID) {
-        return sEapIds.get(methodID);
-    }
-
-    public static Integer mapEAPMethod(EAPMethodID methodID) {
-        return sRevEapIds.get(methodID);
-    }
-
-    public static AuthInfoID mapAuthMethod(int methodID) {
-        return sAuthIds.get(methodID);
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/EAPMethod.java b/service/java/com/android/server/wifi/anqp/eap/EAPMethod.java
deleted file mode 100644
index 97c53a4..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/EAPMethod.java
+++ /dev/null
@@ -1,192 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.hotspot2.AuthMatch;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * An EAP Method, part of the NAI Realm ANQP element, specified in
- * IEEE802.11-2012 section 8.4.4.10, figure 8-420
- */
-public class EAPMethod {
-    private final EAP.EAPMethodID mEAPMethodID;
-    private final Map<EAP.AuthInfoID, Set<AuthParam>> mAuthParams;
-
-    public EAPMethod(ByteBuffer payload) throws ProtocolException {
-        if (payload.remaining() < 3) {
-            throw new ProtocolException("Runt EAP Method: " + payload.remaining());
-        }
-
-        int length = payload.get() & Constants.BYTE_MASK;
-        int methodID = payload.get() & Constants.BYTE_MASK;
-        int count = payload.get() & Constants.BYTE_MASK;
-
-        mEAPMethodID = EAP.mapEAPMethod(methodID);
-        mAuthParams = new EnumMap<>(EAP.AuthInfoID.class);
-
-        int realCount = 0;
-
-        ByteBuffer paramPayload = payload.duplicate().order(ByteOrder.LITTLE_ENDIAN);
-        paramPayload.limit(paramPayload.position() + length - 2);
-        payload.position(payload.position() + length - 2);
-        while (paramPayload.hasRemaining()) {
-            int id = paramPayload.get() & Constants.BYTE_MASK;
-
-            EAP.AuthInfoID authInfoID = EAP.mapAuthMethod(id);
-            if (authInfoID == null) {
-                throw new ProtocolException("Unknown auth parameter ID: " + id);
-            }
-
-            int len = paramPayload.get() & Constants.BYTE_MASK;
-            if (len == 0 || len > paramPayload.remaining()) {
-                throw new ProtocolException("Bad auth method length: " + len);
-            }
-
-            switch (authInfoID) {
-                case ExpandedEAPMethod:
-                    addAuthParam(new ExpandedEAPMethod(authInfoID, len, paramPayload));
-                    break;
-                case NonEAPInnerAuthType:
-                    addAuthParam(new NonEAPInnerAuth(len, paramPayload));
-                    break;
-                case InnerAuthEAPMethodType:
-                    addAuthParam(new InnerAuthEAP(len, paramPayload));
-                    break;
-                case ExpandedInnerEAPMethod:
-                    addAuthParam(new ExpandedEAPMethod(authInfoID, len, paramPayload));
-                    break;
-                case CredentialType:
-                    addAuthParam(new Credential(authInfoID, len, paramPayload));
-                    break;
-                case TunneledEAPMethodCredType:
-                    addAuthParam(new Credential(authInfoID, len, paramPayload));
-                    break;
-                case VendorSpecific:
-                    addAuthParam(new VendorSpecificAuth(len, paramPayload));
-                    break;
-            }
-
-            realCount++;
-        }
-        if (realCount != count)
-            throw new ProtocolException("Invalid parameter count: " + realCount +
-                    ", expected " + count);
-    }
-
-    public EAPMethod(EAP.EAPMethodID eapMethodID, AuthParam authParam) {
-        mEAPMethodID = eapMethodID;
-        mAuthParams = new HashMap<>(1);
-        if (authParam != null) {
-            Set<AuthParam> authParams = new HashSet<>();
-            authParams.add(authParam);
-            mAuthParams.put(authParam.getAuthInfoID(), authParams);
-        }
-    }
-
-    private void addAuthParam(AuthParam param) {
-        Set<AuthParam> authParams = mAuthParams.get(param.getAuthInfoID());
-        if (authParams == null) {
-            authParams = new HashSet<>();
-            mAuthParams.put(param.getAuthInfoID(), authParams);
-        }
-        authParams.add(param);
-    }
-
-    public Map<EAP.AuthInfoID, Set<AuthParam>> getAuthParams() {
-        return Collections.unmodifiableMap(mAuthParams);
-    }
-
-    public EAP.EAPMethodID getEAPMethodID() {
-        return mEAPMethodID;
-    }
-
-    public int match(com.android.server.wifi.hotspot2.pps.Credential credential) {
-
-        EAPMethod credMethod = credential.getEAPMethod();
-        if (mEAPMethodID != credMethod.getEAPMethodID()) {
-            return AuthMatch.None;
-        }
-
-        switch (mEAPMethodID) {
-            case EAP_TTLS:
-                if (mAuthParams.isEmpty()) {
-                    return AuthMatch.Method;
-                }
-                int paramCount = 0;
-                for (Map.Entry<EAP.AuthInfoID, Set<AuthParam>> entry :
-                        credMethod.getAuthParams().entrySet()) {
-                    Set<AuthParam> params = mAuthParams.get(entry.getKey());
-                    if (params == null) {
-                        continue;
-                    }
-
-                    if (!Collections.disjoint(params, entry.getValue())) {
-                        return AuthMatch.MethodParam;
-                    }
-                    paramCount += params.size();
-                }
-                return paramCount > 0 ? AuthMatch.None : AuthMatch.Method;
-            case EAP_TLS:
-                return AuthMatch.MethodParam;
-            case EAP_SIM:
-            case EAP_AKA:
-            case EAP_AKAPrim:
-                return AuthMatch.Method;
-            default:
-                return AuthMatch.Method;
-        }
-    }
-
-    public AuthParam getAuthParam() {
-        if (mAuthParams.isEmpty()) {
-            return null;
-        }
-        Set<AuthParam> params = mAuthParams.values().iterator().next();
-        if (params.isEmpty()) {
-            return null;
-        }
-        return params.iterator().next();
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (this == thatObject) {
-            return true;
-        }
-        else if (thatObject == null || getClass() != thatObject.getClass()) {
-            return false;
-        }
-
-        EAPMethod that = (EAPMethod) thatObject;
-        return mEAPMethodID == that.mEAPMethodID && mAuthParams.equals(that.mAuthParams);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mEAPMethodID.hashCode();
-        result = 31 * result + mAuthParams.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("EAP Method ").append(mEAPMethodID).append('\n');
-        for (Set<AuthParam> paramSet : mAuthParams.values()) {
-            for (AuthParam param : paramSet) {
-                sb.append("      ").append(param.toString());
-            }
-        }
-        return sb.toString();
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/ExpandedEAPMethod.java b/service/java/com/android/server/wifi/anqp/eap/ExpandedEAPMethod.java
deleted file mode 100644
index 95763a4..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/ExpandedEAPMethod.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-import static com.android.server.wifi.anqp.Constants.INT_MASK;
-import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
-
-/**
- * An EAP authentication parameter, IEEE802.11-2012, table 8-188
- */
-public class ExpandedEAPMethod implements AuthParam {
-
-    private final EAP.AuthInfoID mAuthInfoID;
-    private final int mVendorID;
-    private final long mVendorType;
-
-    public ExpandedEAPMethod(EAP.AuthInfoID authInfoID, int length, ByteBuffer payload)
-            throws ProtocolException {
-        if (length != 7) {
-            throw new ProtocolException("Bad length: " + payload.remaining());
-        }
-
-        mAuthInfoID = authInfoID;
-
-        ByteBuffer vndBuffer = payload.duplicate().order(ByteOrder.BIG_ENDIAN);
-
-        int id = vndBuffer.getShort() & SHORT_MASK;
-        id = (id << Byte.SIZE) | (vndBuffer.get() & BYTE_MASK);
-        mVendorID = id;
-        mVendorType = vndBuffer.getInt() & INT_MASK;
-
-        payload.position(payload.position()+7);
-    }
-
-    public ExpandedEAPMethod(EAP.AuthInfoID authInfoID, int vendorID, long vendorType) {
-        mAuthInfoID = authInfoID;
-        mVendorID = vendorID;
-        mVendorType = vendorType;
-    }
-
-    @Override
-    public EAP.AuthInfoID getAuthInfoID() {
-        return mAuthInfoID;
-    }
-
-    @Override
-    public int hashCode() {
-        return (mAuthInfoID.hashCode() * 31 + mVendorID) * 31 + (int) mVendorType;
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (thatObject == this) {
-            return true;
-        } else if (thatObject == null || thatObject.getClass() != ExpandedEAPMethod.class) {
-            return false;
-        } else {
-            ExpandedEAPMethod that = (ExpandedEAPMethod) thatObject;
-            return that.getVendorID() == getVendorID() && that.getVendorType() == getVendorType();
-        }
-    }
-
-    public int getVendorID() {
-        return mVendorID;
-    }
-
-    public long getVendorType() {
-        return mVendorType;
-    }
-
-    @Override
-    public String toString() {
-        return "Auth method " + mAuthInfoID + ", id " + mVendorID + ", type " + mVendorType + "\n";
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/InnerAuthEAP.java b/service/java/com/android/server/wifi/anqp/eap/InnerAuthEAP.java
deleted file mode 100644
index a5bc4f1..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/InnerAuthEAP.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-
-/**
- * An EAP authentication parameter, IEEE802.11-2012, table 8-188
- */
-public class InnerAuthEAP implements AuthParam {
-
-    private final EAP.EAPMethodID mEapMethodID;
-
-    public InnerAuthEAP(int length, ByteBuffer payload) throws ProtocolException {
-        if (length != 1) {
-            throw new ProtocolException("Bad length: " + length);
-        }
-        int typeID = payload.get() & BYTE_MASK;
-        mEapMethodID = EAP.mapEAPMethod(typeID);
-    }
-
-    public InnerAuthEAP(EAP.EAPMethodID eapMethodID) {
-        mEapMethodID = eapMethodID;
-    }
-
-    @Override
-    public EAP.AuthInfoID getAuthInfoID() {
-        return EAP.AuthInfoID.InnerAuthEAPMethodType;
-    }
-
-    public EAP.EAPMethodID getEAPMethodID() {
-        return mEapMethodID;
-    }
-
-    @Override
-    public int hashCode() {
-        return mEapMethodID != null ? mEapMethodID.hashCode() : 0;
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (thatObject == this) {
-            return true;
-        } else if (thatObject == null || thatObject.getClass() != InnerAuthEAP.class) {
-            return false;
-        } else {
-            return ((InnerAuthEAP) thatObject).getEAPMethodID() == getEAPMethodID();
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "Auth method InnerAuthEAP, inner = " + mEapMethodID + '\n';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/NonEAPInnerAuth.java b/service/java/com/android/server/wifi/anqp/eap/NonEAPInnerAuth.java
deleted file mode 100644
index a96124a..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/NonEAPInnerAuth.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-
-/**
- * An EAP authentication parameter, IEEE802.11-2012, table 8-188
- */
-public class NonEAPInnerAuth implements AuthParam {
-
-    public enum NonEAPType {Reserved, PAP, CHAP, MSCHAP, MSCHAPv2}
-    private static final Map<NonEAPType, String> sOmaMap = new EnumMap<>(NonEAPType.class);
-    private static final Map<String, NonEAPType> sRevOmaMap = new HashMap<>();
-
-    private final NonEAPType mType;
-
-    static {
-        sOmaMap.put(NonEAPType.PAP, "PAP");
-        sOmaMap.put(NonEAPType.CHAP, "CHAP");
-        sOmaMap.put(NonEAPType.MSCHAP, "MS-CHAP");
-        sOmaMap.put(NonEAPType.MSCHAPv2, "MS-CHAP-V2");
-
-        for (Map.Entry<NonEAPType, String> entry : sOmaMap.entrySet()) {
-            sRevOmaMap.put(entry.getValue(), entry.getKey());
-        }
-    }
-
-    public NonEAPInnerAuth(int length, ByteBuffer payload) throws ProtocolException {
-        if (length != 1) {
-            throw new ProtocolException("Bad length: " + payload.remaining());
-        }
-
-        int typeID = payload.get() & BYTE_MASK;
-        mType = typeID < NonEAPType.values().length ?
-                NonEAPType.values()[typeID] :
-                NonEAPType.Reserved;
-    }
-
-    public NonEAPInnerAuth(NonEAPType type) {
-        mType = type;
-    }
-
-    /**
-     * Construct from the OMA-DM PPS data
-     * @param eapType as defined in the HS2.0 spec.
-     */
-    public NonEAPInnerAuth(String eapType) {
-        mType = sRevOmaMap.get(eapType);
-    }
-
-    @Override
-    public EAP.AuthInfoID getAuthInfoID() {
-        return EAP.AuthInfoID.NonEAPInnerAuthType;
-    }
-
-    public NonEAPType getType() {
-        return mType;
-    }
-
-    public String getOMAtype() {
-        return sOmaMap.get(mType);
-    }
-
-    public static String mapInnerType(NonEAPType type) {
-        return sOmaMap.get(type);
-    }
-
-    @Override
-    public int hashCode() {
-        return mType.hashCode();
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (thatObject == this) {
-            return true;
-        } else if (thatObject == null || thatObject.getClass() != NonEAPInnerAuth.class) {
-            return false;
-        } else {
-            return ((NonEAPInnerAuth) thatObject).getType() == getType();
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "Auth method NonEAPInnerAuthEAP, inner = " + mType + '\n';
-    }
-}
diff --git a/service/java/com/android/server/wifi/anqp/eap/VendorSpecificAuth.java b/service/java/com/android/server/wifi/anqp/eap/VendorSpecificAuth.java
deleted file mode 100644
index 1d94192..0000000
--- a/service/java/com/android/server/wifi/anqp/eap/VendorSpecificAuth.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.android.server.wifi.anqp.eap;
-
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-/**
- * An EAP authentication parameter, IEEE802.11-2012, table 8-188
- */
-public class VendorSpecificAuth implements AuthParam {
-
-    private final byte[] mData;
-
-    public VendorSpecificAuth(int length, ByteBuffer payload) throws ProtocolException {
-        mData = new byte[length];
-        payload.get(mData);
-    }
-
-    @Override
-    public EAP.AuthInfoID getAuthInfoID() {
-        return EAP.AuthInfoID.VendorSpecific;
-    }
-
-    public int hashCode() {
-        return Arrays.hashCode(mData);
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (thatObject == this) {
-            return true;
-        } else if (thatObject == null || thatObject.getClass() != VendorSpecificAuth.class) {
-            return false;
-        } else {
-            return Arrays.equals(((VendorSpecificAuth) thatObject).getData(), getData());
-        }
-    }
-
-    public byte[] getData() {
-        return mData;
-    }
-
-    @Override
-    public String toString() {
-        return "Auth method VendorSpecificAuth, data = " + Arrays.toString(mData) + '\n';
-    }
-}
diff --git a/service/java/com/android/server/wifi/aware/Capabilities.java b/service/java/com/android/server/wifi/aware/Capabilities.java
new file mode 100644
index 0000000..63076a8
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/Capabilities.java
@@ -0,0 +1,70 @@
+/*
+ * 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.aware;
+
+import android.net.wifi.aware.Characteristics;
+import android.os.Bundle;
+
+/**
+ * A container class for Aware (vendor) implementation capabilities (or
+ * limitations). Filled-in by the firmware.
+ */
+public class Capabilities {
+    public int maxConcurrentAwareClusters;
+    public int maxPublishes;
+    public int maxSubscribes;
+    public int maxServiceNameLen;
+    public int maxMatchFilterLen;
+    public int maxTotalMatchFilterLen;
+    public int maxServiceSpecificInfoLen;
+    public int maxExtendedServiceSpecificInfoLen;
+    public int maxNdiInterfaces;
+    public int maxNdpSessions;
+    public int maxAppInfoLen;
+    public int maxQueuedTransmitMessages;
+    public int maxSubscribeInterfaceAddresses;
+    public int supportedCipherSuites;
+
+    /**
+     * Converts the internal capabilities to a parcelable & potentially app-facing
+     * characteristics bundle. Only some of the information is exposed.
+     */
+    public Characteristics toPublicCharacteristics() {
+        Bundle bundle = new Bundle();
+        bundle.putInt(Characteristics.KEY_MAX_SERVICE_NAME_LENGTH, maxServiceNameLen);
+        bundle.putInt(Characteristics.KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH,
+                maxServiceSpecificInfoLen);
+        bundle.putInt(Characteristics.KEY_MAX_MATCH_FILTER_LENGTH, maxMatchFilterLen);
+        return new Characteristics(bundle);
+    }
+
+    @Override
+    public String toString() {
+        return "Capabilities [maxConcurrentAwareClusters=" + maxConcurrentAwareClusters
+                + ", maxPublishes=" + maxPublishes + ", maxSubscribes=" + maxSubscribes
+                + ", maxServiceNameLen=" + maxServiceNameLen + ", maxMatchFilterLen="
+                + maxMatchFilterLen + ", maxTotalMatchFilterLen=" + maxTotalMatchFilterLen
+                + ", maxServiceSpecificInfoLen=" + maxServiceSpecificInfoLen
+                + ", maxExtendedServiceSpecificInfoLen=" + maxExtendedServiceSpecificInfoLen
+                + ", maxNdiInterfaces=" + maxNdiInterfaces + ", maxNdpSessions="
+                + maxNdpSessions + ", maxAppInfoLen=" + maxAppInfoLen
+                + ", maxQueuedTransmitMessages=" + maxQueuedTransmitMessages
+                + ", maxSubscribeInterfaceAddresses=" + maxSubscribeInterfaceAddresses
+                + ", supportedCipherSuites=" + supportedCipherSuites
+                + "]";
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/OWNERS b/service/java/com/android/server/wifi/aware/OWNERS
new file mode 100644
index 0000000..cf116f8
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/OWNERS
@@ -0,0 +1 @@
+etancohen@google.com
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareClientState.java b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
new file mode 100644
index 0000000..7123d01
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
@@ -0,0 +1,315 @@
+/*
+ * 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.aware;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.RttManager;
+import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * Manages the service-side Aware state of an individual "client". A client
+ * corresponds to a single instantiation of the WifiAwareManager - 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 Aware-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 WifiAwareClientState {
+    private static final String TAG = "WifiAwareClientState";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    /* package */ static final int CLUSTER_CHANGE_EVENT_STARTED = 0;
+    /* package */ static final int CLUSTER_CHANGE_EVENT_JOINED = 1;
+
+    private final Context mContext;
+    private final IWifiAwareEventCallback mCallback;
+    private final SparseArray<WifiAwareDiscoverySessionState> mSessions = new SparseArray<>();
+
+    private final int mClientId;
+    private ConfigRequest mConfigRequest;
+    private final int mUid;
+    private final int mPid;
+    private final String mCallingPackage;
+    private final boolean mNotifyIdentityChange;
+
+    private AppOpsManager mAppOps;
+
+    private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
+    private byte[] mLastDiscoveryInterfaceMac = ALL_ZERO_MAC;
+
+    public WifiAwareClientState(Context context, int clientId, int uid, int pid,
+            String callingPackage, IWifiAwareEventCallback callback, ConfigRequest configRequest,
+            boolean notifyIdentityChange) {
+        mContext = context;
+        mClientId = clientId;
+        mUid = uid;
+        mPid = pid;
+        mCallingPackage = callingPackage;
+        mCallback = callback;
+        mConfigRequest = configRequest;
+        mNotifyIdentityChange = notifyIdentityChange;
+
+        mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+    }
+
+    /**
+     * Destroy the current client - corresponds to a disconnect() request from
+     * the client. Destroys all discovery sessions belonging to this client.
+     */
+    public void destroy() {
+        for (int i = 0; i < mSessions.size(); ++i) {
+            mSessions.valueAt(i).terminate();
+        }
+        mSessions.clear();
+        mConfigRequest = null;
+    }
+
+    public ConfigRequest getConfigRequest() {
+        return mConfigRequest;
+    }
+
+    public int getClientId() {
+        return mClientId;
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public boolean getNotifyIdentityChange() {
+        return mNotifyIdentityChange;
+    }
+
+    /**
+     * 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 Aware session corresponding to the requested ID.
+     */
+    public WifiAwareDiscoverySessionState getAwareSessionStateForPubSubId(int pubSubId) {
+        for (int i = 0; i < mSessions.size(); ++i) {
+            WifiAwareDiscoverySessionState session = mSessions.valueAt(i);
+            if (session.isPubSubIdSession(pubSubId)) {
+                return session;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Add the session to the client database.
+     *
+     * @param session Session to be added.
+     */
+    public void addSession(WifiAwareDiscoverySessionState session) {
+        int sessionId = session.getSessionId();
+        if (mSessions.get(sessionId) != null) {
+            Log.w(TAG, "createSession: sessionId already exists (replaced) - " + sessionId);
+        }
+
+        mSessions.put(sessionId, session);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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) {
+        WifiAwareDiscoverySessionState 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 WifiAwareDiscoverySessionState getSession(int sessionId) {
+        return mSessions.get(sessionId);
+    }
+
+    /**
+     * Called to dispatch the Aware 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 (VDBG) {
+            Log.v(TAG,
+                    "onInterfaceAddressChange: mClientId=" + mClientId + ", mNotifyIdentityChange="
+                            + mNotifyIdentityChange + ", mac=" + String.valueOf(
+                            HexEncoding.encode(mac)) + ", mLastDiscoveryInterfaceMac="
+                            + String.valueOf(HexEncoding.encode(mLastDiscoveryInterfaceMac)));
+        }
+        if (mNotifyIdentityChange && !Arrays.equals(mac, mLastDiscoveryInterfaceMac)) {
+            try {
+                boolean hasPermission = hasLocationingPermission();
+                if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission);
+                mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
+            }
+        }
+
+        mLastDiscoveryInterfaceMac = mac;
+    }
+
+    /**
+     * Called to dispatch the Aware 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 (VDBG) {
+            Log.v(TAG,
+                    "onClusterChange: mClientId=" + mClientId + ", mNotifyIdentityChange="
+                            + mNotifyIdentityChange + ", mac=" + String.valueOf(
+                            HexEncoding.encode(mac)) + ", currentDiscoveryInterfaceMac="
+                            + String.valueOf(HexEncoding.encode(currentDiscoveryInterfaceMac))
+                            + ", mLastDiscoveryInterfaceMac=" + String.valueOf(
+                            HexEncoding.encode(mLastDiscoveryInterfaceMac)));
+        }
+        if (mNotifyIdentityChange && !Arrays.equals(currentDiscoveryInterfaceMac,
+                mLastDiscoveryInterfaceMac)) {
+            try {
+                boolean hasPermission = hasLocationingPermission();
+                if (VDBG) Log.v(TAG, "hasPermission=" + hasPermission);
+                mCallback.onIdentityChanged(hasPermission ? mac : ALL_ZERO_MAC);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
+            }
+        }
+
+        mLastDiscoveryInterfaceMac = currentDiscoveryInterfaceMac;
+    }
+
+    private boolean hasLocationingPermission() {
+        // FINE provides COARSE, so only have to check for the latter
+        return mContext.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, mPid, mUid)
+                == PackageManager.PERMISSION_GRANTED && mAppOps.noteOp(
+                AppOpsManager.OP_COARSE_LOCATION, mUid, mCallingPackage)
+                == AppOpsManager.MODE_ALLOWED;
+    }
+
+    /**
+     * 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("AwareClientState:");
+        pw.println("  mClientId: " + mClientId);
+        pw.println("  mConfigRequest: " + mConfigRequest);
+        pw.println("  mNotifyIdentityChange: " + mNotifyIdentityChange);
+        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/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
new file mode 100644
index 0000000..14d855e
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -0,0 +1,1003 @@
+/*
+ * 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.aware;
+
+import android.content.Context;
+import android.hardware.wifi.V1_0.NanDataPathChannelCfg;
+import android.net.ConnectivityManager;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.net.RouteInfo;
+import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import libcore.util.HexEncoding;
+
+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.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages Aware data-path lifetime: interface creation/deletion, data-path setup and tear-down.
+ * The Aware network configuration is:
+ * - transport = TRANSPORT_WIFI_AWARE
+ * - capabilities = NET_CAPABILITY_NOT_VPN
+ * - network specifier generated by DiscoverySession.createNetworkSpecifier(...) or
+ *   WifiAwareManager.createNetworkSpecifier(...).
+ */
+public class WifiAwareDataPathStateManager {
+    private static final String TAG = "WifiAwareDataPathStMgr";
+
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private static final String AWARE_INTERFACE_PREFIX = "aware_data";
+    private static final String NETWORK_TAG = "WIFI_AWARE_FACTORY";
+    private static final String AGENT_TAG_PREFIX = "WIFI_AWARE_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 WifiAwareStateManager mMgr;
+    private final NetworkInterfaceWrapper mNiWrapper = new NetworkInterfaceWrapper();
+    private final NetworkCapabilities mNetworkCapabilitiesFilter = new NetworkCapabilities();
+    private final Set<String> mInterfaces = new HashSet<>();
+    private final Map<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
+            mNetworkRequestsCache = new ArrayMap<>();
+    private Context mContext;
+    private Looper mLooper;
+    private WifiAwareNetworkFactory mNetworkFactory;
+    private INetworkManagementService mNwService;
+
+    public WifiAwareDataPathStateManager(WifiAwareStateManager mgr) {
+        mMgr = mgr;
+    }
+
+    /**
+     * Initialize the Aware 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_AWARE);
+        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(new MatchAllNetworkSpecifier());
+        mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
+        mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(NETWORK_FACTORY_BANDWIDTH_AVAIL);
+        mNetworkCapabilitiesFilter.setSignalStrength(NETWORK_FACTORY_SIGNAL_STRENGTH_AVAIL);
+
+        mNetworkFactory = new WifiAwareNetworkFactory(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<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation>
+                getNetworkRequestByNdpId(int ndpId) {
+        for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry :
+                mNetworkRequestsCache.entrySet()) {
+            if (entry.getValue().ndpId == ndpId) {
+                return entry;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Create all Aware 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.getCapabilities() == null) {
+            Log.e(TAG, "createAllInterfaces: capabilities aren't initialized yet!");
+            return;
+        }
+
+        for (int i = 0; i < mMgr.getCapabilities().maxNdiInterfaces; ++i) {
+            String name = AWARE_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 Aware 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(WifiAwareNetworkSpecifier networkSpecifier, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onDataPathInitiateSuccess: networkSpecifier=" + networkSpecifier + ", ndpId="
+                            + ndpId);
+        }
+
+        AwareNetworkRequestInformation 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
+                != AwareNetworkRequestInformation.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 = AwareNetworkRequestInformation.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(WifiAwareNetworkSpecifier networkSpecifier, int reason) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onDataPathInitiateFail: networkSpecifier=" + networkSpecifier + ", reason="
+                            + reason);
+        }
+
+        AwareNetworkRequestInformation nnri = mNetworkRequestsCache.remove(networkSpecifier);
+        if (nnri == null) {
+            Log.w(TAG, "onDataPathInitiateFail: network request not found for networkSpecifier="
+                    + networkSpecifier);
+            return;
+        }
+
+        if (nnri.state
+                != AwareNetworkRequestInformation.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.
+     * @return The network specifier of the data-path (or null if none/error)
+     */
+    public WifiAwareNetworkSpecifier onDataPathRequest(int pubSubId, byte[] mac, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onDataPathRequest: pubSubId=" + pubSubId + ", mac=" + String.valueOf(
+                            HexEncoding.encode(mac)) + ", ndpId=" + ndpId);
+        }
+
+        WifiAwareNetworkSpecifier networkSpecifier = null;
+        AwareNetworkRequestInformation nnri = null;
+        for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> 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 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().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)));
+            if (DBG) {
+                Log.d(TAG, "onDataPathRequest: network request cache = " + mNetworkRequestsCache);
+            }
+            mMgr.respondToDataPathRequest(false, ndpId, "", null, null);
+            return null;
+        }
+
+        if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
+            Log.w(TAG, "onDataPathRequest: request " + networkSpecifier + " is incorrect state="
+                    + nnri.state);
+            mMgr.respondToDataPathRequest(false, ndpId, "", null, null);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            return null;
+        }
+
+        nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE;
+        nnri.ndpId = ndpId;
+        nnri.interfaceName = selectInterfaceForRequest(nnri);
+        mMgr.respondToDataPathRequest(true, ndpId, nnri.interfaceName, nnri.networkSpecifier.pmk,
+                nnri.networkSpecifier.passphrase);
+
+        return networkSpecifier;
+    }
+
+    /**
+     * Called on the RESPONDER when the response to data-path request has been completed.
+     *
+     * @param ndpId The ID of the data-path (NDP)
+     * @param success Whether or not the 'RespondToDataPathRequest' operation was a success.
+     */
+    public void onRespondToDataPathRequest(int ndpId, boolean success) {
+        if (VDBG) {
+            Log.v(TAG, "onRespondToDataPathRequest: ndpId=" + ndpId + ", success=" + success);
+        }
+
+        WifiAwareNetworkSpecifier networkSpecifier = null;
+        AwareNetworkRequestInformation nnri = null;
+        for (Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> entry :
+                mNetworkRequestsCache.entrySet()) {
+            if (entry.getValue().ndpId == ndpId) {
+                networkSpecifier = entry.getKey();
+                nnri = entry.getValue();
+                break;
+            }
+        }
+
+        if (nnri == null) {
+            Log.w(TAG, "onRespondToDataPathRequest: can't find a request with specified ndpId="
+                    + ndpId);
+            if (DBG) {
+                Log.d(TAG, "onRespondToDataPathRequest: network request cache = "
+                        + mNetworkRequestsCache);
+            }
+            return;
+        }
+
+        if (!success) {
+            Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier
+                    + " failed responding");
+            mMgr.endDataPath(ndpId);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            return;
+        }
+
+        if (nnri.state
+                != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE) {
+            Log.w(TAG, "onRespondToDataPathRequest: request " + networkSpecifier
+                    + " is incorrect state=" + nnri.state);
+            mMgr.endDataPath(ndpId);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            return;
+        }
+
+        nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_CONFIRM;
+    }
+
+    /**
+     * 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.
+     * @return The network specifier of the data-path or a null if none/error.
+     */
+    public WifiAwareNetworkSpecifier onDataPathConfirm(int ndpId, byte[] mac, boolean accept,
+            int reason, byte[] message) {
+        if (VDBG) {
+            Log.v(TAG, "onDataPathConfirm: ndpId=" + ndpId + ", mac=" + String.valueOf(
+                    HexEncoding.encode(mac)) + ", accept=" + accept + ", reason=" + reason);
+        }
+
+        Map.Entry<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> nnriE =
+                getNetworkRequestByNdpId(ndpId);
+        if (nnriE == null) {
+            Log.w(TAG, "onDataPathConfirm: network request not found for ndpId=" + ndpId);
+            if (accept) {
+                mMgr.endDataPath(ndpId);
+            }
+            return null;
+        }
+
+        WifiAwareNetworkSpecifier networkSpecifier = nnriE.getKey();
+        AwareNetworkRequestInformation nnri = nnriE.getValue();
+
+        // validate state
+        if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                && nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_CONFIRM) {
+            Log.w(TAG, "onDataPathConfirm: INITIATOR in invalid state=" + nnri.state);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            if (accept) {
+                mMgr.endDataPath(ndpId);
+            }
+            return networkSpecifier;
+        }
+        if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER
+                && nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_CONFIRM) {
+            Log.w(TAG, "onDataPathConfirm: RESPONDER in invalid state=" + nnri.state);
+            mNetworkRequestsCache.remove(networkSpecifier);
+            if (accept) {
+                mMgr.endDataPath(ndpId);
+            }
+            return networkSpecifier;
+        }
+
+        if (accept) {
+            nnri.state = (nnri.networkSpecifier.role
+                    == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)
+                    ? AwareNetworkRequestInformation.STATE_INITIATOR_CONFIRMED
+                    : AwareNetworkRequestInformation.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 {
+                mNwService.setInterfaceUp(nnri.interfaceName);
+                mNwService.enableIpv6(nnri.interfaceName);
+            } catch (Exception e) { // NwService throws runtime exceptions for errors
+                Log.e(TAG, "onDataPathConfirm: ACCEPT nnri=" + nnri + ": can't configure network - "
+                        + e);
+                mMgr.endDataPath(ndpId);
+                return networkSpecifier;
+            }
+
+            if (!mNiWrapper.configureAgentProperties(nnri, networkSpecifier, ndpId, networkInfo,
+                    networkCapabilities, linkProperties)) {
+                return networkSpecifier;
+            }
+
+            nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext,
+                    AGENT_TAG_PREFIX + nnri.ndpId,
+                    new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORK_TAG, ""),
+                    networkCapabilities, linkProperties, NETWORK_FACTORY_SCORE_AVAIL,
+                    networkSpecifier, ndpId);
+            nnri.networkAgent.sendNetworkInfo(networkInfo);
+        } 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<WifiAwareNetworkSpecifier, AwareNetworkRequestInformation> 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 Aware comes down. Clean up all pending and up network requeests and agents.
+     */
+    public void onAwareDownCleanupDataPaths() {
+        if (VDBG) Log.v(TAG, "onAwareDownCleanupDataPaths");
+
+        for (AwareNetworkRequestInformation 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(NetworkSpecifier networkSpecifier) {
+        if (VDBG) Log.v(TAG, "handleDataPathTimeout: networkSpecifier=" + networkSpecifier);
+
+        AwareNetworkRequestInformation 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 WifiAwareNetworkFactory extends NetworkFactory {
+        WifiAwareNetworkFactory(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, "WifiAwareNetworkFactory.acceptRequest: request=" + request + ", score="
+                        + score);
+            }
+
+            if (!mMgr.isUsageEnabled()) {
+                if (VDBG) {
+                    Log.v(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+                            + " -- Aware disabled");
+                }
+                return false;
+            }
+
+            if (mInterfaces.isEmpty()) {
+                Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+                        + " -- No Aware interfaces are up");
+                return false;
+            }
+
+            NetworkSpecifier networkSpecifierBase =
+                    request.networkCapabilities.getNetworkSpecifier();
+            if (!(networkSpecifierBase instanceof WifiAwareNetworkSpecifier)) {
+                Log.w(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+                        + " - not a WifiAwareNetworkSpecifier");
+                return false;
+            }
+
+            WifiAwareNetworkSpecifier networkSpecifier =
+                    (WifiAwareNetworkSpecifier) networkSpecifierBase;
+
+            // look up specifier - are we being called again?
+            AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
+            if (nnri != null) {
+                if (DBG) {
+                    Log.d(TAG, "WifiAwareNetworkFactory.acceptRequest: request=" + request
+                            + " - already in cache!?");
+                }
+
+                // seems to happen after a network agent is created - trying to rematch all
+                // requests again!?
+                return true;
+            }
+
+            // TODO: validate that the client ID actually comes from the correct process and is
+            // not faked?
+            nnri = AwareNetworkRequestInformation.processNetworkSpecifier(networkSpecifier, mMgr);
+            if (nnri == null) {
+                Log.e(TAG, "WifiAwareNetworkFactory.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, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
+                        + networkRequest + ", score=" + score);
+            }
+
+            NetworkSpecifier networkSpecifierObj =
+                    networkRequest.networkCapabilities.getNetworkSpecifier();
+            WifiAwareNetworkSpecifier networkSpecifier = null;
+            if (networkSpecifierObj instanceof WifiAwareNetworkSpecifier) {
+                networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierObj;
+            }
+            AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
+            if (nnri == null) {
+                Log.e(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
+                        + networkRequest + " not in cache!?");
+                return;
+            }
+
+            if (nnri.networkSpecifier.role
+                    == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
+                if (nnri.state != AwareNetworkRequestInformation.STATE_INITIATOR_IDLE) {
+                    if (DBG) {
+                        Log.d(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
+                                + networkRequest + " - already in progress");
+                        // TODO: understand how/when can be called again/while in progress (seems
+                        // to be related to score re-calculation after a network agent is created)
+                    }
+                    return;
+                }
+
+                nnri.interfaceName = selectInterfaceForRequest(nnri);
+                mMgr.initiateDataPathSetup(networkSpecifier, nnri.networkSpecifier.peerId,
+                        NanDataPathChannelCfg.REQUEST_CHANNEL_SETUP, selectChannelForRequest(nnri),
+                        nnri.peerDiscoveryMac, nnri.interfaceName, nnri.networkSpecifier.pmk,
+                        nnri.networkSpecifier.passphrase);
+                nnri.state =
+                        AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE;
+            } else {
+                if (nnri.state != AwareNetworkRequestInformation.STATE_RESPONDER_IDLE) {
+                    if (DBG) {
+                        Log.d(TAG, "WifiAwareNetworkFactory.needNetworkFor: networkRequest="
+                                + networkRequest + " - already in progress");
+                        // TODO: understand how/when can be called again/while in progress (seems
+                        // to be related to score re-calculation after a network agent is created)
+                    }
+                    return;
+                }
+
+                nnri.state = AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST;
+            }
+        }
+
+        @Override
+        protected void releaseNetworkFor(NetworkRequest networkRequest) {
+            if (VDBG) {
+                Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest="
+                        + networkRequest);
+            }
+
+            NetworkSpecifier networkSpecifierObj =
+                    networkRequest.networkCapabilities.getNetworkSpecifier();
+            WifiAwareNetworkSpecifier networkSpecifier = null;
+            if (networkSpecifierObj instanceof WifiAwareNetworkSpecifier) {
+                networkSpecifier = (WifiAwareNetworkSpecifier) networkSpecifierObj;
+            }
+
+            AwareNetworkRequestInformation nnri = mNetworkRequestsCache.get(networkSpecifier);
+            if (nnri == null) {
+                Log.e(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest="
+                        + networkRequest + " not in cache!?");
+                return;
+            }
+
+            if (nnri.networkAgent != null) {
+                if (VDBG) {
+                    Log.v(TAG, "WifiAwareNetworkFactory.releaseNetworkFor: networkRequest="
+                            + networkRequest + ", nnri=" + nnri
+                            + ": agent already created - deferring ending data-path to agent"
+                            + ".unwanted()");
+                }
+                return;
+            }
+
+            if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                    && nnri.state
+                    > AwareNetworkRequestInformation.STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE) {
+                mMgr.endDataPath(nnri.ndpId);
+            }
+            if (nnri.networkSpecifier.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER
+                    && nnri.state
+                    > AwareNetworkRequestInformation.STATE_RESPONDER_WAIT_FOR_REQUEST) {
+                mMgr.endDataPath(nnri.ndpId);
+            }
+
+            // Will get a callback (on both initiator and responder) when data-path actually
+            // terminated. At that point will inform the agent and will clear the cache.
+        }
+    }
+
+    private class WifiAwareNetworkAgent extends NetworkAgent {
+        private NetworkInfo mNetworkInfo;
+        private WifiAwareNetworkSpecifier mNetworkSpecifier;
+        private int mNdpId;
+
+        WifiAwareNetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+                NetworkCapabilities nc, LinkProperties lp, int score,
+                WifiAwareNetworkSpecifier 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, "WifiAwareNetworkAgent.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, "WifiAwareNetworkAgent.reconfigureAgentAsDisconnected: networkSpecifier="
+                        + mNetworkSpecifier + ", ndpId=" + mNdpId);
+            }
+
+            mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, "");
+            sendNetworkInfo(mNetworkInfo);
+        }
+    }
+
+    private void tearDownInterface(AwareNetworkRequestInformation nnri) {
+        if (VDBG) Log.v(TAG, "tearDownInterface: nnri=" + nnri);
+
+        if (nnri.interfaceName != null && !nnri.interfaceName.isEmpty()) {
+            try {
+                mNwService.setInterfaceDown(nnri.interfaceName);
+            } catch (Exception e) { // NwService throws runtime exceptions for errors
+                Log.e(TAG,
+                        "tearDownInterface: nnri=" + nnri + ": can't bring interface down - " + e);
+            }
+        }
+
+        if (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(AwareNetworkRequestInformation 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(AwareNetworkRequestInformation req) {
+        return 2437;
+    }
+
+    /**
+     * Aware network request. State object: contains network request information/state through its
+     * lifetime.
+     */
+    @VisibleForTesting
+    public static class AwareNetworkRequestInformation {
+        static final int STATE_INITIATOR_IDLE = 100;
+        static final int STATE_INITIATOR_WAIT_FOR_REQUEST_RESPONSE = 101;
+        static final int STATE_INITIATOR_WAIT_FOR_CONFIRM = 102;
+        static final int STATE_INITIATOR_CONFIRMED = 103;
+
+        static final int STATE_RESPONDER_IDLE = 200;
+        static final int STATE_RESPONDER_WAIT_FOR_REQUEST = 201;
+        static final int STATE_RESPONDER_WAIT_FOR_RESPOND_RESPONSE = 202;
+        static final int STATE_RESPONDER_WAIT_FOR_CONFIRM = 203;
+        static final int STATE_RESPONDER_CONFIRMED = 204;
+
+        public int state;
+
+        public int uid;
+        public String interfaceName;
+        public int pubSubId = 0;
+        public byte[] peerDiscoveryMac = null;
+        public int ndpId;
+        public byte[] peerDataMac;
+        public WifiAwareNetworkSpecifier networkSpecifier;
+
+        public WifiAwareNetworkAgent networkAgent;
+
+        static AwareNetworkRequestInformation processNetworkSpecifier(WifiAwareNetworkSpecifier ns,
+                WifiAwareStateManager mgr) {
+            int uid, pubSubId = 0;
+            byte[] peerMac = ns.peerMac;
+
+            if (VDBG) {
+                Log.v(TAG, "processNetworkSpecifier: networkSpecifier=" + ns);
+            }
+
+            // type: always valid
+            if (ns.type < 0
+                    || ns.type > WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_MAX_VALID) {
+                Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
+                        + ", invalid 'type' value");
+                return null;
+            }
+
+            // role: always valid
+            if (ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                    && ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
+                Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
+                        + " -- invalid 'role' value");
+                return null;
+            }
+
+            if (ns.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                    && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB
+                    && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB) {
+                Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
+                        + " -- invalid 'type' value for INITIATOR (only IB and OOB are "
+                        + "permitted)");
+                return null;
+            }
+
+            // look up network specifier information in Aware state manager
+            WifiAwareClientState client = mgr.getClient(ns.clientId);
+            if (client == null) {
+                Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
+                        + " -- not client with this id -- clientId=" + ns.clientId);
+                return null;
+            }
+            uid = client.getUid();
+
+            // validate the role (if session ID provided: i.e. session 1xx)
+            if (ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB
+                    || ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER) {
+                WifiAwareDiscoverySessionState session = client.getSession(ns.sessionId);
+                if (session == null) {
+                    Log.e(TAG,
+                            "processNetworkSpecifier: networkSpecifier=" + ns
+                                    + " -- no session with this id -- sessionId=" + ns.sessionId);
+                    return null;
+                }
+
+                if ((session.isPublishSession()
+                        && ns.role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) || (
+                        !session.isPublishSession() && ns.role
+                                != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)) {
+                    Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
+                            + " -- invalid role for session type");
+                    return null;
+                }
+
+                if (ns.type == WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB) {
+                    pubSubId = session.getPubSubId();
+                    String peerMacStr = session.getMac(ns.peerId, null);
+                    if (peerMacStr == null) {
+                        Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
+                                + " -- no MAC address associated with this peer id -- peerId="
+                                + ns.peerId);
+                        return null;
+                    }
+                    try {
+                        peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
+                        if (peerMac == null || peerMac.length != 6) {
+                            Log.e(TAG, "processNetworkSpecifier: networkSpecifier="
+                                    + ns + " -- invalid peer MAC address");
+                            return null;
+                        }
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
+                                + " -- invalid peer MAC address -- e=" + e);
+                        return null;
+                    }
+                }
+            }
+
+            // create container and populate
+            AwareNetworkRequestInformation nnri = new AwareNetworkRequestInformation();
+            nnri.state = (ns.role == WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR)
+                    ? AwareNetworkRequestInformation.STATE_INITIATOR_IDLE
+                    : AwareNetworkRequestInformation.STATE_RESPONDER_IDLE;
+            nnri.uid = uid;
+            nnri.pubSubId = pubSubId;
+            nnri.peerDiscoveryMac = peerMac;
+            nnri.networkSpecifier = ns;
+
+            return nnri;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("AwareNetworkRequestInformation: ");
+            sb.append("state=").append(state).append(", ns=").append(networkSpecifier).append(
+                    ", uid=").append(uid).append(", interfaceName=").append(interfaceName).append(
+                    ", pubSubId=").append(pubSubId).append(", peerDiscoveryMac=").append(
+                    peerDiscoveryMac == null ? ""
+                            : String.valueOf(HexEncoding.encode(peerDiscoveryMac))).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(AwareNetworkRequestInformation nnri,
+                WifiAwareNetworkSpecifier 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("WifiAwareDataPathStateManager:");
+        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/aware/WifiAwareDiscoverySessionState.java b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
new file mode 100644
index 0000000..d006aa8
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
@@ -0,0 +1,276 @@
+/*
+ * 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.aware;
+
+import android.hardware.wifi.V1_0.NanStatusType;
+import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Manages the state of a single Aware 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 WifiAwareDiscoverySessionState {
+    private static final String TAG = "WifiAwareDiscSessState";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private final WifiAwareNativeApi mWifiAwareNativeApi;
+    private int mSessionId;
+    private int mPubSubId;
+    private IWifiAwareDiscoverySessionCallback mCallback;
+    private boolean mIsPublishSession;
+
+    private final SparseArray<String> mMacByRequestorInstanceId = new SparseArray<>();
+
+    public WifiAwareDiscoverySessionState(WifiAwareNativeApi wifiAwareNativeApi, int sessionId,
+            int pubSubId, IWifiAwareDiscoverySessionCallback callback, boolean isPublishSession) {
+        mWifiAwareNativeApi = wifiAwareNativeApi;
+        mSessionId = sessionId;
+        mPubSubId = pubSubId;
+        mCallback = callback;
+        mIsPublishSession = isPublishSession;
+    }
+
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    public int getPubSubId() {
+        return mPubSubId;
+    }
+
+    public boolean isPublishSession() {
+        return mIsPublishSession;
+    }
+
+    public IWifiAwareDiscoverySessionCallback 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) {
+            mWifiAwareNativeApi.stopPublish((short) 0, mPubSubId);
+        } else {
+            mWifiAwareNativeApi.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 mPubSubId == pubSubId;
+    }
+
+    /**
+     * 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(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.e(TAG, "updatePublish: RemoteException=" + e);
+            }
+            return false;
+        }
+
+        boolean success = mWifiAwareNativeApi.publish(transactionId, mPubSubId, config);
+        if (!success) {
+            try {
+                mCallback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "updatePublish onSessionConfigFail(): RemoteException (FYI): " + e);
+            }
+        }
+
+        return success;
+    }
+
+    /**
+     * 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(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.e(TAG, "updateSubscribe: RemoteException=" + e);
+            }
+            return false;
+        }
+
+        boolean success = mWifiAwareNativeApi.subscribe(transactionId, mPubSubId, config);
+        if (!success) {
+            try {
+                mCallback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "updateSubscribe onSessionConfigFail(): RemoteException (FYI): " + e);
+            }
+        }
+
+        return success;
+    }
+
+    /**
+     * 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 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 messageId) {
+        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");
+            try {
+                mCallback.onMessageSendFail(messageId, NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.e(TAG, "sendMessage: RemoteException=" + e);
+            }
+            return false;
+        }
+        byte[] peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
+
+        boolean success = mWifiAwareNativeApi.sendMessage(transactionId, mPubSubId, peerId, peerMac,
+                message, messageId);
+        if (!success) {
+            try {
+                mCallback.onMessageSendFail(messageId, NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.e(TAG, "sendMessage: RemoteException=" + e);
+            }
+            return false;
+        }
+
+        return success;
+    }
+
+    /**
+     * 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 matchFilter The filter from the discovery advertisement (which was
+     *            used in the match decision).
+     */
+    public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
+            byte[] matchFilter) {
+        String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
+        mMacByRequestorInstanceId.put(requestorInstanceId, new String(HexEncoding.encode(peerMac)));
+
+        if (DBG) Log.d(TAG, "onMatch: previous peer MAC replaced - " + prevMac);
+
+        try {
+            mCallback.onMatch(requestorInstanceId, serviceSpecificInfo, matchFilter);
+        } 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.
+     */
+    public void onMessageReceived(int requestorInstanceId, byte[] peerMac, byte[] message) {
+        String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
+        mMacByRequestorInstanceId.put(requestorInstanceId, new String(HexEncoding.encode(peerMac)));
+
+        if (DBG) {
+            Log.d(TAG, "onMessageReceived: previous peer MAC replaced - " + prevMac);
+        }
+
+        try {
+            mCallback.onMessageReceived(requestorInstanceId, message);
+        } 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("AwareSessionState:");
+        pw.println("  mSessionId: " + mSessionId);
+        pw.println("  mIsPublishSession: " + mIsPublishSession);
+        pw.println("  mPubSubId: " + mPubSubId);
+        pw.println("  mMacByRequestorInstanceId: [" + mMacByRequestorInstanceId + "]");
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
new file mode 100644
index 0000000..2efa2ae
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
@@ -0,0 +1,825 @@
+/*
+ * 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.aware;
+
+import android.hardware.wifi.V1_0.IWifiNanIface;
+import android.hardware.wifi.V1_0.NanBandIndex;
+import android.hardware.wifi.V1_0.NanBandSpecificConfig;
+import android.hardware.wifi.V1_0.NanCipherSuiteType;
+import android.hardware.wifi.V1_0.NanConfigRequest;
+import android.hardware.wifi.V1_0.NanDataPathSecurityType;
+import android.hardware.wifi.V1_0.NanEnableRequest;
+import android.hardware.wifi.V1_0.NanInitiateDataPathRequest;
+import android.hardware.wifi.V1_0.NanMatchAlg;
+import android.hardware.wifi.V1_0.NanPublishRequest;
+import android.hardware.wifi.V1_0.NanRespondToDataPathIndicationRequest;
+import android.hardware.wifi.V1_0.NanSubscribeRequest;
+import android.hardware.wifi.V1_0.NanTransmitFollowupRequest;
+import android.hardware.wifi.V1_0.NanTxType;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
+import android.os.RemoteException;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Translates Wi-Fi Aware requests from the framework to the HAL (HIDL).
+ *
+ * Delegates the management of the NAN interface to WifiAwareNativeManager.
+ */
+public class WifiAwareNativeApi {
+    private static final String TAG = "WifiAwareNativeApi";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private final WifiAwareNativeManager mHal;
+
+    public WifiAwareNativeApi(WifiAwareNativeManager wifiAwareNativeManager) {
+        mHal = wifiAwareNativeManager;
+    }
+
+    /**
+     * Query the 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.v(TAG, "getCapabilities: transactionId=" + transactionId);
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "getCapabilities: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status = iface.getCapabilitiesRequest(transactionId);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "getCapabilities: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getCapabilities: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Enable and configure Aware.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param configRequest Requested Aware configuration.
+     * @param notifyIdentityChange Indicates whether or not to get address change callbacks.
+     * @param initialConfiguration Specifies whether initial configuration
+     *            (true) or an update (false) to the configuration.
+     */
+    public boolean enableAndConfigure(short transactionId, ConfigRequest configRequest,
+            boolean notifyIdentityChange, boolean initialConfiguration) {
+        if (VDBG) {
+            Log.v(TAG, "enableAndConfigure: transactionId=" + transactionId + ", configRequest="
+                    + configRequest + ", notifyIdentityChange=" + notifyIdentityChange
+                    + ", initialConfiguration=" + initialConfiguration);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "enableAndConfigure: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status;
+            if (initialConfiguration) {
+                // translate framework to HIDL configuration
+                NanEnableRequest req = new NanEnableRequest();
+
+                req.operateInBand[NanBandIndex.NAN_BAND_24GHZ] = true;
+                req.operateInBand[NanBandIndex.NAN_BAND_5GHZ] = configRequest.mSupport5gBand;
+                req.hopCountMax = 2;
+                req.configParams.masterPref = (byte) configRequest.mMasterPreference;
+                req.configParams.disableDiscoveryAddressChangeIndication = !notifyIdentityChange;
+                req.configParams.disableStartedClusterIndication = !notifyIdentityChange;
+                req.configParams.disableJoinedClusterIndication = !notifyIdentityChange;
+                req.configParams.includePublishServiceIdsInBeacon = true;
+                req.configParams.numberOfPublishServiceIdsInBeacon = 0;
+                req.configParams.includeSubscribeServiceIdsInBeacon = true;
+                req.configParams.numberOfSubscribeServiceIdsInBeacon = 0;
+                req.configParams.rssiWindowSize = 8;
+                req.configParams.macAddressRandomizationIntervalSec = 1800;
+
+                NanBandSpecificConfig config24 = new NanBandSpecificConfig();
+                config24.rssiClose = 60;
+                config24.rssiMiddle = 70;
+                config24.rssiCloseProximity = 60;
+                config24.dwellTimeMs = (byte) 200;
+                config24.scanPeriodSec = 20;
+                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]
+                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
+                    config24.validDiscoveryWindowIntervalVal = false;
+                } else {
+                    config24.validDiscoveryWindowIntervalVal = true;
+                    config24.discoveryWindowIntervalVal =
+                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
+                                    .NAN_BAND_24GHZ];
+                }
+                req.configParams.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ] = config24;
+
+                NanBandSpecificConfig config5 = new NanBandSpecificConfig();
+                config5.rssiClose = 60;
+                config5.rssiMiddle = 75;
+                config5.rssiCloseProximity = 60;
+                config5.dwellTimeMs = (byte) 200;
+                config5.scanPeriodSec = 20;
+                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]
+                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
+                    config5.validDiscoveryWindowIntervalVal = false;
+                } else {
+                    config5.validDiscoveryWindowIntervalVal = true;
+                    config5.discoveryWindowIntervalVal =
+                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
+                                    .NAN_BAND_5GHZ];
+                }
+                req.configParams.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ] = config5;
+
+                req.debugConfigs.validClusterIdVals = true;
+                req.debugConfigs.clusterIdTopRangeVal = (short) configRequest.mClusterHigh;
+                req.debugConfigs.clusterIdBottomRangeVal = (short) configRequest.mClusterLow;
+                req.debugConfigs.validIntfAddrVal = false;
+                req.debugConfigs.validOuiVal = false;
+                req.debugConfigs.ouiVal = 0;
+                req.debugConfigs.validRandomFactorForceVal = false;
+                req.debugConfigs.randomFactorForceVal = 0;
+                req.debugConfigs.validHopCountForceVal = false;
+                req.debugConfigs.hopCountForceVal = 0;
+                req.debugConfigs.validDiscoveryChannelVal = false;
+                req.debugConfigs.discoveryChannelMhzVal[NanBandIndex.NAN_BAND_24GHZ] = 0;
+                req.debugConfigs.discoveryChannelMhzVal[NanBandIndex.NAN_BAND_5GHZ] = 0;
+                req.debugConfigs.validUseBeaconsInBandVal = false;
+                req.debugConfigs.useBeaconsInBandVal[NanBandIndex.NAN_BAND_24GHZ] = true;
+                req.debugConfigs.useBeaconsInBandVal[NanBandIndex.NAN_BAND_5GHZ] = true;
+                req.debugConfigs.validUseSdfInBandVal = false;
+                req.debugConfigs.useSdfInBandVal[NanBandIndex.NAN_BAND_24GHZ] = true;
+                req.debugConfigs.useSdfInBandVal[NanBandIndex.NAN_BAND_5GHZ] = true;
+
+                status = iface.enableRequest(transactionId, req);
+            } else {
+                NanConfigRequest req = new NanConfigRequest();
+                req.masterPref = (byte) configRequest.mMasterPreference;
+                req.disableDiscoveryAddressChangeIndication = !notifyIdentityChange;
+                req.disableStartedClusterIndication = !notifyIdentityChange;
+                req.disableJoinedClusterIndication = !notifyIdentityChange;
+                req.includePublishServiceIdsInBeacon = true;
+                req.numberOfPublishServiceIdsInBeacon = 0;
+                req.includeSubscribeServiceIdsInBeacon = true;
+                req.numberOfSubscribeServiceIdsInBeacon = 0;
+                req.rssiWindowSize = 8;
+                req.macAddressRandomizationIntervalSec = 1800;
+
+                NanBandSpecificConfig config24 = new NanBandSpecificConfig();
+                config24.rssiClose = 60;
+                config24.rssiMiddle = 70;
+                config24.rssiCloseProximity = 60;
+                config24.dwellTimeMs = (byte) 200;
+                config24.scanPeriodSec = 20;
+                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]
+                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
+                    config24.validDiscoveryWindowIntervalVal = false;
+                } else {
+                    config24.validDiscoveryWindowIntervalVal = true;
+                    config24.discoveryWindowIntervalVal =
+                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
+                                    .NAN_BAND_24GHZ];
+                }
+                req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ] = config24;
+
+                NanBandSpecificConfig config5 = new NanBandSpecificConfig();
+                config5.rssiClose = 60;
+                config5.rssiMiddle = 75;
+                config5.rssiCloseProximity = 60;
+                config5.dwellTimeMs = (byte) 200;
+                config5.scanPeriodSec = 20;
+                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]
+                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
+                    config5.validDiscoveryWindowIntervalVal = false;
+                } else {
+                    config5.validDiscoveryWindowIntervalVal = true;
+                    config5.discoveryWindowIntervalVal =
+                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
+                                    .NAN_BAND_5GHZ];
+                }
+                req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ] = config5;
+
+                status = iface.configRequest(transactionId, req);
+            }
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "enableAndConfigure: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "enableAndConfigure: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Disable Aware.
+     *
+     * @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, "disable");
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "disable: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status = iface.disableRequest(transactionId);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "disable: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "disable: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * 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 + ", config=" + publishConfig);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "publish: null interface");
+            return false;
+        }
+
+        NanPublishRequest req = new NanPublishRequest();
+        req.baseConfigs.sessionId = 0;
+        req.baseConfigs.ttlSec = (short) publishConfig.mTtlSec;
+        req.baseConfigs.discoveryWindowPeriod = 1;
+        req.baseConfigs.discoveryCount = 0;
+        convertNativeByteArrayToArrayList(publishConfig.mServiceName, req.baseConfigs.serviceName);
+        // TODO: what's the right value on publish?
+        req.baseConfigs.discoveryMatchIndicator = NanMatchAlg.MATCH_ONCE;
+        convertNativeByteArrayToArrayList(publishConfig.mServiceSpecificInfo,
+                req.baseConfigs.serviceSpecificInfo);
+        convertNativeByteArrayToArrayList(publishConfig.mMatchFilter,
+                publishConfig.mPublishType == PublishConfig.PUBLISH_TYPE_UNSOLICITED
+                        ? req.baseConfigs.txMatchFilter : req.baseConfigs.rxMatchFilter);
+        req.baseConfigs.useRssiThreshold = false;
+        req.baseConfigs.disableDiscoveryTerminationIndication =
+                !publishConfig.mEnableTerminateNotification;
+        req.baseConfigs.disableMatchExpirationIndication = true;
+        req.baseConfigs.disableFollowupReceivedIndication = false;
+
+        // TODO: configure ranging and security
+        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+        req.baseConfigs.rangingRequired = false;
+        req.autoAcceptDataPathRequests = false;
+
+        req.publishType = publishConfig.mPublishType;
+        req.txType = NanTxType.BROADCAST;
+
+        try {
+            WifiStatus status = iface.startPublishRequest(transactionId, req);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "publish: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "publish: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * 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 + ", config=" + subscribeConfig);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "subscribe: null interface");
+            return false;
+        }
+
+        NanSubscribeRequest req = new NanSubscribeRequest();
+        req.baseConfigs.sessionId = 0;
+        req.baseConfigs.ttlSec = (short) subscribeConfig.mTtlSec;
+        req.baseConfigs.discoveryWindowPeriod = 1;
+        req.baseConfigs.discoveryCount = 0;
+        convertNativeByteArrayToArrayList(subscribeConfig.mServiceName,
+                req.baseConfigs.serviceName);
+        req.baseConfigs.discoveryMatchIndicator = NanMatchAlg.MATCH_ONCE;
+        convertNativeByteArrayToArrayList(subscribeConfig.mServiceSpecificInfo,
+                req.baseConfigs.serviceSpecificInfo);
+        convertNativeByteArrayToArrayList(subscribeConfig.mMatchFilter,
+                subscribeConfig.mSubscribeType == SubscribeConfig.SUBSCRIBE_TYPE_ACTIVE
+                        ? req.baseConfigs.txMatchFilter : req.baseConfigs.rxMatchFilter);
+        req.baseConfigs.useRssiThreshold = false;
+        req.baseConfigs.disableDiscoveryTerminationIndication =
+                !subscribeConfig.mEnableTerminateNotification;
+        req.baseConfigs.disableMatchExpirationIndication = true;
+        req.baseConfigs.disableFollowupReceivedIndication = false;
+
+        // TODO: configure ranging and security
+        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+        req.baseConfigs.rangingRequired = false;
+
+        req.subscribeType = subscribeConfig.mSubscribeType;
+
+        try {
+            WifiStatus status = iface.startSubscribeRequest(transactionId, req);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "subscribe: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "subscribe: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * 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 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 messageId) {
+        if (VDBG) {
+            Log.d(TAG,
+                    "sendMessage: transactionId=" + transactionId + ", pubSubId=" + pubSubId
+                            + ", requestorInstanceId=" + requestorInstanceId + ", dest="
+                            + String.valueOf(HexEncoding.encode(dest)) + ", messageId="
+                            + messageId);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "sendMessage: null interface");
+            return false;
+        }
+
+        NanTransmitFollowupRequest req = new NanTransmitFollowupRequest();
+        req.discoverySessionId = (byte) pubSubId;
+        req.peerId = requestorInstanceId;
+        copyArray(dest, req.addr);
+        req.isHighPriority = false;
+        req.shouldUseDiscoveryWindow = true;
+        convertNativeByteArrayToArrayList(message, req.serviceSpecificInfo);
+        req.disableFollowupResultIndication = false;
+
+        try {
+            WifiStatus status = iface.transmitFollowupRequest(transactionId, req);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "sendMessage: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "sendMessage: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * 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);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "stopPublish: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status = iface.stopPublishRequest(transactionId, (byte) pubSubId);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "stopPublish: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "stopPublish: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * 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);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "stopSubscribe: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status = iface.stopSubscribeRequest(transactionId, (byte) pubSubId);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "stopSubscribe: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "stopSubscribe: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Create a Aware 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. "aware0".
+     */
+    public boolean createAwareNetworkInterface(short transactionId, String interfaceName) {
+        if (VDBG) {
+            Log.v(TAG, "createAwareNetworkInterface: transactionId=" + transactionId + ", "
+                    + "interfaceName=" + interfaceName);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "createAwareNetworkInterface: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status = iface.createDataInterfaceRequest(transactionId, interfaceName);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "createAwareNetworkInterface: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "createAwareNetworkInterface: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Deletes a Aware 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. "aware0".
+     */
+    public boolean deleteAwareNetworkInterface(short transactionId, String interfaceName) {
+        if (VDBG) {
+            Log.v(TAG, "deleteAwareNetworkInterface: transactionId=" + transactionId + ", "
+                    + "interfaceName=" + interfaceName);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "deleteAwareNetworkInterface: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status = iface.deleteDataInterfaceRequest(transactionId, interfaceName);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "deleteAwareNetworkInterface: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "deleteAwareNetworkInterface: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Initiates setting up a data-path between device and peer. Security is provided by either
+     * PMK or Passphrase (not both) - if both are null then an open (unencrypted) link is set up.
+     *
+     * @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 pmk Pairwise master key (PMK - see IEEE 802.11i) for the data-path.
+     * @param passphrase  Passphrase for the data-path.
+     * @param capabilities The capabilities of the firmware.
+     */
+    public boolean initiateDataPath(short transactionId, int peerId, int channelRequestType,
+            int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
+            Capabilities capabilities) {
+        if (VDBG) {
+            Log.v(TAG, "initiateDataPath: transactionId=" + transactionId + ", peerId=" + peerId
+                    + ", channelRequestType=" + channelRequestType + ", channel=" + channel
+                    + ", peer=" + String.valueOf(HexEncoding.encode(peer)) + ", interfaceName="
+                    + interfaceName);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "initiateDataPath: null interface");
+            return false;
+        }
+
+        if (capabilities == null) {
+            Log.e(TAG, "initiateDataPath: null capabilities");
+            return false;
+        }
+
+        NanInitiateDataPathRequest req = new NanInitiateDataPathRequest();
+        req.peerId = peerId;
+        copyArray(peer, req.peerDiscMacAddr);
+        req.channelRequestType = channelRequestType;
+        req.channel = channel;
+        req.ifaceName = interfaceName;
+        req.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+        if (pmk != null && pmk.length != 0) {
+            req.securityConfig.cipherType = getStrongestCipherSuiteType(
+                    capabilities.supportedCipherSuites);
+            req.securityConfig.securityType = NanDataPathSecurityType.PMK;
+            copyArray(pmk, req.securityConfig.pmk);
+        }
+        if (passphrase != null && passphrase.length() != 0) {
+            req.securityConfig.cipherType = getStrongestCipherSuiteType(
+                    capabilities.supportedCipherSuites);
+            req.securityConfig.securityType = NanDataPathSecurityType.PASSPHRASE;
+            convertNativeByteArrayToArrayList(passphrase.getBytes(), req.securityConfig.passphrase);
+        }
+
+        try {
+            WifiStatus status = iface.initiateDataPathRequest(transactionId, req);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "initiateDataPath: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "initiateDataPath: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Responds to a data request from a peer. Security is provided by either PMK or Passphrase (not
+     * both) - if both are null then an open (unencrypted) link is set up.
+     *
+     * @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 (Aware 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 pmk Pairwise master key (PMK - see IEEE 802.11i) for the data-path.
+     * @param passphrase  Passphrase for the data-path.
+     * @param capabilities The capabilities of the firmware.
+     */
+    public boolean respondToDataPathRequest(short transactionId, boolean accept, int ndpId,
+            String interfaceName, byte[] pmk, String passphrase, Capabilities capabilities) {
+        if (VDBG) {
+            Log.v(TAG, "respondToDataPathRequest: transactionId=" + transactionId + ", accept="
+                    + accept + ", int ndpId=" + ndpId + ", interfaceName=" + interfaceName);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "respondToDataPathRequest: null interface");
+            return false;
+        }
+
+        if (capabilities == null) {
+            Log.e(TAG, "initiateDataPath: null capabilities");
+            return false;
+        }
+
+        NanRespondToDataPathIndicationRequest req = new NanRespondToDataPathIndicationRequest();
+        req.acceptRequest = accept;
+        req.ndpInstanceId = ndpId;
+        req.ifaceName = interfaceName;
+        req.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+        if (pmk != null && pmk.length != 0) {
+            req.securityConfig.cipherType = getStrongestCipherSuiteType(
+                    capabilities.supportedCipherSuites);
+            req.securityConfig.securityType = NanDataPathSecurityType.PMK;
+            copyArray(pmk, req.securityConfig.pmk);
+        }
+        if (passphrase != null && passphrase.length() != 0) {
+            req.securityConfig.cipherType = getStrongestCipherSuiteType(
+                    capabilities.supportedCipherSuites);
+            req.securityConfig.securityType = NanDataPathSecurityType.PASSPHRASE;
+            convertNativeByteArrayToArrayList(passphrase.getBytes(), req.securityConfig.passphrase);
+        }
+
+        try {
+            WifiStatus status = iface.respondToDataPathIndicationRequest(transactionId, req);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "respondToDataPathRequest: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "respondToDataPathRequest: exception: " + e);
+            return false;
+        }
+    }
+
+    /**
+     * 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 (Aware data path) ID to be terminated.
+     */
+    public boolean endDataPath(short transactionId, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG, "endDataPath: transactionId=" + transactionId + ", ndpId=" + ndpId);
+        }
+
+        IWifiNanIface iface = mHal.getWifiNanIface();
+        if (iface == null) {
+            Log.e(TAG, "endDataPath: null interface");
+            return false;
+        }
+
+        try {
+            WifiStatus status = iface.terminateDataPathRequest(transactionId, ndpId);
+            if (status.code == WifiStatusCode.SUCCESS) {
+                return true;
+            } else {
+                Log.e(TAG, "endDataPath: error: " + statusString(status));
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "endDataPath: exception: " + e);
+            return false;
+        }
+    }
+
+
+    // utilities
+
+    /**
+     * Returns the strongest supported cipher suite.
+     *
+     * Baseline is very simple: 256 > 128 > 0.
+     */
+    private int getStrongestCipherSuiteType(int supportedCipherSuites) {
+        if ((supportedCipherSuites & NanCipherSuiteType.SHARED_KEY_256_MASK) != 0) {
+            return NanCipherSuiteType.SHARED_KEY_256_MASK;
+        }
+        if ((supportedCipherSuites & NanCipherSuiteType.SHARED_KEY_128_MASK) != 0) {
+            return NanCipherSuiteType.SHARED_KEY_128_MASK;
+        }
+        return NanCipherSuiteType.NONE;
+    }
+
+    /**
+     * Converts a byte[] to an ArrayList<Byte>. Fills in the entries of the 'to' array if
+     * provided (non-null), otherwise creates and returns a new ArrayList<>.
+     *
+     * @param from The input byte[] to convert from.
+     * @param to An optional ArrayList<> to fill in from 'from'.
+     *
+     * @return A newly allocated ArrayList<> if 'to' is null, otherwise null.
+     */
+    private ArrayList<Byte> convertNativeByteArrayToArrayList(byte[] from, ArrayList<Byte> to) {
+        if (from == null) {
+            from = new byte[0];
+        }
+
+        if (to == null) {
+            to = new ArrayList<>(from.length);
+        } else {
+            to.ensureCapacity(from.length);
+        }
+        for (int i = 0; i < from.length; ++i) {
+            to.add(from[i]);
+        }
+        return to;
+    }
+
+    private void copyArray(byte[] from, byte[] to) {
+        if (from == null || to == null || from.length != to.length) {
+            Log.e(TAG, "copyArray error: from=" + from + ", to=" + to);
+            return;
+        }
+        for (int i = 0; i < from.length; ++i) {
+            to[i] = from[i];
+        }
+    }
+
+    private static String statusString(WifiStatus status) {
+        if (status == null) {
+            return "status=null";
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append(status.code).append(" (").append(status.description).append(")");
+        return sb.toString();
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mHal.dump(fd, pw, args);
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
new file mode 100644
index 0000000..6f1925f
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.aware;
+
+import android.hardware.wifi.V1_0.IWifiNanIfaceEventCallback;
+import android.hardware.wifi.V1_0.NanCapabilities;
+import android.hardware.wifi.V1_0.NanClusterEventInd;
+import android.hardware.wifi.V1_0.NanClusterEventType;
+import android.hardware.wifi.V1_0.NanDataPathConfirmInd;
+import android.hardware.wifi.V1_0.NanDataPathRequestInd;
+import android.hardware.wifi.V1_0.NanFollowupReceivedInd;
+import android.hardware.wifi.V1_0.NanMatchInd;
+import android.hardware.wifi.V1_0.NanStatusType;
+import android.hardware.wifi.V1_0.WifiNanStatus;
+import android.util.Log;
+
+import libcore.util.HexEncoding;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Manages the callbacks from Wi-Fi Aware HIDL (HAL).
+ */
+public class WifiAwareNativeCallback extends IWifiNanIfaceEventCallback.Stub {
+    private static final String TAG = "WifiAwareNativeCallback";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    private final WifiAwareStateManager mWifiAwareStateManager;
+
+    public WifiAwareNativeCallback(WifiAwareStateManager wifiAwareStateManager) {
+        mWifiAwareStateManager = wifiAwareStateManager;
+    }
+
+    @Override
+    public void notifyCapabilitiesResponse(short id, WifiNanStatus status,
+            NanCapabilities capabilities) {
+        if (VDBG) {
+            Log.v(TAG, "notifyCapabilitiesResponse: id=" + id + ", status=" + statusString(status)
+                    + ", capabilities=" + capabilities);
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            Capabilities frameworkCapabilities = new Capabilities();
+            frameworkCapabilities.maxConcurrentAwareClusters = capabilities.maxConcurrentClusters;
+            frameworkCapabilities.maxPublishes = capabilities.maxPublishes;
+            frameworkCapabilities.maxSubscribes = capabilities.maxSubscribes;
+            frameworkCapabilities.maxServiceNameLen = capabilities.maxServiceNameLen;
+            frameworkCapabilities.maxMatchFilterLen = capabilities.maxMatchFilterLen;
+            frameworkCapabilities.maxTotalMatchFilterLen = capabilities.maxTotalMatchFilterLen;
+            frameworkCapabilities.maxServiceSpecificInfoLen =
+                    capabilities.maxServiceSpecificInfoLen;
+            frameworkCapabilities.maxExtendedServiceSpecificInfoLen =
+                    capabilities.maxExtendedServiceSpecificInfoLen;
+            frameworkCapabilities.maxNdiInterfaces = capabilities.maxNdiInterfaces;
+            frameworkCapabilities.maxNdpSessions = capabilities.maxNdpSessions;
+            frameworkCapabilities.maxAppInfoLen = capabilities.maxAppInfoLen;
+            frameworkCapabilities.maxQueuedTransmitMessages =
+                    capabilities.maxQueuedTransmitFollowupMsgs;
+            frameworkCapabilities.maxSubscribeInterfaceAddresses =
+                    capabilities.maxSubscribeInterfaceAddresses;
+            frameworkCapabilities.supportedCipherSuites = capabilities.supportedCipherSuites;
+
+            mWifiAwareStateManager.onCapabilitiesUpdateResponse(id, frameworkCapabilities);
+        } else {
+            Log.e(TAG, "notifyCapabilitiesResponse: error code=" + status.status + " ("
+                    + status.description + ")");
+        }
+    }
+
+    @Override
+    public void notifyEnableResponse(short id, WifiNanStatus status) {
+        if (VDBG) Log.v(TAG, "notifyEnableResponse: id=" + id + ", status=" + statusString(status));
+
+        if (status.status == NanStatusType.SUCCESS) {
+            mWifiAwareStateManager.onConfigSuccessResponse(id);
+        } else {
+            mWifiAwareStateManager.onConfigFailedResponse(id, status.status);
+        }
+    }
+
+    @Override
+    public void notifyConfigResponse(short id, WifiNanStatus status) {
+        if (VDBG) Log.v(TAG, "notifyConfigResponse: id=" + id + ", status=" + statusString(status));
+
+        if (status.status == NanStatusType.SUCCESS) {
+            mWifiAwareStateManager.onConfigSuccessResponse(id);
+        } else {
+            mWifiAwareStateManager.onConfigFailedResponse(id, status.status);
+        }
+    }
+
+    @Override
+    public void notifyDisableResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyDisableResponse: id=" + id + ", status=" + statusString(status));
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            // NOP
+        } else {
+            Log.e(TAG, "notifyDisableResponse: failure - code=" + status.status + " ("
+                    + status.description + ")");
+        }
+    }
+
+    @Override
+    public void notifyStartPublishResponse(short id, WifiNanStatus status, byte publishId) {
+        if (VDBG) {
+            Log.v(TAG, "notifyStartPublishResponse: id=" + id + ", status=" + statusString(status)
+                    + ", publishId=" + publishId);
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            mWifiAwareStateManager.onSessionConfigSuccessResponse(id, true, publishId);
+        } else {
+            mWifiAwareStateManager.onSessionConfigFailResponse(id, true, status.status);
+        }
+    }
+
+    @Override
+    public void notifyStopPublishResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyStopPublishResponse: id=" + id + ", status=" + statusString(status));
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            // NOP
+        } else {
+            Log.e(TAG, "notifyStopPublishResponse: failure - code=" + status.status + " ("
+                    + status.description + ")");
+        }
+    }
+
+    @Override
+    public void notifyStartSubscribeResponse(short id, WifiNanStatus status, byte subscribeId) {
+        if (VDBG) {
+            Log.v(TAG, "notifyStartSubscribeResponse: id=" + id + ", status=" + statusString(status)
+                    + ", subscribeId=" + subscribeId);
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            mWifiAwareStateManager.onSessionConfigSuccessResponse(id, false, subscribeId);
+        } else {
+            mWifiAwareStateManager.onSessionConfigFailResponse(id, false, status.status);
+        }
+    }
+
+    @Override
+    public void notifyStopSubscribeResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyStopSubscribeResponse: id=" + id + ", status="
+                    + statusString(status));
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            // NOP
+        } else {
+            Log.e(TAG, "notifyStopSubscribeResponse: failure - code=" + status.status + " ("
+                    + status.description + ")");
+        }
+    }
+
+    @Override
+    public void notifyTransmitFollowupResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyTransmitFollowupResponse: id=" + id + ", status="
+                    + statusString(status));
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            mWifiAwareStateManager.onMessageSendQueuedSuccessResponse(id);
+        } else {
+            mWifiAwareStateManager.onMessageSendQueuedFailResponse(id, status.status);
+        }
+    }
+
+    @Override
+    public void notifyCreateDataInterfaceResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyCreateDataInterfaceResponse: id=" + id + ", status="
+                    + statusString(status));
+        }
+
+        mWifiAwareStateManager.onCreateDataPathInterfaceResponse(id,
+                status.status == NanStatusType.SUCCESS, status.status);
+    }
+
+    @Override
+    public void notifyDeleteDataInterfaceResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyDeleteDataInterfaceResponse: id=" + id + ", status="
+                    + statusString(status));
+        }
+
+        mWifiAwareStateManager.onDeleteDataPathInterfaceResponse(id,
+                status.status == NanStatusType.SUCCESS, status.status);
+    }
+
+    @Override
+    public void notifyInitiateDataPathResponse(short id, WifiNanStatus status,
+            int ndpInstanceId) {
+        if (VDBG) {
+            Log.v(TAG, "notifyInitiateDataPathResponse: id=" + id + ", status="
+                    + statusString(status) + ", ndpInstanceId=" + ndpInstanceId);
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            mWifiAwareStateManager.onInitiateDataPathResponseSuccess(id, ndpInstanceId);
+        } else {
+            mWifiAwareStateManager.onInitiateDataPathResponseFail(id, status.status);
+        }
+    }
+
+    @Override
+    public void notifyRespondToDataPathIndicationResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyRespondToDataPathIndicationResponse: id=" + id
+                    + ", status=" + statusString(status));
+        }
+
+        mWifiAwareStateManager.onRespondToDataPathSetupRequestResponse(id,
+                status.status == NanStatusType.SUCCESS, status.status);
+    }
+
+    @Override
+    public void notifyTerminateDataPathResponse(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "notifyTerminateDataPathResponse: id=" + id + ", status="
+                    + statusString(status));
+        }
+
+        mWifiAwareStateManager.onEndDataPathResponse(id, status.status == NanStatusType.SUCCESS,
+                status.status);
+    }
+
+    @Override
+    public void eventClusterEvent(NanClusterEventInd event) {
+        if (VDBG) {
+            Log.v(TAG, "eventClusterEvent: eventType=" + event.eventType + ", addr="
+                    + String.valueOf(HexEncoding.encode(event.addr)));
+        }
+
+        if (event.eventType == NanClusterEventType.DISCOVERY_MAC_ADDRESS_CHANGED) {
+            mWifiAwareStateManager.onInterfaceAddressChangeNotification(event.addr);
+        } else if (event.eventType == NanClusterEventType.STARTED_CLUSTER) {
+            mWifiAwareStateManager.onClusterChangeNotification(
+                    WifiAwareClientState.CLUSTER_CHANGE_EVENT_STARTED, event.addr);
+        } else if (event.eventType == NanClusterEventType.JOINED_CLUSTER) {
+            mWifiAwareStateManager.onClusterChangeNotification(
+                    WifiAwareClientState.CLUSTER_CHANGE_EVENT_JOINED, event.addr);
+        } else {
+            Log.e(TAG, "eventClusterEvent: invalid eventType=" + event.eventType);
+        }
+    }
+
+    @Override
+    public void eventDisabled(WifiNanStatus status) {
+        if (VDBG) Log.v(TAG, "eventDisabled: status=" + statusString(status));
+
+        mWifiAwareStateManager.onAwareDownNotification(status.status);
+    }
+
+    @Override
+    public void eventPublishTerminated(byte sessionId, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "eventPublishTerminated: sessionId=" + sessionId + ", status="
+                    + statusString(status));
+        }
+
+        mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, true);
+    }
+
+    @Override
+    public void eventSubscribeTerminated(byte sessionId, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "eventSubscribeTerminated: sessionId=" + sessionId + ", status="
+                    + statusString(status));
+        }
+
+        mWifiAwareStateManager.onSessionTerminatedNotification(sessionId, status.status, false);
+    }
+
+    @Override
+    public void eventMatch(NanMatchInd event) {
+        if (VDBG) {
+            Log.v(TAG, "eventMatch: discoverySessionId=" + event.discoverySessionId + ", peerId="
+                    + event.peerId + ", addr=" + String.valueOf(HexEncoding.encode(event.addr))
+                    + ", serviceSpecificInfo=" + Arrays.toString(
+                    convertArrayListToNativeByteArray(event.serviceSpecificInfo)) + ", matchFilter="
+                    + Arrays.toString(convertArrayListToNativeByteArray(event.matchFilter)));
+        }
+
+        mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId,
+                event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo),
+                convertArrayListToNativeByteArray(event.matchFilter));
+    }
+
+    @Override
+    public void eventMatchExpired(byte discoverySessionId, int peerId) {
+        if (VDBG) {
+            Log.v(TAG, "eventMatchExpired: discoverySessionId=" + discoverySessionId
+                    + ", peerId=" + peerId);
+        }
+
+        // NOP
+    }
+
+    @Override
+    public void eventFollowupReceived(NanFollowupReceivedInd event) {
+        if (VDBG) {
+            Log.v(TAG, "eventFollowupReceived: discoverySessionId=" + event.discoverySessionId
+                    + ", peerId=" + event.peerId + ", addr=" + String.valueOf(
+                    HexEncoding.encode(event.addr)));
+        }
+
+        mWifiAwareStateManager.onMessageReceivedNotification(event.discoverySessionId, event.peerId,
+                event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo));
+    }
+
+    @Override
+    public void eventTransmitFollowup(short id, WifiNanStatus status) {
+        if (VDBG) {
+            Log.v(TAG, "eventTransmitFollowup: id=" + id + ", status=" + statusString(status));
+        }
+
+        if (status.status == NanStatusType.SUCCESS) {
+            mWifiAwareStateManager.onMessageSendSuccessNotification(id);
+        } else {
+            mWifiAwareStateManager.onMessageSendFailNotification(id, status.status);
+        }
+    }
+
+    @Override
+    public void eventDataPathRequest(NanDataPathRequestInd event) {
+        if (VDBG) {
+            Log.v(TAG, "eventDataPathRequest: discoverySessionId=" + event.discoverySessionId
+                    + ", peerDiscMacAddr=" + String.valueOf(
+                    HexEncoding.encode(event.peerDiscMacAddr)) + ", ndpInstanceId="
+                    + event.ndpInstanceId);
+        }
+
+        mWifiAwareStateManager.onDataPathRequestNotification(event.discoverySessionId,
+                event.peerDiscMacAddr, event.ndpInstanceId);
+    }
+
+    @Override
+    public void eventDataPathConfirm(NanDataPathConfirmInd event) {
+        if (VDBG) {
+            Log.v(TAG, "onDataPathConfirm: ndpInstanceId=" + event.ndpInstanceId
+                    + ", peerNdiMacAddr=" + String.valueOf(HexEncoding.encode(event.peerNdiMacAddr))
+                    + ", dataPathSetupSuccess=" + event.dataPathSetupSuccess + ", reason="
+                    + event.status.status);
+        }
+
+        mWifiAwareStateManager.onDataPathConfirmNotification(event.ndpInstanceId,
+                event.peerNdiMacAddr, event.dataPathSetupSuccess, event.status.status,
+                convertArrayListToNativeByteArray(event.appInfo));
+    }
+
+    @Override
+    public void eventDataPathTerminated(int ndpInstanceId) {
+        if (VDBG) Log.v(TAG, "eventDataPathTerminated: ndpInstanceId=" + ndpInstanceId);
+
+        mWifiAwareStateManager.onDataPathEndNotification(ndpInstanceId);
+    }
+
+    // utilities
+
+    /**
+     * Converts an ArrayList<Byte> to a byte[].
+     *
+     * @param from The input ArrayList<Byte></Byte> to convert from.
+     *
+     * @return A newly allocated byte[].
+     */
+    private byte[] convertArrayListToNativeByteArray(ArrayList<Byte> from) {
+        if (from == null) {
+            return null;
+        }
+
+        byte[] to = new byte[from.size()];
+        for (int i = 0; i < from.size(); ++i) {
+            to[i] = from.get(i);
+        }
+        return to;
+    }
+
+    private static String statusString(WifiNanStatus status) {
+        if (status == null) {
+            return "status=null";
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append(status.status).append(" (").append(status.description).append(")");
+        return sb.toString();
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
new file mode 100644
index 0000000..22f1385
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -0,0 +1,162 @@
+/*
+ * 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.aware;
+
+import android.hardware.wifi.V1_0.IWifiNanIface;
+import android.hardware.wifi.V1_0.IfaceType;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.HalDeviceManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Manages the interface to Wi-Fi Aware HIDL (HAL).
+ */
+class WifiAwareNativeManager {
+    private static final String TAG = "WifiAwareNativeManager";
+    private static final boolean DBG = false;
+
+    // to be used for synchronizing access to any of the WifiAwareNative objects
+    private final Object mLock = new Object();
+
+    private WifiAwareStateManager mWifiAwareStateManager;
+    private HalDeviceManager mHalDeviceManager;
+    private WifiAwareNativeCallback mWifiAwareNativeCallback;
+    private IWifiNanIface mWifiNanIface = null;
+    private InterfaceDestroyedListener mInterfaceDestroyedListener =
+            new InterfaceDestroyedListener();
+    private InterfaceAvailableForRequestListener mInterfaceAvailableForRequestListener =
+            new InterfaceAvailableForRequestListener();
+
+    WifiAwareNativeManager(WifiAwareStateManager awareStateManager,
+            HalDeviceManager halDeviceManager,
+            WifiAwareNativeCallback wifiAwareNativeCallback) {
+        mWifiAwareStateManager = awareStateManager;
+        mHalDeviceManager = halDeviceManager;
+        mWifiAwareNativeCallback = wifiAwareNativeCallback;
+        mHalDeviceManager.registerStatusListener(
+                new HalDeviceManager.ManagerStatusListener() {
+                    @Override
+                    public void onStatusChanged() {
+                        if (DBG) Log.d(TAG, "onStatusChanged");
+                        // only care about isStarted (Wi-Fi started) not isReady - since if not
+                        // ready then Wi-Fi will also be down.
+                        if (mHalDeviceManager.isStarted()) {
+                            // 1. no problem registering duplicates - only one will be called
+                            // 2. will be called immediately if available
+                            mHalDeviceManager.registerInterfaceAvailableForRequestListener(
+                                    IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+                        } else {
+                            awareIsDown();
+                        }
+                    }
+                }, null);
+        if (mHalDeviceManager.isStarted()) {
+            tryToGetAware();
+        }
+    }
+
+    /* package */ IWifiNanIface getWifiNanIface() {
+        synchronized (mLock) {
+            return mWifiNanIface;
+        }
+    }
+
+    private void tryToGetAware() {
+        synchronized (mLock) {
+            if (DBG) Log.d(TAG, "tryToGetAware: mWifiNanIface=" + mWifiNanIface);
+
+            if (mWifiNanIface != null) {
+                return;
+            }
+            IWifiNanIface iface = mHalDeviceManager.createNanIface(mInterfaceDestroyedListener,
+                    null);
+            if (iface == null) {
+                if (DBG) Log.d(TAG, "Was not able to obtain an IWifiNanIface");
+            } else {
+                if (DBG) Log.d(TAG, "Obtained an IWifiNanIface");
+
+                try {
+                    WifiStatus status = iface.registerEventCallback(mWifiAwareNativeCallback);
+                    if (status.code != WifiStatusCode.SUCCESS) {
+                        Log.e(TAG, "IWifiNanIface.registerEventCallback error: " + statusString(
+                                status));
+                        mHalDeviceManager.removeIface(iface);
+                        return;
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "IWifiNanIface.registerEventCallback exception: " + e);
+                    mHalDeviceManager.removeIface(iface);
+                    return;
+                }
+                mWifiNanIface = iface;
+                mWifiAwareStateManager.enableUsage();
+            }
+        }
+    }
+
+    private void awareIsDown() {
+        synchronized (mLock) {
+            if (DBG) Log.d(TAG, "awareIsDown: mWifiNanIface=" + mWifiNanIface);
+            if (mWifiNanIface != null) {
+                mWifiNanIface = null;
+                mWifiAwareStateManager.disableUsage();
+            }
+        }
+    }
+
+    private class InterfaceDestroyedListener implements
+            HalDeviceManager.InterfaceDestroyedListener {
+        @Override
+        public void onDestroyed() {
+            if (DBG) Log.d(TAG, "Interface was destroyed");
+            awareIsDown();
+        }
+    }
+
+    private class InterfaceAvailableForRequestListener implements
+            HalDeviceManager.InterfaceAvailableForRequestListener {
+        @Override
+        public void onAvailableForRequest() {
+            if (DBG) Log.d(TAG, "Interface is possibly available");
+            tryToGetAware();
+        }
+    }
+
+    private static String statusString(WifiStatus status) {
+        if (status == null) {
+            return "status=null";
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append(status.code).append(" (").append(status.description).append(")");
+        return sb.toString();
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WifiAwareNativeManager:");
+        pw.println("  mWifiNanIface: " + mWifiNanIface);
+        mHalDeviceManager.dump(fd, pw, args);
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java
new file mode 100644
index 0000000..74f34b3
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.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.aware;
+
+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 Aware and the RTT service. Duplicates some of the functionality
+ * of the RttManager.
+ */
+public class WifiAwareRttStateManager {
+    private static final String TAG = "WifiAwareRttStateMgr";
+
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private final SparseArray<WifiAwareClientState> 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 AwareRttHandler(looper), messenger);
+    }
+
+    private WifiAwareClientState getAndRemovePendingOperationClient(int rangingId) {
+        WifiAwareClientState client = mPendingOperations.get(rangingId);
+        mPendingOperations.delete(rangingId);
+        return client;
+    }
+
+    /**
+     * Start a ranging operation for the client + peer MAC.
+     */
+    public void startRanging(int rangingId, WifiAwareClientState 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,
+                    "Aware 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 AwareRttHandler extends Handler {
+        AwareRttHandler(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
+            WifiAwareClientState 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("WifiAwareRttStateManager:");
+        pw.println("  mPendingOperations: [" + mPendingOperations + "]");
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareService.java b/service/java/com/android/server/wifi/aware/WifiAwareService.java
new file mode 100644
index 0000000..75efa02
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareService.java
@@ -0,0 +1,73 @@
+/*
+ * 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.aware;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.server.SystemService;
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiInjector;
+
+/**
+ * Service implementing Wi-Fi Aware functionality. Delegates actual interface
+ * implementation to WifiAwareServiceImpl.
+ */
+public final class WifiAwareService extends SystemService {
+    private static final String TAG = "WifiAwareService";
+    final WifiAwareServiceImpl mImpl;
+
+    public WifiAwareService(Context context) {
+        super(context);
+        mImpl = new WifiAwareServiceImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        Log.i(TAG, "Registering " + Context.WIFI_AWARE_SERVICE);
+        publishBinderService(Context.WIFI_AWARE_SERVICE, mImpl);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            WifiInjector wifiInjector = WifiInjector.getInstance();
+            if (wifiInjector == null) {
+                Log.e(TAG, "onBootPhase(PHASE_SYSTEM_SERVICES_READY): NULL injector!");
+                return;
+            }
+
+            HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager();
+            halDeviceManager.initialize();
+
+            WifiAwareStateManager wifiAwareStateManager = new WifiAwareStateManager();
+            WifiAwareNativeCallback wifiAwareNativeCallback = new WifiAwareNativeCallback(
+                    wifiAwareStateManager);
+            WifiAwareNativeManager wifiAwareNativeManager = new WifiAwareNativeManager(
+                    wifiAwareStateManager, halDeviceManager, wifiAwareNativeCallback);
+            WifiAwareNativeApi wifiAwareNativeApi = new WifiAwareNativeApi(wifiAwareNativeManager);
+            wifiAwareStateManager.setNative(wifiAwareNativeApi);
+
+            HandlerThread awareHandlerThread = wifiInjector.getWifiAwareHandlerThread();
+            mImpl.start(awareHandlerThread, wifiAwareStateManager);
+        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+            mImpl.startLate();
+        }
+    }
+}
+
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
new file mode 100644
index 0000000..fa695cd
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -0,0 +1,417 @@
+/*
+ * 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.aware;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.wifi.V1_0.NanStatusType;
+import android.net.wifi.RttManager;
+import android.net.wifi.aware.Characteristics;
+import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.DiscoverySession;
+import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
+import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareManager;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
+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 IWifiAwareManager AIDL interface. Performs validity
+ * (permission and clientID-UID mapping) checks and delegates execution to the
+ * WifiAwareStateManager singleton handler. Limited state to feedback which has to
+ * be provided instantly: client and session IDs.
+ */
+public class WifiAwareServiceImpl extends IWifiAwareManager.Stub {
+    private static final String TAG = "WifiAwareService";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private Context mContext;
+    private WifiAwareStateManager mStateManager;
+
+    private final Object mLock = new Object();
+    private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
+            new SparseArray<>();
+    private int mNextClientId = 1;
+    private int mNextRangingId = 1;
+    private final SparseIntArray mUidByClientId = new SparseIntArray();
+
+    public WifiAwareServiceImpl(Context context) {
+        mContext = context.getApplicationContext();
+    }
+
+    /**
+     * 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(HandlerThread handlerThread, WifiAwareStateManager awareStateManager) {
+        Log.i(TAG, "Starting Wi-Fi Aware service");
+
+        mStateManager = awareStateManager;
+        mStateManager.start(mContext, handlerThread.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 Aware service");
+
+        mStateManager.startLate();
+    }
+
+    @Override
+    public boolean isUsageEnabled() {
+        enforceAccessPermission();
+
+        return mStateManager.isUsageEnabled();
+    }
+
+    @Override
+    public Characteristics getCharacteristics() {
+        enforceAccessPermission();
+
+        return mStateManager.getCapabilities() == null ? null
+                : mStateManager.getCapabilities().toPublicCharacteristics();
+    }
+
+    @Override
+    public void connect(final IBinder binder, String callingPackage,
+            IWifiAwareEventCallback callback, ConfigRequest configRequest,
+            boolean notifyOnIdentityChanged) {
+        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 (notifyOnIdentityChanged) {
+            enforceLocationPermission();
+        }
+
+        if (configRequest != null) {
+            enforceConnectivityInternalPermission();
+        } else {
+            configRequest = new ConfigRequest.Builder().build();
+        }
+        configRequest.validate();
+
+        final int uid = getMockableCallingUid();
+        int pid = getCallingPid();
+
+        final int clientId;
+        synchronized (mLock) {
+            clientId = mNextClientId++;
+        }
+
+        if (VDBG) {
+            Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest"
+                    + configRequest + ", notifyOnIdentityChanged=" + notifyOnIdentityChanged);
+        }
+
+        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                if (DBG) Log.d(TAG, "binderDied: clientId=" + clientId);
+                binder.unlinkToDeath(this, 0);
+
+                synchronized (mLock) {
+                    mDeathRecipientsByClientId.delete(clientId);
+                    mUidByClientId.delete(clientId);
+                }
+
+                mStateManager.disconnect(clientId);
+            }
+        };
+
+        try {
+            binder.linkToDeath(dr, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error on linkToDeath - " + e);
+            try {
+                callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e1) {
+                Log.e(TAG, "Error on onConnectFail()");
+            }
+            return;
+        }
+
+        synchronized (mLock) {
+            mDeathRecipientsByClientId.put(clientId, dr);
+            mUidByClientId.put(clientId, uid);
+        }
+
+        mStateManager.connect(clientId, uid, pid, callingPackage, callback, configRequest,
+                notifyOnIdentityChanged);
+    }
+
+    @Override
+    public void disconnect(int clientId, IBinder binder) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid + ", clientId=" + clientId);
+
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
+
+        synchronized (mLock) {
+            IBinder.DeathRecipient dr = mDeathRecipientsByClientId.get(clientId);
+            if (dr != null) {
+                binder.unlinkToDeath(dr, 0);
+                mDeathRecipientsByClientId.delete(clientId);
+            }
+            mUidByClientId.delete(clientId);
+        }
+
+        mStateManager.disconnect(clientId);
+    }
+
+    @Override
+    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,
+            IWifiAwareDiscoverySessionCallback callback) {
+        enforceAccessPermission();
+        enforceChangePermission();
+        enforceLocationPermission();
+
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (publishConfig == null) {
+            throw new IllegalArgumentException("PublishConfig must not be null");
+        }
+        publishConfig.assertValid(mStateManager.getCharacteristics());
+
+        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.assertValid(mStateManager.getCharacteristics());
+
+        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,
+            IWifiAwareDiscoverySessionCallback callback) {
+        enforceAccessPermission();
+        enforceChangePermission();
+        enforceLocationPermission();
+
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (subscribeConfig == null) {
+            throw new IllegalArgumentException("SubscribeConfig must not be null");
+        }
+        subscribeConfig.assertValid(mStateManager.getCharacteristics());
+
+        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.assertValid(mStateManager.getCharacteristics());
+
+        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 messageId,
+            int retryCount) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (retryCount != 0) {
+            enforceConnectivityInternalPermission();
+        }
+
+        if (message != null
+                && message.length > mStateManager.getCharacteristics().getMaxServiceNameLength()) {
+            throw new IllegalArgumentException(
+                    "Message length longer than supported by device characteristics");
+        }
+        if (retryCount < 0 || retryCount > DiscoverySession.getMaxSendRetryCount()) {
+            throw new IllegalArgumentException("Invalid 'retryCount' must be non-negative "
+                    + "and <= DiscoverySession.MAX_SEND_RETRY_COUNT");
+        }
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG,
+                    "sendMessage: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
+                            + clientId + ", peerId=" + peerId + ", messageId=" + messageId
+                            + ", retryCount=" + retryCount);
+        }
+
+        mStateManager.sendMessage(clientId, sessionId, peerId, message, messageId, retryCount);
+    }
+
+    @Override
+    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
+        enforceAccessPermission();
+        enforceLocationPermission();
+
+        // TODO: b/35676064 restricts access to this API until decide if will open.
+        enforceConnectivityInternalPermission();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
+                    + ", parms=" + Arrays.toString(params.mParams));
+        }
+
+        if (params.mParams.length == 0) {
+            throw new IllegalArgumentException("Empty ranging parameters");
+        }
+
+        int rangingId;
+        synchronized (mLock) {
+            rangingId = mNextRangingId++;
+        }
+        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
+        return rangingId;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump WifiAwareService from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+        pw.println("Wi-Fi Aware Service");
+        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);
+    }
+
+    private void enforceChangePermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
+    }
+
+    private void enforceLocationPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
+                TAG);
+    }
+
+    private void enforceConnectivityInternalPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                TAG);
+    }
+}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
new file mode 100644
index 0000000..a35c786
--- /dev/null
+++ b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -0,0 +1,2707 @@
+/*
+ * 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.aware;
+
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.wifi.V1_0.NanStatusType;
+import android.net.wifi.RttManager;
+import android.net.wifi.aware.Characteristics;
+import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
+import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
+import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+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.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Manages the state of the Wi-Fi Aware system service.
+ */
+public class WifiAwareStateManager {
+    private static final String TAG = "WifiAwareStateManager";
+    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";
+
+    /*
+     * 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_AWARE_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[]{WifiAwareStateManager.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_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_SSI_DATA = "ssi_data";
+    private static final String MESSAGE_BUNDLE_KEY_FILTER_DATA = "filter_data";
+    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_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_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_PID = "pid";
+    private static final String MESSAGE_BUNDLE_KEY_CALLING_PACKAGE = "calling_package";
+    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 static final String MESSAGE_BUNDLE_KEY_NOTIFY_IDENTITY_CHANGE = "notify_identity_chg";
+    private static final String MESSAGE_BUNDLE_KEY_PMK = "pmk";
+    private static final String MESSAGE_BUNDLE_KEY_PASSPHRASE = "passphrase";
+
+    private WifiAwareNativeApi mWifiAwareNativeApi;
+
+    /*
+     * Asynchronous access with no lock
+     */
+    private volatile boolean mUsageEnabled = false;
+
+    /*
+     * Synchronous access: state is only accessed through the state machine
+     * handler thread: no need to use a lock.
+     */
+    private Context mContext;
+    private volatile Capabilities mCapabilities;
+    private volatile Characteristics mCharacteristics = null;
+    private WifiAwareStateMachine mSm;
+    private WifiAwareRttStateManager mRtt;
+    private WifiAwareDataPathStateManager mDataPathMgr;
+
+    private final SparseArray<WifiAwareClientState> mClients = new SparseArray<>();
+    private ConfigRequest mCurrentAwareConfiguration = null;
+    private boolean mCurrentIdentityNotification = false;
+
+    private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
+    private byte[] mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
+
+    public WifiAwareStateManager() {
+        // empty
+    }
+
+    public void setNative(WifiAwareNativeApi wifiAwareNativeApi) {
+        mWifiAwareNativeApi = wifiAwareNativeApi;
+    }
+
+    /**
+     * 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()");
+
+        mContext = context;
+        mSm = new WifiAwareStateMachine(TAG, looper);
+        mSm.setDbg(DBG);
+        mSm.start();
+
+        mRtt = new WifiAwareRttStateManager();
+        mDataPathMgr = new WifiAwareDataPathStateManager(this);
+        mDataPathMgr.start(mContext, mSm.getHandler().getLooper());
+    }
+
+    /**
+     * Initialize the late-initialization sub-services: depend on other services already existing.
+     */
+    public void startLate() {
+        mRtt.start(mContext, mSm.getHandler().getLooper());
+    }
+
+    /**
+     * Get the client state for the specified ID (or null if none exists).
+     */
+    /* package */ WifiAwareClientState getClient(int clientId) {
+        return mClients.get(clientId);
+    }
+
+    /**
+     * Get the capabilities.
+     */
+    public Capabilities getCapabilities() {
+        return mCapabilities;
+    }
+
+    /**
+     * Get the public characteristics derived from the capabilities. Use lazy initialization.
+     */
+    public Characteristics getCharacteristics() {
+        if (mCharacteristics == null && mCapabilities != null) {
+            mCharacteristics = mCapabilities.toPublicCharacteristics();
+        }
+
+        return mCharacteristics;
+    }
+
+    /*
+     * COMMANDS
+     */
+
+    /**
+     * Place a request for a new client connection on the state machine queue.
+     */
+    public void connect(int clientId, int uid, int pid, String callingPackage,
+            IWifiAwareEventCallback callback, ConfigRequest configRequest,
+            boolean notifyOnIdentityChanged) {
+        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);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_PID, pid);
+        msg.getData().putString(MESSAGE_BUNDLE_KEY_CALLING_PACKAGE, callingPackage);
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_NOTIFY_IDENTITY_CHANGE,
+                notifyOnIdentityChanged);
+        mSm.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);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Place a request to start a new publish discovery session on the state
+     * machine queue.
+     */
+    public void publish(int clientId, PublishConfig publishConfig,
+            IWifiAwareDiscoverySessionCallback 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);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Place a request to start a new subscribe discovery session on the state
+     * machine queue.
+     */
+    public void subscribe(int clientId, SubscribeConfig subscribeConfig,
+            IWifiAwareDiscoverySessionCallback 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);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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 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_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 Aware. Doesn't actually turn on Aware (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 Aware. Terminates all existing clients with onAwareDown().
+     */
+    public void disableUsage() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DISABLE_USAGE;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Checks whether Aware usage is enabled (not necessarily that Aware is up right
+     * now) or disabled.
+     *
+     * @return A boolean indicating whether Aware usage is enabled (true) or
+     *         disabled (false).
+     */
+    public boolean isUsageEnabled() {
+        return mUsageEnabled;
+    }
+
+    /**
+     * Get the capabilities of the current Aware firmware.
+     */
+    public void queryCapabilities() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_GET_CAPABILITIES;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Create all Aware 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 Aware 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(WifiAwareNetworkSpecifier networkSpecifier, int peerId,
+            int channelRequestType, int channel, byte[] peer, String interfaceName, byte[] pmk,
+            String passphrase) {
+        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_PMK, pmk);
+        msg.getData().putString(MESSAGE_BUNDLE_KEY_PASSPHRASE, passphrase);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Command to respond to the data-path request (executed by the responder).
+     */
+    public void respondToDataPathRequest(boolean accept, int ndpId, String interfaceName,
+            byte[] pmk, String passphrase) {
+        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().putByteArray(MESSAGE_BUNDLE_KEY_PMK, pmk);
+        msg.getData().putString(MESSAGE_BUNDLE_KEY_PASSPHRASE, passphrase);
+        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);
+    }
+
+    /**
+     * Aware 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 Aware stack.
+     */
+    public void onCapabilitiesUpdateResponse(short transactionId,
+            Capabilities capabilities) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_CAPABILITIES_UPDATED;
+        msg.arg2 = transactionId;
+        msg.obj = capabilities;
+        mSm.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);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Response from firmware to initiateDataPathSetup(...). 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);
+    }
+
+    /**
+     * Response from firmware to initiateDataPathSetup(...).
+     * 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);
+    }
+
+    /**
+     * Response from firmware to
+     * {@link #respondToDataPathRequest(boolean, int, String, byte[], 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);
+    }
+
+    /**
+     * 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);
+    }
+
+    /*
+     * NOTIFICATIONS
+     */
+
+    /**
+     * 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;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * 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;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * 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, byte[] matchFilter) {
+        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().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
+        mSm.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);
+    }
+
+    /**
+     * 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) {
+        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);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: Aware is going down.
+     */
+    public void onAwareDownNotification(int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_AWARE_DOWN;
+        msg.arg2 = reason;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * 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) {
+        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);
+        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) {
+        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);
+        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 WifiAwareStateMachine 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 AWARE_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 AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT = 5_000;
+        private final Map<WifiAwareNetworkSpecifier, WakeupMessage>
+                mDataPathConfirmTimeoutMessages = new ArrayMap<>();
+
+        WifiAwareStateMachine(String name, Looper looper) {
+            super(name, looper);
+
+            addState(mDefaultState);
+            /* --> */ addState(mWaitState, mDefaultState);
+            /* --> */ addState(mWaitForResponseState, mDefaultState);
+
+            setInitialState(mWaitState);
+        }
+
+        public void onAwareDownCleanupSendQueueState() {
+            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: {
+                        WifiAwareNetworkSpecifier networkSpecifier =
+                                (WifiAwareNetworkSpecifier) 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;
+            }
+        }
+
+        private class WaitState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                if (VDBG) {
+                    Log.v(TAG, getName() + msg.toString());
+                }
+
+                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 */
+                }
+
+                return NOT_HANDLED;
+            }
+        }
+
+        private class WaitForResponseState extends State {
+            private static final long AWARE_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() + AWARE_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 NOTIFICATION_TYPE_CLUSTER_CHANGE: {
+                    int flag = msg.arg2;
+                    byte[] clusterId = (byte[]) msg.obj;
+
+                    onClusterChangeLocal(flag, clusterId);
+                    break;
+                }
+                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);
+                    byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA);
+
+                    onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo,
+                            matchFilter);
+                    break;
+                }
+                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);
+
+                    onMessageReceivedLocal(pubSubId, requestorInstanceId, peerMac, message);
+                    break;
+                }
+                case NOTIFICATION_TYPE_AWARE_DOWN: {
+                    int reason = msg.arg2;
+
+                    /*
+                     * TODO: b/28615938. Use reason code to determine whether or not need clean-up
+                     * local state (only needed if AWARE_DOWN is due to internal firmware reason,
+                     * e.g. concurrency, rather than due to a requested shutdown).
+                     */
+
+                    onAwareDownLocal();
+
+                    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 == NanStatusType.NO_OTA_ACK) {
+                            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: {
+                    WifiAwareNetworkSpecifier networkSpecifier = mDataPathMgr.onDataPathRequest(
+                            msg.arg2, msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS),
+                            (int) msg.obj);
+
+                    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() + AWARE_WAIT_FOR_DP_CONFIRM_TIMEOUT);
+                    }
+
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_DATA_PATH_CONFIRM: {
+                    WifiAwareNetworkSpecifier 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));
+
+                    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.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;
+                    IWifiAwareEventCallback callback = (IWifiAwareEventCallback) msg.obj;
+                    ConfigRequest configRequest = (ConfigRequest) msg.getData()
+                            .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+                    int uid = msg.getData().getInt(MESSAGE_BUNDLE_KEY_UID);
+                    int pid = msg.getData().getInt(MESSAGE_BUNDLE_KEY_PID);
+                    String callingPackage = msg.getData().getString(
+                            MESSAGE_BUNDLE_KEY_CALLING_PACKAGE);
+                    boolean notifyIdentityChange = msg.getData().getBoolean(
+                            MESSAGE_BUNDLE_KEY_NOTIFY_IDENTITY_CHANGE);
+
+                    waitForResponse = connectLocal(mCurrentTransactionId, clientId, uid, pid,
+                            callingPackage, callback, configRequest, notifyIdentityChange);
+                    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;
+                    IWifiAwareDiscoverySessionCallback callback =
+                            (IWifiAwareDiscoverySessionCallback) 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;
+                    IWifiAwareDiscoverySessionCallback callback =
+                            (IWifiAwareDiscoverySessionCallback) 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);
+
+                        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_SENT_MESSAGE, sendMessage);
+
+                        waitForResponse = sendFollowonMessageLocal(mCurrentTransactionId, clientId,
+                                sessionId, peerId, message, 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 = mWifiAwareNativeApi.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 = mWifiAwareNativeApi.createAwareNetworkInterface(
+                            mCurrentTransactionId, (String) msg.obj);
+                    break;
+                case COMMAND_TYPE_DELETE_DATA_PATH_INTERFACE:
+                    waitForResponse = mWifiAwareNativeApi.deleteAwareNetworkInterface(
+                            mCurrentTransactionId, (String) msg.obj);
+                    break;
+                case COMMAND_TYPE_INITIATE_DATA_PATH_SETUP: {
+                    Bundle data = msg.getData();
+
+                    WifiAwareNetworkSpecifier networkSpecifier =
+                            (WifiAwareNetworkSpecifier) 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[] pmk = data.getByteArray(MESSAGE_BUNDLE_KEY_PMK);
+                    String passphrase = data.getString(MESSAGE_BUNDLE_KEY_PASSPHRASE);
+
+                    waitForResponse = initiateDataPathSetupLocal(mCurrentTransactionId,
+                            networkSpecifier, peerId, channelRequestType, channel, peer,
+                            interfaceName, pmk, passphrase);
+
+                    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() + AWARE_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);
+                    byte[] pmk = data.getByteArray(MESSAGE_BUNDLE_KEY_PMK);
+                    String passphrase = data.getString(MESSAGE_BUNDLE_KEY_PASSPHRASE);
+
+                    waitForResponse = respondToDataPathRequestLocal(mCurrentTransactionId, accept,
+                            ndpId, interfaceName, pmk, passphrase);
+
+                    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!");
+                    }
+                    int reason = (Integer) msg.obj;
+                    if (reason == NanStatusType.FOLLOWUP_TX_QUEUE_FULL) {
+                        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");
+                        }
+                    } else {
+                        Message sentMessage = mCurrentCommand.getData().getParcelable(
+                                MESSAGE_BUNDLE_KEY_SENT_MESSAGE);
+                        onMessageSendFailLocal(sentMessage, NanStatusType.INTERNAL_FAILURE);
+                        if (!mSendQueueBlocked) {
+                            transmitNextMessage();
+                        }
+                    }
+                    break;
+                }
+                case RESPONSE_TYPE_ON_CAPABILITIES_UPDATED: {
+                    onCapabilitiesUpdatedResponseLocal((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, NanStatusType.INTERNAL_FAILURE);
+                    break;
+                }
+                case COMMAND_TYPE_DISCONNECT: {
+                    /*
+                     * Will only get here on DISCONNECT if was downgrading. The
+                     * callback will do a NOP - but should still call it.
+                     */
+                    onConfigFailedLocal(mCurrentCommand, NanStatusType.INTERNAL_FAILURE);
+                    break;
+                }
+                case COMMAND_TYPE_TERMINATE_SESSION: {
+                    Log.wtf(TAG, "processTimeout: TERMINATE_SESSION - shouldn't be waiting!");
+                    break;
+                }
+                case COMMAND_TYPE_PUBLISH: {
+                    onSessionConfigFailLocal(mCurrentCommand, true, NanStatusType.INTERNAL_FAILURE);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_PUBLISH: {
+                    onSessionConfigFailLocal(mCurrentCommand, true, NanStatusType.INTERNAL_FAILURE);
+                    break;
+                }
+                case COMMAND_TYPE_SUBSCRIBE: {
+                    onSessionConfigFailLocal(mCurrentCommand, false,
+                            NanStatusType.INTERNAL_FAILURE);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_SUBSCRIBE: {
+                    onSessionConfigFailLocal(mCurrentCommand, false,
+                            NanStatusType.INTERNAL_FAILURE);
+                    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, NanStatusType.INTERNAL_FAILURE);
+                    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)
+                        + AWARE_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 + AWARE_SEND_MESSAGE_TIMEOUT <= currentTime) {
+                    if (VDBG) {
+                        Log.v(TAG, "processSendMessageTimeout: expiring - transactionId="
+                                + transactionId + ", message=" + message
+                                + ", due to messageEnqueueTime=" + messageEnqueueTime
+                                + ", currentTime=" + currentTime);
+                    }
+                    onMessageSendFailLocal(message, NanStatusType.INTERNAL_FAILURE);
+                    it.remove();
+                    first = false;
+                } else {
+                    break;
+                }
+            }
+            updateSendMessageTimeout();
+            mSendQueueBlocked = false;
+            transmitNextMessage();
+        }
+
+        @Override
+        protected String getLogRecString(Message msg) {
+            StringBuilder sb = new StringBuilder(WifiAwareStateManager.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("WifiAwareStateMachine:");
+            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 sendAwareStateChangedBroadcast(boolean enabled) {
+        if (VDBG) {
+            Log.v(TAG, "sendAwareStateChangedBroadcast: enabled=" + enabled);
+        }
+        final Intent intent = new Intent(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    /*
+     * COMMANDS
+     */
+
+    private boolean connectLocal(short transactionId, int clientId, int uid, int pid,
+            String callingPackage, IWifiAwareEventCallback callback, ConfigRequest configRequest,
+            boolean notifyIdentityChange) {
+        if (VDBG) {
+            Log.v(TAG, "connectLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", uid=" + uid + ", pid=" + pid + ", callingPackage=" + callingPackage
+                    + ", callback=" + callback + ", configRequest=" + configRequest
+                    + ", notifyIdentityChange=" + notifyIdentityChange);
+        }
+
+        if (!mUsageEnabled) {
+            Log.w(TAG, "connect(): called with mUsageEnabled=false");
+            return false;
+        }
+
+        if (mClients.get(clientId) != null) {
+            Log.e(TAG, "connectLocal: entry already exists for clientId=" + clientId);
+        }
+
+        if (VDBG) {
+            Log.v(TAG, "mCurrentAwareConfiguration=" + mCurrentAwareConfiguration
+                    + ", mCurrentIdentityNotification=" + mCurrentIdentityNotification);
+        }
+
+        ConfigRequest merged = mergeConfigRequests(configRequest);
+        if (merged == null) {
+            Log.e(TAG, "connectLocal: requested configRequest=" + configRequest
+                    + ", incompatible with current configurations");
+            try {
+                callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectFail(): RemoteException (FYI): " + e);
+            }
+            return false;
+        } else if (VDBG) {
+            Log.v(TAG, "connectLocal: merged=" + merged);
+        }
+
+        if (mCurrentAwareConfiguration != null && mCurrentAwareConfiguration.equals(merged)
+                && mCurrentIdentityNotification == notifyIdentityChange) {
+            try {
+                callback.onConnectSuccess(clientId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectSuccess(): RemoteException (FYI): " + e);
+            }
+            WifiAwareClientState client = new WifiAwareClientState(mContext, clientId, uid, pid,
+                    callingPackage, callback, configRequest, notifyIdentityChange);
+            client.onInterfaceAddressChange(mCurrentDiscoveryInterfaceMac);
+            mClients.append(clientId, client);
+            return false;
+        }
+        boolean notificationRequired =
+                doesAnyClientNeedIdentityChangeNotifications() || notifyIdentityChange;
+
+        boolean success = mWifiAwareNativeApi.enableAndConfigure(transactionId, merged,
+                notificationRequired, mCurrentAwareConfiguration == null);
+        if (!success) {
+            try {
+                callback.onConnectFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectFail(): RemoteException (FYI):  " + e);
+            }
+        }
+
+        return success;
+    }
+
+    private boolean disconnectLocal(short transactionId, int clientId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "disconnectLocal(): transactionId=" + transactionId + ", clientId=" + clientId);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "disconnectLocal: no entry for clientId=" + clientId);
+            return false;
+        }
+        mClients.delete(clientId);
+        client.destroy();
+
+        if (mClients.size() == 0) {
+            mCurrentAwareConfiguration = null;
+            mWifiAwareNativeApi.disable((short) 0);
+            return false;
+        }
+
+        ConfigRequest merged = mergeConfigRequests(null);
+        if (merged == null) {
+            Log.wtf(TAG, "disconnectLocal: got an incompatible merge on remaining configs!?");
+            return false;
+        }
+        boolean notificationReqs = doesAnyClientNeedIdentityChangeNotifications();
+        if (merged.equals(mCurrentAwareConfiguration)
+                && mCurrentIdentityNotification == notificationReqs) {
+            return false;
+        }
+
+        return mWifiAwareNativeApi.enableAndConfigure(transactionId, merged, notificationReqs,
+                false);
+    }
+
+    private void terminateSessionLocal(int clientId, int sessionId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "terminateSessionLocal(): clientId=" + clientId + ", sessionId=" + sessionId);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "terminateSession: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        client.terminateSession(sessionId);
+    }
+
+    private boolean publishLocal(short transactionId, int clientId, PublishConfig publishConfig,
+            IWifiAwareDiscoverySessionCallback callback) {
+        if (VDBG) {
+            Log.v(TAG, "publishLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", publishConfig=" + publishConfig + ", callback=" + callback);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "publishLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        boolean success = mWifiAwareNativeApi.publish(transactionId, 0, publishConfig);
+        if (!success) {
+            try {
+                callback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "publishLocal onSessionConfigFail(): RemoteException (FYI): " + e);
+            }
+        }
+
+        return success;
+    }
+
+    private boolean updatePublishLocal(short transactionId, int clientId, int sessionId,
+            PublishConfig publishConfig) {
+        if (VDBG) {
+            Log.v(TAG, "updatePublishLocal(): transactionId=" + transactionId + ", clientId="
+                    + clientId + ", sessionId=" + sessionId + ", publishConfig=" + publishConfig);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "updatePublishLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiAwareDiscoverySessionState 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, IWifiAwareDiscoverySessionCallback callback) {
+        if (VDBG) {
+            Log.v(TAG, "subscribeLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", subscribeConfig=" + subscribeConfig + ", callback=" + callback);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "subscribeLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        boolean success = mWifiAwareNativeApi.subscribe(transactionId, 0, subscribeConfig);
+        if (!success) {
+            try {
+                callback.onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+            } catch (RemoteException e) {
+                Log.w(TAG, "subscribeLocal onSessionConfigFail(): RemoteException (FYI): " + e);
+            }
+        }
+
+        return success;
+    }
+
+    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);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "updateSubscribeLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiAwareDiscoverySessionState 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 messageId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "sendFollowonMessageLocal(): transactionId=" + transactionId + ", clientId="
+                            + clientId + ", sessionId=" + sessionId + ", peerId=" + peerId
+                            + ", messageId=" + messageId);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "sendFollowonMessageLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiAwareDiscoverySessionState 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, messageId);
+    }
+
+    private void enableUsageLocal() {
+        if (VDBG) Log.v(TAG, "enableUsageLocal: mUsageEnabled=" + mUsageEnabled);
+
+        if (mUsageEnabled) {
+            return;
+        }
+
+        mUsageEnabled = true;
+        queryCapabilities();
+        createAllDataPathInterfaces();
+        sendAwareStateChangedBroadcast(true);
+    }
+
+    private void disableUsageLocal() {
+        if (VDBG) Log.v(TAG, "disableUsageLocal: mUsageEnabled=" + mUsageEnabled);
+
+        if (!mUsageEnabled) {
+            return;
+        }
+
+        onAwareDownLocal();
+        deleteAllDataPathInterfaces();
+
+        mUsageEnabled = false;
+        mWifiAwareNativeApi.disable((short) 0);
+
+        sendAwareStateChangedBroadcast(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);
+        }
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "startRangingLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiAwareDiscoverySessionState 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 = "";
+            }
+        }
+
+        mRtt.startRanging(rangingId, client, params);
+    }
+
+    private boolean initiateDataPathSetupLocal(short transactionId,
+            WifiAwareNetworkSpecifier networkSpecifier, int peerId, int channelRequestType,
+            int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "initiateDataPathSetupLocal(): transactionId=" + transactionId
+                            + ", networkSpecifier=" + networkSpecifier + ", peerId=" + peerId
+                            + ", channelRequestType=" + channelRequestType + ", channel=" + channel
+                            + ", peer=" + String.valueOf(HexEncoding.encode(peer))
+                            + ", interfaceName=" + interfaceName + ", pmk=" + pmk
+                            + ", passphrase=" + passphrase);
+        }
+
+        boolean success = mWifiAwareNativeApi.initiateDataPath(transactionId, peerId,
+                channelRequestType, channel, peer, interfaceName, pmk, passphrase, mCapabilities);
+        if (!success) {
+            mDataPathMgr.onDataPathInitiateFail(networkSpecifier, NanStatusType.INTERNAL_FAILURE);
+        }
+
+        return success;
+    }
+
+    private boolean respondToDataPathRequestLocal(short transactionId, boolean accept,
+            int ndpId, String interfaceName, byte[] pmk, String passphrase) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "respondToDataPathRequestLocal(): transactionId=" + transactionId + ", accept="
+                            + accept + ", ndpId=" + ndpId + ", interfaceName=" + interfaceName
+                            + ", pmk=" + pmk + ", passphrase=" + passphrase);
+        }
+
+        boolean success = mWifiAwareNativeApi.respondToDataPathRequest(transactionId, accept, ndpId,
+                interfaceName, pmk, passphrase, mCapabilities);
+        if (!success) {
+            mDataPathMgr.onRespondToDataPathRequest(ndpId, false);
+        }
+        return success;
+    }
+
+    private boolean endDataPathLocal(short transactionId, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "endDataPathLocal: transactionId=" + transactionId + ", ndpId=" + ndpId);
+        }
+
+        return mWifiAwareNativeApi.endDataPath(transactionId, ndpId);
+    }
+
+    /*
+     * RESPONSES
+     */
+
+    private void onConfigCompletedLocal(Message completedCommand) {
+        if (VDBG) {
+            Log.v(TAG, "onConfigCompleted: completedCommand=" + completedCommand);
+        }
+
+        if (completedCommand.arg1 == COMMAND_TYPE_CONNECT) {
+            Bundle data = completedCommand.getData();
+
+            int clientId = completedCommand.arg2;
+            IWifiAwareEventCallback callback = (IWifiAwareEventCallback) completedCommand.obj;
+            ConfigRequest configRequest = (ConfigRequest) data
+                    .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+            int uid = data.getInt(MESSAGE_BUNDLE_KEY_UID);
+            int pid = data.getInt(MESSAGE_BUNDLE_KEY_PID);
+            boolean notifyIdentityChange = data.getBoolean(
+                    MESSAGE_BUNDLE_KEY_NOTIFY_IDENTITY_CHANGE);
+            String callingPackage = data.getString(MESSAGE_BUNDLE_KEY_CALLING_PACKAGE);
+
+            WifiAwareClientState client = new WifiAwareClientState(mContext, clientId, uid, pid,
+                    callingPackage, callback, configRequest, notifyIdentityChange);
+            mClients.put(clientId, client);
+            try {
+                callback.onConnectSuccess(clientId);
+            } 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;
+        }
+
+        mCurrentAwareConfiguration = mergeConfigRequests(null);
+        if (mCurrentAwareConfiguration == null) {
+            Log.wtf(TAG, "onConfigCompletedLocal: got a null merged configuration after config!?");
+        }
+        mCurrentIdentityNotification = doesAnyClientNeedIdentityChangeNotifications();
+    }
+
+    private void onConfigFailedLocal(Message failedCommand, int reason) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onConfigFailedLocal: failedCommand=" + failedCommand + ", reason=" + reason);
+        }
+
+        if (failedCommand.arg1 == COMMAND_TYPE_CONNECT) {
+            IWifiAwareEventCallback callback = (IWifiAwareEventCallback) 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;
+            IWifiAwareDiscoverySessionCallback callback =
+                    (IWifiAwareDiscoverySessionCallback) completedCommand.obj;
+
+            WifiAwareClientState 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;
+            }
+
+            WifiAwareDiscoverySessionState session = new WifiAwareDiscoverySessionState(
+                    mWifiAwareNativeApi, 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);
+
+            WifiAwareClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG,
+                        "onSessionConfigSuccessLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            WifiAwareDiscoverySessionState 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) {
+            IWifiAwareDiscoverySessionCallback callback =
+                    (IWifiAwareDiscoverySessionCallback) 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);
+
+            WifiAwareClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG, "onSessionConfigFailLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            WifiAwareDiscoverySessionState 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);
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "onMessageSendSuccessLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiAwareDiscoverySessionState 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);
+
+        WifiAwareClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "onMessageSendFailLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiAwareDiscoverySessionState 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(Capabilities capabilities) {
+        if (VDBG) {
+            Log.v(TAG, "onCapabilitiesUpdatedResponseLocal: capabilites=" + capabilities);
+        }
+
+        mCapabilities = capabilities;
+        mCharacteristics = null;
+    }
+
+    private void onCreateDataPathInterfaceResponseLocal(Message command, boolean success,
+            int reasonOnFailure) {
+        if (VDBG) {
+            Log.v(TAG, "onCreateDataPathInterfaceResponseLocal: command=" + command + ", success="
+                    + success + ", reasonOnFailure=" + reasonOnFailure);
+        }
+
+        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 onDeleteDataPathInterfaceResponseLocal(Message command, boolean success,
+            int reasonOnFailure) {
+        if (VDBG) {
+            Log.v(TAG, "onDeleteDataPathInterfaceResponseLocal: command=" + command + ", success="
+                    + success + ", reasonOnFailure=" + reasonOnFailure);
+        }
+
+        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 onInitiateDataPathResponseSuccessLocal(Message command, int ndpId) {
+        if (VDBG) {
+            Log.v(TAG, "onInitiateDataPathResponseSuccessLocal: command=" + command + ", ndpId="
+                    + ndpId);
+        }
+
+        mDataPathMgr.onDataPathInitiateSuccess((WifiAwareNetworkSpecifier) command.obj, ndpId);
+    }
+
+    private void onInitiateDataPathResponseFailLocal(Message command, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onInitiateDataPathResponseFailLocal: command=" + command + ", reason="
+                    + reason);
+        }
+
+        mDataPathMgr.onDataPathInitiateFail((WifiAwareNetworkSpecifier) command.obj, reason);
+    }
+
+    private void onRespondToDataPathSetupRequestResponseLocal(Message command, boolean success,
+            int reasonOnFailure) {
+        if (VDBG) {
+            Log.v(TAG, "onRespondToDataPathSetupRequestResponseLocal: command=" + command
+                    + ", success=" + success + ", reasonOnFailure=" + reasonOnFailure);
+        }
+
+        mDataPathMgr.onRespondToDataPathRequest(command.arg2, success);
+    }
+
+    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)));
+        }
+
+        mCurrentDiscoveryInterfaceMac = mac;
+
+        for (int i = 0; i < mClients.size(); ++i) {
+            WifiAwareClientState client = mClients.valueAt(i);
+            client.onInterfaceAddressChange(mac);
+        }
+    }
+
+    private void onClusterChangeLocal(int flag, byte[] clusterId) {
+        if (VDBG) {
+            Log.v(TAG, "onClusterChange: flag=" + flag + ", clusterId="
+                    + String.valueOf(HexEncoding.encode(clusterId)));
+        }
+
+        for (int i = 0; i < mClients.size(); ++i) {
+            WifiAwareClientState client = mClients.valueAt(i);
+            client.onClusterChange(flag, clusterId, mCurrentDiscoveryInterfaceMac);
+        }
+    }
+
+    private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
+            byte[] serviceSpecificInfo, byte[] matchFilter) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onMatch: pubSubId=" + pubSubId + ", requestorInstanceId=" + requestorInstanceId
+                            + ", peerDiscoveryMac=" + String.valueOf(HexEncoding.encode(peerMac))
+                            + ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo)
+                            + ", matchFilter=" + Arrays.toString(matchFilter));
+        }
+
+        Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data =
+                getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
+            Log.e(TAG, "onMatch: no session found for pubSubId=" + pubSubId);
+            return;
+        }
+
+        data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter);
+    }
+
+    private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onSessionTerminatedLocal: pubSubId=" + pubSubId + ", isPublish=" + isPublish
+                    + ", reason=" + reason);
+        }
+
+        Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> 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) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onMessageReceivedLocal: pubSubId=" + pubSubId + ", requestorInstanceId="
+                            + requestorInstanceId + ", peerDiscoveryMac="
+                            + String.valueOf(HexEncoding.encode(peerMac)));
+        }
+
+        Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data =
+                getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
+            Log.e(TAG, "onMessageReceivedLocal: no session found for pubSubId=" + pubSubId);
+            return;
+        }
+
+        data.second.onMessageReceived(requestorInstanceId, peerMac, message);
+    }
+
+    private void onAwareDownLocal() {
+        if (VDBG) {
+            Log.v(TAG, "onAwareDown");
+        }
+
+        mClients.clear();
+        mCurrentAwareConfiguration = null;
+        mSm.onAwareDownCleanupSendQueueState();
+        mDataPathMgr.onAwareDownCleanupDataPaths();
+        mCurrentDiscoveryInterfaceMac = ALL_ZERO_MAC;
+    }
+
+    /*
+     * Utilities
+     */
+
+    private Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> getClientSessionForPubSubId(
+            int pubSubId) {
+        for (int i = 0; i < mClients.size(); ++i) {
+            WifiAwareClientState client = mClients.valueAt(i);
+            WifiAwareDiscoverySessionState session = client.getAwareSessionStateForPubSubId(
+                    pubSubId);
+            if (session != null) {
+                return new Pair<>(client, session);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Merge all the existing client configurations with the (optional) input configuration request.
+     * If the configurations are "incompatible" (rules in comment below) return a 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;
+        }
+
+        // TODO: continue working on merge algorithm:
+        // - if any request 5g: enable
+        // - maximal master preference
+        // - cluster range: must be identical
+        // - if any request identity change: enable
+        // - discovery window: minimum value if specified, 0 (disable) is considered an infinity
+        boolean support5gBand = false;
+        int masterPreference = 0;
+        boolean clusterIdValid = false;
+        int clusterLow = 0;
+        int clusterHigh = ConfigRequest.CLUSTER_ID_MAX;
+        int[] discoveryWindowInterval =
+                {ConfigRequest.DW_INTERVAL_NOT_INIT, ConfigRequest.DW_INTERVAL_NOT_INIT};
+        if (configRequest != null) {
+            support5gBand = configRequest.mSupport5gBand;
+            masterPreference = configRequest.mMasterPreference;
+            clusterIdValid = true;
+            clusterLow = configRequest.mClusterLow;
+            clusterHigh = configRequest.mClusterHigh;
+            discoveryWindowInterval = configRequest.mDiscoveryWindowInterval;
+        }
+        for (int i = 0; i < mClients.size(); ++i) {
+            ConfigRequest cr = mClients.valueAt(i).getConfigRequest();
+
+            // any request turns on 5G
+            if (cr.mSupport5gBand) {
+                support5gBand = true;
+            }
+
+            // maximal master preference
+            masterPreference = Math.max(masterPreference, cr.mMasterPreference);
+
+            // cluster range must be the same across all config requests
+            if (!clusterIdValid) {
+                clusterIdValid = true;
+                clusterLow = cr.mClusterLow;
+                clusterHigh = cr.mClusterHigh;
+            } else {
+                if (clusterLow != cr.mClusterLow) return null;
+                if (clusterHigh != cr.mClusterHigh) return null;
+            }
+
+            for (int band = ConfigRequest.NAN_BAND_24GHZ; band <= ConfigRequest.NAN_BAND_5GHZ;
+                    ++band) {
+                if (discoveryWindowInterval[band] == ConfigRequest.DW_INTERVAL_NOT_INIT) {
+                    discoveryWindowInterval[band] = cr.mDiscoveryWindowInterval[band];
+                } else if (cr.mDiscoveryWindowInterval[band]
+                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
+                    // do nothing: keep my values
+                } else if (discoveryWindowInterval[band] == ConfigRequest.DW_DISABLE) {
+                    discoveryWindowInterval[band] = cr.mDiscoveryWindowInterval[band];
+                } else if (cr.mDiscoveryWindowInterval[band] == ConfigRequest.DW_DISABLE) {
+                    // do nothing: keep my values
+                } else {
+                    discoveryWindowInterval[band] = Math.min(discoveryWindowInterval[band],
+                            cr.mDiscoveryWindowInterval[band]);
+                }
+            }
+        }
+        ConfigRequest.Builder builder = new ConfigRequest.Builder().setSupport5gBand(support5gBand)
+                .setMasterPreference(masterPreference).setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh);
+        for (int band = ConfigRequest.NAN_BAND_24GHZ; band <= ConfigRequest.NAN_BAND_5GHZ; ++band) {
+            if (discoveryWindowInterval[band] != ConfigRequest.DW_INTERVAL_NOT_INIT) {
+                builder.setDiscoveryWindowInterval(band, discoveryWindowInterval[band]);
+            }
+        }
+        return builder.build();
+    }
+
+    private boolean doesAnyClientNeedIdentityChangeNotifications() {
+        for (int i = 0; i < mClients.size(); ++i) {
+            if (mClients.valueAt(i).getNotifyIdentityChange()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    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("AwareStateManager:");
+        pw.println("  mClients: [" + mClients + "]");
+        pw.println("  mUsageEnabled: " + mUsageEnabled);
+        pw.println("  mCapabilities: [" + mCapabilities + "]");
+        pw.println("  mCurrentAwareConfiguration: " + mCurrentAwareConfiguration);
+        pw.println("  mCurrentIdentityNotification: " + mCurrentIdentityNotification);
+        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);
+        mWifiAwareNativeApi.dump(fd, pw, args);
+    }
+}
diff --git a/service/java/com/android/server/wifi/configparse/ConfigBuilder.java b/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
deleted file mode 100644
index ad0165e..0000000
--- a/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
+++ /dev/null
@@ -1,404 +0,0 @@
-package com.android.server.wifi.configparse;
-
-import android.content.Context;
-import android.net.Uri;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
-import android.provider.DocumentsContract;
-import android.util.Base64;
-import android.util.Log;
-
-import com.android.server.wifi.IMSIParameter;
-import com.android.server.wifi.anqp.eap.AuthParam;
-import com.android.server.wifi.anqp.eap.EAP;
-import com.android.server.wifi.anqp.eap.EAPMethod;
-import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
-import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
-import com.android.server.wifi.hotspot2.pps.Credential;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
-
-import org.xml.sax.SAXException;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.LineNumberReader;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.security.MessageDigest;
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-
-public class ConfigBuilder {
-    public static final String WifiConfigType = "application/x-wifi-config";
-    private static final String ProfileTag = "application/x-passpoint-profile";
-    private static final String KeyTag = "application/x-pkcs12";
-    private static final String CATag = "application/x-x509-ca-cert";
-
-    private static final String X509 = "X.509";
-
-    private static final String TAG = "WCFG";
-
-    public static WifiConfiguration buildConfig(String uriString, byte[] data, Context context)
-            throws IOException, GeneralSecurityException, SAXException {
-        Log.d(TAG, "Content: " + (data != null ? data.length : -1));
-
-        byte[] b64 = Base64.decode(new String(data, StandardCharsets.ISO_8859_1), Base64.DEFAULT);
-        Log.d(TAG, "Decoded: " + b64.length + " bytes.");
-
-        MIMEContainer mimeContainer = new
-                MIMEContainer(new LineNumberReader(
-                new InputStreamReader(new ByteArrayInputStream(b64), StandardCharsets.ISO_8859_1)),
-                null);
-        if (!mimeContainer.isBase64()) {
-            throw new IOException("Encoding for " +
-                    mimeContainer.getContentType() + " is not base64");
-        }
-        MIMEContainer inner;
-        if (mimeContainer.getContentType().equals(WifiConfigType)) {
-            byte[] wrappedContent = Base64.decode(mimeContainer.getText(), Base64.DEFAULT);
-            Log.d(TAG, "Building container from '" +
-                    new String(wrappedContent, StandardCharsets.ISO_8859_1) + "'");
-            inner = new MIMEContainer(new LineNumberReader(
-                    new InputStreamReader(new ByteArrayInputStream(wrappedContent),
-                            StandardCharsets.ISO_8859_1)), null);
-        }
-        else {
-            inner = mimeContainer;
-        }
-        return parse(inner);
-    }
-
-    private static WifiConfiguration parse(MIMEContainer root)
-            throws IOException, GeneralSecurityException, SAXException {
-
-        if (root.getMimeContainers() == null) {
-            throw new IOException("Malformed MIME content: not multipart");
-        }
-
-        String moText = null;
-        X509Certificate caCert = null;
-        PrivateKey clientKey = null;
-        List<X509Certificate> clientChain = null;
-
-        for (MIMEContainer subContainer : root.getMimeContainers()) {
-            Log.d(TAG, " + Content Type: " + subContainer.getContentType());
-            switch (subContainer.getContentType()) {
-                case ProfileTag:
-                    if (subContainer.isBase64()) {
-                        byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
-                        moText = new String(octets, StandardCharsets.UTF_8);
-                    } else {
-                        moText = subContainer.getText();
-                    }
-                    Log.d(TAG, "OMA: " + moText);
-                    break;
-                case CATag: {
-                    if (!subContainer.isBase64()) {
-                        throw new IOException("Can't read non base64 encoded cert");
-                    }
-
-                    byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
-                    CertificateFactory factory = CertificateFactory.getInstance(X509);
-                    caCert = (X509Certificate) factory.generateCertificate(
-                            new ByteArrayInputStream(octets));
-                    Log.d(TAG, "Cert subject " + caCert.getSubjectX500Principal());
-                    Log.d(TAG, "Full Cert: " + caCert);
-                    break;
-                }
-                case KeyTag: {
-                    if (!subContainer.isBase64()) {
-                        throw new IOException("Can't read non base64 encoded key");
-                    }
-
-                    byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT);
-
-                    KeyStore ks = KeyStore.getInstance("PKCS12");
-                    ByteArrayInputStream in = new ByteArrayInputStream(octets);
-                    ks.load(in, new char[0]);
-                    in.close();
-                    Log.d(TAG, "---- Start PKCS12 info " + octets.length + ", size " + ks.size());
-                    Enumeration<String> aliases = ks.aliases();
-                    while (aliases.hasMoreElements()) {
-                        String alias = aliases.nextElement();
-                        clientKey = (PrivateKey) ks.getKey(alias, null);
-                        Log.d(TAG, "Key: " + clientKey.getFormat());
-                        Certificate[] chain = ks.getCertificateChain(alias);
-                        if (chain != null) {
-                            clientChain = new ArrayList<>();
-                            for (Certificate certificate : chain) {
-                                if (!(certificate instanceof X509Certificate)) {
-                                    Log.w(TAG, "Element in cert chain is not an X509Certificate: " +
-                                            certificate.getClass());
-                                }
-                                clientChain.add((X509Certificate) certificate);
-                            }
-                            Log.d(TAG, "Chain: " + clientChain.size());
-                        }
-                    }
-                    Log.d(TAG, "---- End PKCS12 info.");
-                    break;
-                }
-            }
-        }
-
-        if (moText == null) {
-            throw new IOException("Missing profile");
-        }
-
-        HomeSP homeSP = PasspointManagementObjectManager.buildSP(moText);
-
-        return buildConfig(homeSP, caCert, clientChain, clientKey);
-    }
-
-    private static WifiConfiguration buildConfig(HomeSP homeSP, X509Certificate caCert,
-                                                 List<X509Certificate> clientChain, PrivateKey key)
-            throws IOException, GeneralSecurityException {
-
-        WifiConfiguration config;
-
-        EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
-        switch (eapMethodID) {
-            case EAP_TTLS:
-                if (key != null || clientChain != null) {
-                    Log.w(TAG, "Client cert and/or key unnecessarily included with EAP-TTLS "+
-                            "profile");
-                }
-                config = buildTTLSConfig(homeSP, caCert);
-                break;
-            case EAP_TLS:
-                config = buildTLSConfig(homeSP, clientChain, key, caCert);
-                break;
-            case EAP_AKA:
-            case EAP_AKAPrim:
-            case EAP_SIM:
-                if (key != null || clientChain != null || caCert != null) {
-                    Log.i(TAG, "Client/CA cert and/or key unnecessarily included with " +
-                            eapMethodID + " profile");
-                }
-                config = buildSIMConfig(homeSP);
-                break;
-            default:
-                throw new IOException("Unsupported EAP Method: " + eapMethodID);
-        }
-
-        return config;
-    }
-
-    // Retain for debugging purposes
-    /*
-    private static void xIterateCerts(KeyStore ks, X509Certificate caCert)
-            throws GeneralSecurityException {
-        Enumeration<String> aliases = ks.aliases();
-        while (aliases.hasMoreElements()) {
-            String alias = aliases.nextElement();
-            Certificate cert = ks.getCertificate(alias);
-            Log.d("HS2J", "Checking " + alias);
-            if (cert instanceof X509Certificate) {
-                X509Certificate x509Certificate = (X509Certificate) cert;
-                boolean sm = x509Certificate.getSubjectX500Principal().equals(
-                        caCert.getSubjectX500Principal());
-                boolean eq = false;
-                if (sm) {
-                    eq = Arrays.equals(x509Certificate.getEncoded(), caCert.getEncoded());
-                }
-                Log.d("HS2J", "Subject: " + x509Certificate.getSubjectX500Principal() +
-                        ": " + sm + "/" + eq);
-            }
-        }
-    }
-    */
-
-    private static void setAnonymousIdentityToNaiRealm(
-            WifiConfiguration config, Credential credential) {
-        /**
-         * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so
-         * that this value will be sent to the EAP server as part of the EAP-Response/ Identity
-         * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity
-         * packet, and revert to using the (real) identity field for subsequent transactions that
-         * request an identity (e.g. in EAP-TTLS).
-         *
-         * This NAI realm value (the portion of the identity after the '@') is used to tell the
-         * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a
-         * placeholder that is not used--it is set to this value by convention. See Section 5.1 of
-         * RFC3748 for more details.
-         *
-         * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the
-         * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to
-         * identify the device.
-         */
-        config.enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm());
-    }
-
-    private static WifiConfiguration buildTTLSConfig(HomeSP homeSP, X509Certificate caCert)
-            throws IOException {
-        Credential credential = homeSP.getCredential();
-
-        if (credential.getUserName() == null || credential.getPassword() == null) {
-            throw new IOException("EAP-TTLS provisioned without user name or password");
-        }
-
-        EAPMethod eapMethod = credential.getEAPMethod();
-
-        AuthParam authParam = eapMethod.getAuthParam();
-        if (authParam == null ||
-                authParam.getAuthInfoID() != EAP.AuthInfoID.NonEAPInnerAuthType) {
-            throw new IOException("Bad auth parameter for EAP-TTLS: " + authParam);
-        }
-
-        WifiConfiguration config = buildBaseConfiguration(homeSP);
-        NonEAPInnerAuth ttlsParam = (NonEAPInnerAuth) authParam;
-        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
-        enterpriseConfig.setPhase2Method(remapInnerMethod(ttlsParam.getType()));
-        enterpriseConfig.setIdentity(credential.getUserName());
-        enterpriseConfig.setPassword(credential.getPassword());
-        enterpriseConfig.setCaCertificate(caCert);
-
-        setAnonymousIdentityToNaiRealm(config, credential);
-
-        return config;
-    }
-
-    private static WifiConfiguration buildTLSConfig(HomeSP homeSP,
-                                                    List<X509Certificate> clientChain,
-                                                    PrivateKey clientKey,
-                                                    X509Certificate caCert)
-            throws IOException, GeneralSecurityException {
-
-        Credential credential = homeSP.getCredential();
-
-        X509Certificate clientCertificate = null;
-
-        if (clientKey == null || clientChain == null) {
-            throw new IOException("No key and/or cert passed for EAP-TLS");
-        }
-        if (credential.getCertType() != Credential.CertType.x509v3) {
-            throw new IOException("Invalid certificate type for TLS: " +
-                    credential.getCertType());
-        }
-
-        byte[] reference = credential.getFingerPrint();
-        MessageDigest digester = MessageDigest.getInstance("SHA-256");
-        for (X509Certificate certificate : clientChain) {
-            digester.reset();
-            byte[] fingerprint = digester.digest(certificate.getEncoded());
-            if (Arrays.equals(reference, fingerprint)) {
-                clientCertificate = certificate;
-                break;
-            }
-        }
-        if (clientCertificate == null) {
-            throw new IOException("No certificate in chain matches supplied fingerprint");
-        }
-
-        String alias = Base64.encodeToString(reference, Base64.DEFAULT);
-
-        WifiConfiguration config = buildBaseConfiguration(homeSP);
-        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
-        enterpriseConfig.setClientCertificateAlias(alias);
-        enterpriseConfig.setClientKeyEntry(clientKey, clientCertificate);
-        enterpriseConfig.setCaCertificate(caCert);
-
-        setAnonymousIdentityToNaiRealm(config, credential);
-
-        return config;
-    }
-
-    private static WifiConfiguration buildSIMConfig(HomeSP homeSP)
-            throws IOException {
-
-        Credential credential = homeSP.getCredential();
-        IMSIParameter credImsi = credential.getImsi();
-
-        /*
-         * Uncomment to enforce strict IMSI matching with currently installed SIM cards.
-         *
-        TelephonyManager tm = TelephonyManager.from(context);
-        SubscriptionManager sub = SubscriptionManager.from(context);
-        boolean match = false;
-
-        for (int subId : sub.getActiveSubscriptionIdList()) {
-            String imsi = tm.getSubscriberId(subId);
-            if (credImsi.matches(imsi)) {
-                match = true;
-                break;
-            }
-        }
-        if (!match) {
-            throw new IOException("Supplied IMSI does not match any SIM card");
-        }
-        */
-
-        WifiConfiguration config = buildBaseConfiguration(homeSP);
-        config.enterpriseConfig.setPlmn(credImsi.toString());
-        return config;
-    }
-
-    private static WifiConfiguration buildBaseConfiguration(HomeSP homeSP) throws IOException {
-        EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
-
-        WifiConfiguration config = new WifiConfiguration();
-
-        config.FQDN = homeSP.getFQDN();
-
-        HashSet<Long> roamingConsortiumIds = homeSP.getRoamingConsortiums();
-        config.roamingConsortiumIds = new long[roamingConsortiumIds.size()];
-        int i = 0;
-        for (long id : roamingConsortiumIds) {
-            config.roamingConsortiumIds[i] = id;
-            i++;
-        }
-        config.providerFriendlyName = homeSP.getFriendlyName();
-
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
-
-        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
-        enterpriseConfig.setEapMethod(remapEAPMethod(eapMethodID));
-        enterpriseConfig.setRealm(homeSP.getCredential().getRealm());
-        config.enterpriseConfig = enterpriseConfig;
-        // The framework based config builder only ever builds r1 configs:
-        config.updateIdentifier = null;
-
-        return config;
-    }
-
-    private static int remapEAPMethod(EAP.EAPMethodID eapMethodID) throws IOException {
-        switch (eapMethodID) {
-            case EAP_TTLS:
-                return WifiEnterpriseConfig.Eap.TTLS;
-            case EAP_TLS:
-                return WifiEnterpriseConfig.Eap.TLS;
-            case EAP_SIM:
-                return WifiEnterpriseConfig.Eap.SIM;
-            case EAP_AKA:
-                return WifiEnterpriseConfig.Eap.AKA;
-            case EAP_AKAPrim:
-                return WifiEnterpriseConfig.Eap.AKA_PRIME;
-            default:
-                throw new IOException("Bad EAP method: " + eapMethodID);
-        }
-    }
-
-    private static int remapInnerMethod(NonEAPInnerAuth.NonEAPType type) throws IOException {
-        switch (type) {
-            case PAP:
-                return WifiEnterpriseConfig.Phase2.PAP;
-            case MSCHAP:
-                return WifiEnterpriseConfig.Phase2.MSCHAP;
-            case MSCHAPv2:
-                return WifiEnterpriseConfig.Phase2.MSCHAPV2;
-            case CHAP:
-            default:
-                throw new IOException("Inner method " + type + " not supported");
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/configparse/MIMEContainer.java b/service/java/com/android/server/wifi/configparse/MIMEContainer.java
deleted file mode 100644
index 10ad456..0000000
--- a/service/java/com/android/server/wifi/configparse/MIMEContainer.java
+++ /dev/null
@@ -1,345 +0,0 @@
-package com.android.server.wifi.configparse;
-
-import android.util.Log;
-
-import com.android.server.wifi.hotspot2.Utils;
-
-import java.io.IOException;
-import java.io.LineNumberReader;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class MIMEContainer {
-    private static final String Type = "Content-Type";
-    private static final String Encoding = "Content-Transfer-Encoding";
-
-    private static final String Boundary = "boundary=";
-    private static final String CharsetTag = "charset=";
-
-    private final boolean mLast;
-    private final List<MIMEContainer> mMimeContainers;
-    private final String mText;
-
-    private final boolean mMixed;
-    private final boolean mBase64;
-    private final Charset mCharset;
-    private final String mContentType;
-
-    /**
-     * Parse nested MIME content
-     * @param in A reader to read MIME data from; Note that the charset should be ISO-8859-1 to
-     *           ensure transparent octet to character mapping. This is because the content will
-     *           be re-encoded using the correct charset once it is discovered.
-     * @param boundary A boundary string for the MIME section that this container is in.
-     *                 Pass null for the top level object.
-     * @throws java.io.IOException
-     */
-    public MIMEContainer(LineNumberReader in, String boundary) throws IOException {
-        Map<String,List<String>> headers = parseHeader(in);
-
-        List<String> type = headers.get(Type);
-        if (type == null || type.isEmpty()) {
-            throw new IOException("Missing " + Type + " @ " + in.getLineNumber());
-        }
-
-        boolean multiPart = false;
-        boolean mixed = false;
-        String subBoundary = null;
-        Charset charset = StandardCharsets.ISO_8859_1;
-
-        mContentType = type.get(0);
-
-        if (mContentType.startsWith("multipart/")) {
-            multiPart = true;
-
-            for (String attribute : type) {
-                if (attribute.startsWith(Boundary)) {
-                    subBoundary = Utils.unquote(attribute.substring(Boundary.length()));
-                }
-            }
-
-            if (mContentType.endsWith("/mixed")) {
-                mixed = true;
-            }
-        }
-        else if (mContentType.startsWith("text/")) {
-            for (String attribute : type) {
-                if (attribute.startsWith(CharsetTag)) {
-                    charset = Charset.forName(attribute.substring(CharsetTag.length()));
-                }
-            }
-        }
-
-        mMixed = mixed;
-        mCharset = charset;
-
-        if (multiPart && subBoundary != null) {
-            for (;;) {
-                String line = in.readLine();
-                if (line == null) {
-                    throw new IOException("Unexpected EOF before first boundary @ " +
-                            in.getLineNumber());
-                }
-                if (line.startsWith("--") && line.length() == subBoundary.length() + 2 &&
-                        line.regionMatches(2, subBoundary, 0, subBoundary.length())) {
-                    break;
-                }
-            }
-
-            mMimeContainers = new ArrayList<>();
-            for (;;) {
-                MIMEContainer container = new MIMEContainer(in, subBoundary);
-                mMimeContainers.add(container);
-                if (container.isLast()) {
-                    break;
-                }
-            }
-        }
-        else {
-            mMimeContainers = null;
-        }
-
-        List<String> encoding = headers.get(Encoding);
-        boolean quoted = false;
-        boolean base64 = false;
-        if (encoding != null) {
-            for (String text : encoding) {
-                if (text.equalsIgnoreCase("quoted-printable")) {
-                    quoted = true;
-                    break;
-                }
-                else if (text.equalsIgnoreCase("base64")) {
-                    base64 = true;
-                    break;
-                }
-            }
-        }
-        mBase64 = base64;
-
-        Log.d(Utils.hs2LogTag(getClass()),
-                String.format("%s MIME container, boundary '%s', type '%s', encoding %s",
-                multiPart ? "multipart" : "plain", boundary, mContentType, encoding));
-
-        AtomicBoolean eof = new AtomicBoolean();
-        mText = recode(getBody(in, boundary, quoted, eof), charset);
-        mLast = eof.get();
-    }
-
-    public List<MIMEContainer> getMimeContainers() {
-        return mMimeContainers;
-    }
-
-    public String getText() {
-        return mText;
-    }
-
-    public boolean isMixed() {
-        return mMixed;
-    }
-
-    public boolean isBase64() {
-        return mBase64;
-    }
-
-    public String getContentType() {
-        return mContentType;
-    }
-
-    private boolean isLast() {
-        return mLast;
-    }
-
-    private void toString(StringBuilder sb, int nesting) {
-        char[] indent = new char[nesting*4];
-        Arrays.fill(indent, ' ');
-        if (mBase64) {
-            sb.append("base64, type ").append(mContentType).append('\n');
-        }
-        else if (mMimeContainers != null) {
-            sb.append(indent).append("multipart/").append((mMixed ? "mixed" : "other" )).append('\n');
-        }
-        else {
-            sb.append(indent).append(
-                    String.format("%s, type %s",
-                            mCharset,
-                            mContentType)
-            ).append('\n');
-        }
-
-        if (mMimeContainers != null) {
-            for (MIMEContainer mimeContainer : mMimeContainers) {
-                mimeContainer.toString(sb, nesting + 1);
-            }
-        }
-        sb.append(indent).append("Text: ");
-        if (mText.length() < 100000) {
-            sb.append("'").append(mText).append("'\n");
-        }
-        else {
-            sb.append(mText.length()).append(" chars\n");
-        }
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        toString(sb, 0);
-        return sb.toString();
-    }
-
-    private static Map<String,List<String>> parseHeader(LineNumberReader in) throws IOException {
-
-        StringBuilder value = null;
-        String header = null;
-
-        Map<String,List<String>> headers = new HashMap<>();
-
-        for (;;) {
-            String line = in.readLine();
-            if ( line == null ) {
-                throw new IOException("Missing body @ " + in.getLineNumber());
-            }
-            else if (line.length() == 0) {
-                break;
-            }
-
-            if (line.charAt(0) <= ' ') {
-                if (value == null) {
-                    throw new IOException("Illegal blank prefix in header line '" + line + "' @ " + in.getLineNumber());
-                }
-                value.append(' ').append(line.trim());
-                continue;
-            }
-
-            int nameEnd = line.indexOf(':');
-            if (nameEnd < 0) {
-                throw new IOException("Bad header line: '" + line + "' @ " + in.getLineNumber());
-            }
-
-            if (header != null) {
-                String[] values = value.toString().split(";");
-                List<String> valueList = new ArrayList<>(values.length);
-                for (String segment : values) {
-                    valueList.add(segment.trim());
-                }
-                headers.put(header, valueList);
-                //System.out.println("Header '" + header + "' = " + valueList);
-            }
-
-            header = line.substring(0, nameEnd);
-            value = new StringBuilder();
-            value.append(line.substring(nameEnd+1).trim());
-        }
-
-        if (header != null) {
-            String[] values = value.toString().split(";");
-            List<String> valueList = new ArrayList<>(values.length);
-            for (String segment : values) {
-                valueList.add(segment.trim());
-            }
-            headers.put(header, valueList);
-            //System.out.println("Header '" + header + "' = " + valueList);
-        }
-
-        return headers;
-    }
-
-    private static String getBody(LineNumberReader in, String boundary, boolean quoted, AtomicBoolean eof)
-            throws IOException {
-
-        StringBuilder text = new StringBuilder();
-        for (;;) {
-            String line = in.readLine();
-            if (line == null) {
-                if (boundary != null) {
-                    throw new IOException("Unexpected EOF file in body @ " + in.getLineNumber());
-                }
-                else {
-                    return text.toString();
-                }
-            }
-            Boolean end = boundaryCheck(line, boundary);
-            if (end != null) {
-                eof.set(end);
-                //System.out.println("Boundary " + boundary + ": " + end);
-                return text.toString();
-            }
-
-            if (quoted) {
-                if (line.endsWith("=")) {
-                    text.append(unescape(line.substring(line.length() - 1), in.getLineNumber()));
-                }
-                else {
-                    text.append(unescape(line, in.getLineNumber()));
-                }
-            }
-            else {
-                text.append(line);
-            }
-        }
-    }
-
-    private static String recode(String s, Charset charset) {
-        if (charset.equals(StandardCharsets.ISO_8859_1) || charset.equals(StandardCharsets.US_ASCII)) {
-            return s;
-        }
-
-        byte[] octets = s.getBytes(StandardCharsets.ISO_8859_1);
-        return new String(octets, charset);
-    }
-
-    private static Boolean boundaryCheck(String line, String boundary) {
-        if (line.startsWith("--") && line.regionMatches(2, boundary, 0, boundary.length())) {
-            if (line.length() == boundary.length() + 2) {
-                return Boolean.FALSE;
-            }
-            else if (line.length() == boundary.length() + 4 && line.endsWith("--") ) {
-                return Boolean.TRUE;
-            }
-        }
-        return null;
-    }
-
-    private static String unescape(String text, int line) throws IOException {
-        StringBuilder sb = new StringBuilder();
-        for (int n = 0; n < text.length(); n++) {
-            char ch = text.charAt(n);
-            if (ch > 127) {
-                throw new IOException("Bad codepoint " + (int)ch + " in quoted printable @ " + line);
-            }
-            if (ch == '=' && n < text.length() - 2) {
-                int h1 = fromStrictHex(text.charAt(n+1));
-                int h2 = fromStrictHex(text.charAt(n+2));
-                if (h1 >= 0 && h2 >= 0) {
-                    sb.append((char)((h1 << 4) | h2));
-                    n += 2;
-                }
-                else {
-                    sb.append(ch);
-                }
-            }
-            else {
-                sb.append(ch);
-            }
-        }
-        return sb.toString();
-    }
-
-    private static int fromStrictHex(char ch) {
-        if (ch >= '0' && ch <= '9') {
-            return ch - '0';
-        }
-        else if (ch >= 'A' && ch <= 'F') {
-            return ch - 'A' + 10;
-        }
-        else {
-            return -1;
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPData.java b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
index 164ea20..6dbbe8a 100644
--- a/service/java/com/android/server/wifi/hotspot2/ANQPData.java
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPData.java
@@ -1,156 +1,79 @@
+/*
+ * 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.hotspot2;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.Clock;
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
+/**
+ * Class for maintaining ANQP elements and managing the lifetime of the elements.
+ */
 public class ANQPData {
     /**
-     * The regular cache time for entries with a non-zero domain id.
+     * Entry lifetime.
      */
-    private static final long ANQP_QUALIFIED_CACHE_TIMEOUT = 3600000L;
-    /**
-     * The cache time for entries with a zero domain id. The zero domain id indicates that ANQP
-     * data from the AP may change at any time, thus a relatively short cache time is given to
-     * such data, but still long enough to avoid excessive querying.
-     */
-    private static final long ANQP_UNQUALIFIED_CACHE_TIMEOUT = 300000L;
-    /**
-     * This is the hold off time for pending queries, i.e. the time during which subsequent queries
-     * are squelched.
-     */
-    private static final long ANQP_HOLDOFF_TIME = 10000L;
+    @VisibleForTesting
+    public static final long DATA_LIFETIME_MILLISECONDS = 3600000L;
 
-    /**
-     * Max value for the retry counter for unanswered queries. This limits the maximum time-out to
-     * ANQP_HOLDOFF_TIME * 2^MAX_RETRY. With current values this results in 640s.
-     */
-    private static final int MAX_RETRY = 6;
-
-    private final NetworkDetail mNetwork;
-    private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
-    private final long mCtime;
-    private final long mExpiry;
-    private final int mRetry;
     private final Clock mClock;
+    private final Map<Constants.ANQPElementType, ANQPElement> mANQPElements;
+    private final long mExpiryTime;
 
-    public ANQPData(Clock clock, NetworkDetail network,
-                    Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
-
+    public ANQPData(Clock clock, Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
         mClock = clock;
-        mNetwork = network;
-        mANQPElements = anqpElements != null ? new HashMap<>(anqpElements) : null;
-        mCtime = mClock.currentTimeMillis();
-        mRetry = 0;
-        if (anqpElements == null) {
-            mExpiry = mCtime + ANQP_HOLDOFF_TIME;
+        mANQPElements = new HashMap<>();
+        if (anqpElements != null) {
+            mANQPElements.putAll(anqpElements);
         }
-        else if (network.getAnqpDomainID() == 0) {
-            mExpiry = mCtime + ANQP_UNQUALIFIED_CACHE_TIMEOUT;
-        }
-        else {
-            mExpiry = mCtime + ANQP_QUALIFIED_CACHE_TIMEOUT;
-        }
+        mExpiryTime = mClock.getElapsedSinceBootMillis() + DATA_LIFETIME_MILLISECONDS;
     }
 
-    public ANQPData(Clock clock, NetworkDetail network, ANQPData existing) {
-        mClock = clock;
-        mNetwork = network;
-        mANQPElements = null;
-        mCtime = mClock.currentTimeMillis();
-        if (existing == null) {
-            mRetry = 0;
-            mExpiry = mCtime + ANQP_HOLDOFF_TIME;
-        }
-        else {
-            mRetry = Math.max(existing.getRetry() + 1, MAX_RETRY);
-            mExpiry = ANQP_HOLDOFF_TIME * (1<<mRetry);
-        }
-    }
-
-    public List<Constants.ANQPElementType> disjoint(List<Constants.ANQPElementType> querySet) {
-        if (mANQPElements == null) {
-            // Ignore the query set for pending responses, it has minimal probability to happen
-            // and a new query will be reissued on the next round anyway.
-            return null;
-        }
-        else {
-            List<Constants.ANQPElementType> additions = new ArrayList<>();
-            for (Constants.ANQPElementType element : querySet) {
-                if (!mANQPElements.containsKey(element)) {
-                    additions.add(element);
-                }
-            }
-            return additions.isEmpty() ? null : additions;
-        }
-    }
-
-    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
+    /**
+     * Return the ANQP elements.
+     *
+     * @return Map of ANQP elements
+     */
+    public Map<Constants.ANQPElementType, ANQPElement> getElements() {
         return Collections.unmodifiableMap(mANQPElements);
     }
 
-    public NetworkDetail getNetwork() {
-        return mNetwork;
-    }
-
-    public boolean expired() {
-        return expired(mClock.currentTimeMillis());
-    }
-
+    /**
+     * Check if this entry is expired at the specified time.
+     *
+     * @param at The time to check for
+     * @return true if it is expired at the given time
+     */
     public boolean expired(long at) {
-        return mExpiry <= at;
-    }
-
-    protected boolean hasData() {
-        return mANQPElements != null;
-    }
-
-    protected void merge(Map<Constants.ANQPElementType, ANQPElement> data) {
-        if (data != null) {
-            mANQPElements.putAll(data);
-        }
-    }
-
-    protected boolean isValid(NetworkDetail nwk) {
-        return mANQPElements != null &&
-                nwk.getAnqpDomainID() == mNetwork.getAnqpDomainID() &&
-                mExpiry > mClock.currentTimeMillis();
-    }
-
-    private int getRetry() {
-        return mRetry;
-    }
-
-    public String toString(boolean brief) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(mNetwork.toKeyString()).append(", domid ").append(mNetwork.getAnqpDomainID());
-        if (mANQPElements == null) {
-            sb.append(", unresolved, ");
-        }
-        else {
-            sb.append(", ").append(mANQPElements.size()).append(" elements, ");
-        }
-        long now = mClock.currentTimeMillis();
-        sb.append(Utils.toHMS(now-mCtime)).append(" old, expires in ").
-                append(Utils.toHMS(mExpiry-now)).append(' ');
-        if (brief) {
-            sb.append(expired(now) ? 'x' : '-');
-            sb.append(mANQPElements == null ? 'u' : '-');
-        }
-        else if (mANQPElements != null) {
-            sb.append(" data=").append(mANQPElements);
-        }
-        return sb.toString();
+        return mExpiryTime <= at;
     }
 
     @Override
     public String toString() {
-        return toString(true);
+        StringBuilder sb = new StringBuilder();
+        sb.append(mANQPElements.size()).append(" elements, ");
+        long now = mClock.getElapsedSinceBootMillis();
+        sb.append(" expires in ").append(Utils.toHMS(mExpiryTime - now)).append(' ');
+        sb.append(expired(now) ? 'x' : '-');
+        return sb.toString();
     }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPMatcher.java b/service/java/com/android/server/wifi/hotspot2/ANQPMatcher.java
new file mode 100644
index 0000000..6495346
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPMatcher.java
@@ -0,0 +1,257 @@
+/*
+ * 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.hotspot2;
+
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.hotspot2.anqp.CellularNetwork;
+import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
+import com.android.server.wifi.hotspot2.anqp.NAIRealmData;
+import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
+import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
+import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
+import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
+import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class for providing matching functions against ANQP elements.
+ */
+public class ANQPMatcher {
+    /**
+     * Match the domain names in the ANQP element against the provider's FQDN and SIM credential.
+     * The Domain Name ANQP element might contain domains for 3GPP network (e.g.
+     * wlan.mnc*.mcc*.3gppnetwork.org), so we should match that against the provider's SIM
+     * credential if one is provided.
+     *
+     * @param element The Domain Name ANQP element
+     * @param fqdn The FQDN to compare against
+     * @param imsiParam The IMSI parameter of the provider
+     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
+     *                    IMSI parameter
+     * @return true if a match is found
+     */
+    public static boolean matchDomainName(DomainNameElement element, String fqdn,
+            IMSIParameter imsiParam, List<String> simImsiList) {
+        if (element == null) {
+            return false;
+        }
+
+        for (String domain : element.getDomains()) {
+            if (DomainMatcher.arg2SubdomainOfArg1(fqdn, domain)) {
+                return true;
+            }
+
+            // Try to retrieve the MCC-MNC string from the domain (for 3GPP network domain) and
+            // match against the provider's SIM credential.
+            if (matchMccMnc(Utils.getMccMnc(Utils.splitDomain(domain)), imsiParam, simImsiList)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Match the roaming consortium OIs in the ANQP element against the roaming consortium OIs
+     * of a provider.
+     *
+     * @param element The Roaming Consortium ANQP element
+     * @param providerOIs The roaming consortium OIs of the provider
+     * @return true if a match is found
+     */
+    public static boolean matchRoamingConsortium(RoamingConsortiumElement element,
+            long[] providerOIs) {
+        if (element == null) {
+            return false;
+        }
+        if (providerOIs == null) {
+            return false;
+        }
+        List<Long> rcOIs = element.getOIs();
+        for (long oi : providerOIs) {
+            if (rcOIs.contains(oi)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Match the NAI realm in the ANQP element against the realm and authentication method of
+     * a provider.
+     *
+     * @param element The NAI Realm ANQP element
+     * @param realm The realm of the provider's credential
+     * @param eapMethodID The EAP Method ID of the provider's credential
+     * @param authParam The authentication parameter of the provider's credential
+     * @return an integer indicating the match status
+     */
+    public static int matchNAIRealm(NAIRealmElement element, String realm, int eapMethodID,
+            AuthParam authParam) {
+        if (element == null || element.getRealmDataList().isEmpty()) {
+            return AuthMatch.INDETERMINATE;
+        }
+
+        int bestMatch = AuthMatch.NONE;
+        for (NAIRealmData realmData : element.getRealmDataList()) {
+            int match = matchNAIRealmData(realmData, realm, eapMethodID, authParam);
+            if (match > bestMatch) {
+                bestMatch = match;
+                if (bestMatch == AuthMatch.EXACT) {
+                    break;
+                }
+            }
+        }
+        return bestMatch;
+    }
+
+    /**
+     * Match the 3GPP Network in the ANQP element against the SIM credential of a provider.
+     *
+     * @param element 3GPP Network ANQP element
+     * @param imsiParam The IMSI parameter of the provider's SIM credential
+     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
+     *                    IMSI parameter
+     * @return true if a matched is found
+     */
+    public static  boolean matchThreeGPPNetwork(ThreeGPPNetworkElement element,
+            IMSIParameter imsiParam, List<String> simImsiList) {
+        if (element == null) {
+            return false;
+        }
+        for (CellularNetwork network : element.getNetworks()) {
+            if (matchCellularNetwork(network, imsiParam, simImsiList)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Match the given NAI Realm data against the realm and authentication method of a provider.
+     *
+     * @param realmData The NAI Realm data
+     * @param realm The realm of the provider's credential
+     * @param eapMethodID The EAP Method ID of the provider's credential
+     * @param authParam The authentication parameter of the provider's credential
+     * @return an integer indicating the match status
+     */
+    private static int matchNAIRealmData(NAIRealmData realmData, String realm, int eapMethodID,
+            AuthParam authParam) {
+        // Check for realm domain name match.
+        int realmMatch = AuthMatch.NONE;
+        for (String realmStr : realmData.getRealms()) {
+            if (DomainMatcher.arg2SubdomainOfArg1(realm, realmStr)) {
+                realmMatch = AuthMatch.REALM;
+                break;
+            }
+        }
+
+        if (realmMatch == AuthMatch.NONE || realmData.getEAPMethods().isEmpty()) {
+            return realmMatch;
+        }
+
+        // Check for EAP method match.
+        int eapMethodMatch = AuthMatch.NONE;
+        for (EAPMethod eapMethod : realmData.getEAPMethods()) {
+            eapMethodMatch = matchEAPMethod(eapMethod, eapMethodID, authParam);
+            if (eapMethodMatch != AuthMatch.NONE) {
+                break;
+            }
+        }
+
+        if (eapMethodMatch == AuthMatch.NONE) {
+            return AuthMatch.NONE;
+        }
+        return realmMatch | eapMethodMatch;
+    }
+
+    /**
+     * Match the given EAPMethod against the authentication method of a provider.
+     *
+     * @param method The EAP Method
+     * @param eapMethodID The EAP Method ID of the provider's credential
+     * @param authParam The authentication parameter of the provider's credential
+     * @return an integer indicating the match status
+     */
+    private static int matchEAPMethod(EAPMethod method, int eapMethodID, AuthParam authParam) {
+        if (method.getEAPMethodID() != eapMethodID) {
+            return AuthMatch.NONE;
+        }
+        // Check for authentication parameter match.
+        if (authParam != null) {
+            Map<Integer, Set<AuthParam>> authParams = method.getAuthParams();
+            Set<AuthParam> paramSet = authParams.get(authParam.getAuthTypeID());
+            if (paramSet == null || !paramSet.contains(authParam)) {
+                return AuthMatch.NONE;
+            }
+            return AuthMatch.METHOD_PARAM;
+        }
+        return AuthMatch.METHOD;
+    }
+
+    /**
+     * Match a cellular network information in the 3GPP Network ANQP element against the SIM
+     * credential of a provider.
+     *
+     * @param network The cellular network that contained list of PLMNs
+     * @param imsiParam IMSI parameter of the provider
+     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
+     *                    IMSI parameter
+     * @return true if a match is found
+     */
+    private static boolean matchCellularNetwork(CellularNetwork network, IMSIParameter imsiParam,
+            List<String> simImsiList) {
+        for (String plmn : network.getPlmns()) {
+            if (matchMccMnc(plmn, imsiParam, simImsiList)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Match a MCC-MNC against the SIM credential of a provider.
+     *
+     * @param mccMnc The string containing MCC-MNC
+     * @param imsiParam The IMSI parameter of the provider
+     * @param simImsiList The list of IMSI from the installed SIM cards that matched provider's
+     *                    IMSI parameter
+     * @return true if a match is found
+     */
+    private static boolean matchMccMnc(String mccMnc, IMSIParameter imsiParam,
+            List<String> simImsiList) {
+        if (imsiParam == null || simImsiList == null) {
+            return false;
+        }
+        // Match against the IMSI parameter in the provider.
+        if (!imsiParam.matchesMccMnc(mccMnc)) {
+            return false;
+        }
+        // Additional check for verifying the match with IMSIs from the SIM cards, since the IMSI
+        // parameter might not contain the full 6-digit MCC MNC (e.g. IMSI parameter is an IMSI
+        // prefix that contained less than 6-digit of numbers "12345*").
+        for (String imsi : simImsiList) {
+            if (imsi.startsWith(mccMnc)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java b/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.java
new file mode 100644
index 0000000..aaaedb3
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPNetworkKey.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.hotspot2;
+
+import android.text.TextUtils;
+
+/**
+ * Unique key for identifying APs that will contain the same ANQP information.
+ *
+ * APs in the same ESS (SSID or HESSID) with the same ANQP domain ID will have the same ANQP
+ * information. Thus, those APs will be keyed by the ESS identifier (SSID or HESSID) and the
+ * ANQP domain ID.
+ *
+ * APs without ANQP domain ID set will assumed to have unique ANQP information. Thus, those
+ * APs will be keyed by SSID and BSSID.
+ */
+public class ANQPNetworkKey {
+    private final String mSSID;
+    private final long mBSSID;
+    private final long mHESSID;
+    private final int mAnqpDomainID;
+
+    public ANQPNetworkKey(String ssid, long bssid, long hessid, int anqpDomainID) {
+        mSSID = ssid;
+        mBSSID = bssid;
+        mHESSID = hessid;
+        mAnqpDomainID = anqpDomainID;
+    }
+
+    /**
+     * Build an ANQP network key suitable for the granularity of the key space as follows:
+     *
+     * HESSID   domainID    Key content       Rationale
+     * -------- ----------- -----------       --------------------
+     * n/a      zero        SSID/BSSID        Domain ID indicates unique AP info
+     * not set  set         SSID/domainID     Standard definition of an ESS
+     * set      set         HESSID/domainID   The ESS is defined by the HESSID
+     *
+     * @param ssid The SSID of the AP
+     * @param bssid The BSSID of the AP
+     * @param hessid The HESSID of the AP
+     * @param anqpDomainId The ANQP Domain ID of the AP
+     * @return {@link ANQPNetworkKey}
+     */
+    public static ANQPNetworkKey buildKey(String ssid, long bssid, long hessid, int anqpDomainId) {
+        if (anqpDomainId == 0) {
+            return new ANQPNetworkKey(ssid, bssid, 0, 0);
+        } else if (hessid != 0L) {
+            return new ANQPNetworkKey(null, 0, hessid, anqpDomainId);
+        }
+        return new ANQPNetworkKey(ssid, 0, 0, anqpDomainId);
+    }
+
+    @Override
+    public int hashCode() {
+        if (mHESSID != 0) {
+            return (int) (((mHESSID >>> 32) * 31 + mHESSID) * 31 + mAnqpDomainID);
+        } else if (mBSSID != 0) {
+            return (int) ((mSSID.hashCode() * 31 + (mBSSID >>> 32)) * 31 + mBSSID);
+        } else {
+            return mSSID.hashCode() * 31 + mAnqpDomainID;
+        }
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        }
+        if (!(thatObject instanceof ANQPNetworkKey)) {
+            return false;
+        }
+        ANQPNetworkKey that = (ANQPNetworkKey) thatObject;
+        return TextUtils.equals(that.mSSID, mSSID)
+                && that.mBSSID == mBSSID
+                && that.mHESSID == mHESSID
+                && that.mAnqpDomainID == mAnqpDomainID;
+    }
+
+    @Override
+    public String toString() {
+        if (mHESSID != 0L) {
+            return Utils.macToString(mHESSID) + ":" + mAnqpDomainID;
+        } else if (mBSSID != 0L) {
+            return Utils.macToString(mBSSID)
+                    + ":<" + Utils.toUnicodeEscapedString(mSSID) + ">";
+        } else {
+            return "<" + Utils.toUnicodeEscapedString(mSSID) + ">:" + mAnqpDomainID;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java b/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java
new file mode 100644
index 0000000..a050b16
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java
@@ -0,0 +1,211 @@
+/*
+ * 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.hotspot2;
+
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for managing sending of ANQP requests.  This manager will ignore ANQP requests for a
+ * period of time (hold off time) to a specified AP if the previous request to that AP goes
+ * unanswered or failed.  The hold off time will increase exponentially until the max is reached.
+ */
+public class ANQPRequestManager {
+    private static final String TAG = "ANQPRequestManager";
+
+    private final PasspointEventHandler mPasspointHandler;
+    private final Clock mClock;
+
+    /**
+     * List of pending ANQP request associated with an AP (BSSID).
+     */
+    private final Map<Long, ANQPNetworkKey> mPendingQueries;
+
+    /**
+     * List of hold off time information associated with APs specified by their BSSID.
+     * Used to determine when an ANQP request can be send to the corresponding AP after the
+     * previous request goes unanswered or failed.
+     */
+    private final Map<Long, HoldOffInfo> mHoldOffInfo;
+
+    /**
+     * Minimum number of milliseconds to wait for before attempting ANQP queries to the same AP
+     * after previous request goes unanswered or failed.
+     */
+    @VisibleForTesting
+    public static final int BASE_HOLDOFF_TIME_MILLISECONDS = 10000;
+
+    /**
+     * Max value for the hold off counter for unanswered/failed queries.  This limits the maximum
+     * hold off time to:
+     * BASE_HOLDOFF_TIME_MILLISECONDS * 2^MAX_HOLDOFF_COUNT
+     * which is 640 seconds.
+     */
+    @VisibleForTesting
+    public static final int MAX_HOLDOFF_COUNT = 6;
+
+    private static final List<Constants.ANQPElementType> R1_ANQP_BASE_SET = Arrays.asList(
+            Constants.ANQPElementType.ANQPVenueName,
+            Constants.ANQPElementType.ANQPIPAddrAvailability,
+            Constants.ANQPElementType.ANQPNAIRealm,
+            Constants.ANQPElementType.ANQP3GPPNetwork,
+            Constants.ANQPElementType.ANQPDomName);
+
+    private static final List<Constants.ANQPElementType> R2_ANQP_BASE_SET = Arrays.asList(
+            Constants.ANQPElementType.HSFriendlyName,
+            Constants.ANQPElementType.HSWANMetrics,
+            Constants.ANQPElementType.HSConnCapability,
+            Constants.ANQPElementType.HSOSUProviders);
+
+    /**
+     * Class to keep track of AP status for ANQP requests.
+     */
+    private class HoldOffInfo {
+        /**
+         * Current hold off count.  Will max out at {@link #MAX_HOLDOFF_COUNT}.
+         */
+        public int holdOffCount;
+        /**
+         * The time stamp in milliseconds when we're allow to send ANQP request to the
+         * corresponding AP.
+         */
+        public long holdOffExpirationTime;
+    }
+
+    public ANQPRequestManager(PasspointEventHandler handler, Clock clock) {
+        mPasspointHandler = handler;
+        mClock = clock;
+        mPendingQueries = new HashMap<>();
+        mHoldOffInfo = new HashMap<>();
+    }
+
+    /**
+     * Request ANQP elements from the specified AP.  This will request the basic Release 1 ANQP
+     * elements {@link #R1_ANQP_BASE_SET}.  Additional elements will be requested based on the
+     * information provided in the Information Element (Roaming Consortium OI count and the
+     * supported Hotspot 2.0 release version).
+     *
+     * @param bssid The BSSID of the AP
+     * @param anqpNetworkKey The unique network key associated with this request
+     * @param rcOIs Flag indicating the inclusion of roaming consortium OIs. When set to true,
+     *              Roaming Consortium ANQP element will be requested
+     * @param hsReleaseR2 Flag indicating the support of Hotspot 2.0 Release 2. When set to true,
+     *              the Release 2 ANQP elements {@link #R2_ANQP_BASE_SET} will be requested
+     * @return true if a request was sent successfully
+     */
+    public boolean requestANQPElements(long bssid, ANQPNetworkKey anqpNetworkKey, boolean rcOIs,
+            boolean hsReleaseR2) {
+        // Check if we are allow to send the request now.
+        if (!canSendRequestNow(bssid)) {
+            return false;
+        }
+
+        // No need to hold off future requests for send failures.
+        if (!mPasspointHandler.requestANQP(bssid, getRequestElementIDs(rcOIs, hsReleaseR2))) {
+            return false;
+        }
+
+        // Update hold off info on when we are allowed to send the next ANQP request to
+        // the given AP.
+        updateHoldOffInfo(bssid);
+
+        mPendingQueries.put(bssid, anqpNetworkKey);
+        return true;
+    }
+
+    /**
+     * Notification of the completion of an ANQP request.
+     *
+     * @param bssid The BSSID of the AP
+     * @param success Flag indicating the result of the query
+     * @return {@link ANQPNetworkKey} associated with the completed request
+     */
+    public ANQPNetworkKey onRequestCompleted(long bssid, boolean success) {
+        if (success) {
+            // Query succeeded.  No need to hold off request to the given AP.
+            mHoldOffInfo.remove(bssid);
+        }
+        return mPendingQueries.remove(bssid);
+    }
+
+    /**
+     * Check if we are allowed to send ANQP request to the specified AP now.
+     *
+     * @param bssid The BSSID of an AP
+     * @return true if we are allowed to send the request now
+     */
+    private boolean canSendRequestNow(long bssid) {
+        long currentTime = mClock.getElapsedSinceBootMillis();
+        HoldOffInfo info = mHoldOffInfo.get(bssid);
+        if (info != null && info.holdOffExpirationTime > currentTime) {
+            Log.d(TAG, "Not allowed to send ANQP request to " + bssid + " for another "
+                    + (info.holdOffExpirationTime - currentTime) / 1000 + " seconds");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Update the ANQP request hold off info associated with the given AP.
+     *
+     * @param bssid The BSSID of an AP
+     */
+    private void updateHoldOffInfo(long bssid) {
+        HoldOffInfo info = mHoldOffInfo.get(bssid);
+        if (info == null) {
+            info = new HoldOffInfo();
+            mHoldOffInfo.put(bssid, info);
+        }
+        info.holdOffExpirationTime = mClock.getElapsedSinceBootMillis()
+                + BASE_HOLDOFF_TIME_MILLISECONDS * (1 << info.holdOffCount);
+        if (info.holdOffCount < MAX_HOLDOFF_COUNT) {
+            info.holdOffCount++;
+        }
+    }
+
+    /**
+     * Get the list of ANQP element IDs to request based on the Hotspot 2.0 release number
+     * and the ANQP OI count indicated in the Information Element.
+     *
+     * @param rcOIs Flag indicating the inclusion of roaming consortium OIs
+     * @param hsReleaseR2 Flag indicating support of Hotspot 2.0 Release 2
+     * @return List of ANQP Element ID
+     */
+    private static List<Constants.ANQPElementType> getRequestElementIDs(boolean rcOIs,
+            boolean hsReleaseR2) {
+        List<Constants.ANQPElementType> requestList = new ArrayList<>();
+        requestList.addAll(R1_ANQP_BASE_SET);
+        if (rcOIs) {
+            requestList.add(Constants.ANQPElementType.ANQPRoamingConsortium);
+        }
+
+        if (hsReleaseR2) {
+            requestList.addAll(R2_ANQP_BASE_SET);
+        }
+        return requestList;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
index a6cd42e..215f6a3 100644
--- a/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
+++ b/service/java/com/android/server/wifi/hotspot2/AnqpCache.java
@@ -1,10 +1,25 @@
+/*
+ * 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.hotspot2;
 
-import android.util.Log;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.Clock;
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -12,194 +27,77 @@
 import java.util.List;
 import java.util.Map;
 
+/**
+ * Cache for storing ANQP data.  This is simply a data cache, all the logic related to
+ * ANQP data query will be handled elsewhere (e.g. the consumer of the cache).
+ */
 public class AnqpCache {
-    private static final boolean DBG = false;
+    @VisibleForTesting
+    public static final long CACHE_SWEEP_INTERVAL_MILLISECONDS = 60000L;
 
-    private static final long CACHE_RECHECK = 60000L;
-    private static final boolean STANDARD_ESS = true;  // Regular AP keying; see CacheKey below.
     private long mLastSweep;
     private Clock mClock;
 
-    private final HashMap<CacheKey, ANQPData> mANQPCache;
+    private final Map<ANQPNetworkKey, ANQPData> mANQPCache;
 
     public AnqpCache(Clock clock) {
         mClock = clock;
         mANQPCache = new HashMap<>();
-        mLastSweep = mClock.currentTimeMillis();
+        mLastSweep = mClock.getElapsedSinceBootMillis();
     }
 
-    private static class CacheKey {
-        private final String mSSID;
-        private final long mBSSID;
-        private final long mHESSID;
-
-        private CacheKey(String ssid, long bssid, long hessid) {
-            mSSID = ssid;
-            mBSSID = bssid;
-            mHESSID = hessid;
-        }
-
-        /**
-         * Build an ANQP cache key suitable for the granularity of the key space as follows:
-         *
-         * HESSID   domainID    standardESS     Key content Rationale
-         * -------- ----------- --------------- ----------- --------------------
-         * n/a      zero        n/a             SSID/BSSID  Domain ID indicates unique AP info
-         * not set  set         false           SSID/BSSID  Strict per AP keying override
-         * not set  set         true            SSID        Standard definition of an ESS
-         * set      set         n/a             HESSID      The ESS is defined by the HESSID
-         *
-         * @param network The network to build the key for.
-         * @param standardESS If this parameter is set the "standard" paradigm for an ESS is used
-         *                    for the cache, i.e. all APs with identical SSID is considered an ESS,
-         *                    otherwise caching is performed per AP.
-         * @return A CacheKey.
-         */
-        private static CacheKey buildKey(NetworkDetail network, boolean standardESS) {
-            String ssid;
-            long bssid;
-            long hessid;
-            if (network.getAnqpDomainID() == 0L || (network.getHESSID() == 0L && !standardESS)) {
-                ssid = network.getSSID();
-                bssid = network.getBSSID();
-                hessid = 0L;
-            }
-            else if (network.getHESSID() != 0L && network.getAnqpDomainID() > 0) {
-                ssid = null;
-                bssid = 0L;
-                hessid = network.getHESSID();
-            }
-            else {
-                ssid = network.getSSID();
-                bssid = 0L;
-                hessid = 0L;
-            }
-
-            return new CacheKey(ssid, bssid, hessid);
-        }
-
-        @Override
-        public int hashCode() {
-            if (mHESSID != 0) {
-                return (int)((mHESSID >>> 32) * 31 + mHESSID);
-            }
-            else if (mBSSID != 0) {
-                return (int)((mSSID.hashCode() * 31 + (mBSSID >>> 32)) * 31 + mBSSID);
-            }
-            else {
-                return mSSID.hashCode();
-            }
-        }
-
-        @Override
-        public boolean equals(Object thatObject) {
-            if (thatObject == this) {
-                return true;
-            }
-            else if (thatObject == null || thatObject.getClass() != CacheKey.class) {
-                return false;
-            }
-            CacheKey that = (CacheKey) thatObject;
-            return Utils.compare(that.mSSID, mSSID) == 0 &&
-                    that.mBSSID == mBSSID &&
-                    that.mHESSID == mHESSID;
-        }
-
-        @Override
-        public String toString() {
-            if (mHESSID != 0L) {
-                return "HESSID:" + NetworkDetail.toMACString(mHESSID);
-            }
-            else if (mBSSID != 0L) {
-                return NetworkDetail.toMACString(mBSSID) +
-                        ":<" + Utils.toUnicodeEscapedString(mSSID) + ">";
-            }
-            else {
-                return '<' + Utils.toUnicodeEscapedString(mSSID) + '>';
-            }
-        }
+    /**
+     * Add an ANQP entry associated with the given key.
+     *
+     * @param key The key that's associated with the entry
+     * @param anqpElements The ANQP elements from the AP
+     */
+    public void addEntry(ANQPNetworkKey key,
+            Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+        ANQPData data = new ANQPData(mClock, anqpElements);
+        mANQPCache.put(key, data);
     }
 
-    public List<Constants.ANQPElementType> initiate(NetworkDetail network,
-                                                    List<Constants.ANQPElementType> querySet) {
-        CacheKey key = CacheKey.buildKey(network, STANDARD_ESS);
-
-        synchronized (mANQPCache) {
-            ANQPData data = mANQPCache.get(key);
-            if (data == null || data.expired()) {
-                mANQPCache.put(key, new ANQPData(mClock, network, data));
-                return querySet;
-            }
-            else {
-                List<Constants.ANQPElementType> newList = data.disjoint(querySet);
-                Log.d(Utils.hs2LogTag(getClass()),
-                        String.format("New ANQP elements for BSSID %012x: %s",
-                                network.getBSSID(), newList));
-                return newList;
-            }
-        }
+    /**
+     * Get the ANQP data associated with the given AP.
+     *
+     * @param key The key that's associated with the entry
+     * @return {@link ANQPData}
+     */
+    public ANQPData getEntry(ANQPNetworkKey key) {
+        return mANQPCache.get(key);
     }
 
-    public void update(NetworkDetail network,
-                       Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
-
-        CacheKey key = CacheKey.buildKey(network, STANDARD_ESS);
-
-        // Networks with a 0 ANQP Domain ID are still cached, but with a very short expiry, just
-        // long enough to prevent excessive re-querying.
-        synchronized (mANQPCache) {
-            ANQPData data = mANQPCache.get(key);
-            if (data != null && data.hasData()) {
-                data.merge(anqpElements);
-            }
-            else {
-                data = new ANQPData(mClock, network, anqpElements);
-                mANQPCache.put(key, data);
-            }
-        }
-    }
-
-    public ANQPData getEntry(NetworkDetail network) {
-        ANQPData data;
-
-        CacheKey key = CacheKey.buildKey(network, STANDARD_ESS);
-        synchronized (mANQPCache) {
-            data = mANQPCache.get(key);
+    /**
+     * Go through the cache to remove any expired entries.
+     */
+    public void sweep() {
+        long now = mClock.getElapsedSinceBootMillis();
+        // Check if it is time to perform the sweep.
+        if (now < mLastSweep + CACHE_SWEEP_INTERVAL_MILLISECONDS) {
+            return;
         }
 
-        return data != null && data.isValid(network) ? data : null;
-    }
-
-    public void clear(boolean all, boolean debug) {
-        if (DBG) Log.d(Utils.hs2LogTag(getClass()), "Clearing ANQP cache: all: " + all);
-        long now = mClock.currentTimeMillis();
-        synchronized (mANQPCache) {
-            if (all) {
-                mANQPCache.clear();
-                mLastSweep = now;
-            }
-            else if (now > mLastSweep + CACHE_RECHECK) {
-                List<CacheKey> retirees = new ArrayList<>();
-                for (Map.Entry<CacheKey, ANQPData> entry : mANQPCache.entrySet()) {
-                    if (entry.getValue().expired(now)) {
-                        retirees.add(entry.getKey());
-                    }
-                }
-                for (CacheKey key : retirees) {
-                    mANQPCache.remove(key);
-                    if (debug) {
-                        Log.d(Utils.hs2LogTag(getClass()), "Retired " + key);
-                    }
-                }
-                mLastSweep = now;
+        // Get all expired keys.
+        List<ANQPNetworkKey> expiredKeys = new ArrayList<>();
+        for (Map.Entry<ANQPNetworkKey, ANQPData> entry : mANQPCache.entrySet()) {
+            if (entry.getValue().expired(now)) {
+                expiredKeys.add(entry.getKey());
             }
         }
+
+        // Remove all expired entries.
+        for (ANQPNetworkKey key : expiredKeys) {
+            mANQPCache.remove(key);
+        }
+        mLastSweep = now;
     }
 
     public void dump(PrintWriter out) {
-        out.println("Last sweep " + Utils.toHMS(mClock.currentTimeMillis() - mLastSweep) + " ago.");
-        for (ANQPData anqpData : mANQPCache.values()) {
-            out.println(anqpData.toString(false));
+        out.println("Last sweep " + Utils.toHMS(mClock.getElapsedSinceBootMillis() - mLastSweep)
+                + " ago.");
+        for (Map.Entry<ANQPNetworkKey, ANQPData> entry : mANQPCache.entrySet()) {
+            out.println(entry.getKey() + ": " + entry.getValue());
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/AnqpEvent.java b/service/java/com/android/server/wifi/hotspot2/AnqpEvent.java
new file mode 100644
index 0000000..0fe8a3a
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/AnqpEvent.java
@@ -0,0 +1,75 @@
+/*
+ * 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.hotspot2;
+
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class carries the response of an ANQP request.
+ */
+public class AnqpEvent {
+    private static final String TAG = "AnqpEvent";
+    private static final Map<String, Constants.ANQPElementType> sWpsNames = new HashMap<>();
+
+    static {
+        sWpsNames.put("anqp_venue_name", Constants.ANQPElementType.ANQPVenueName);
+        sWpsNames.put("anqp_roaming_consortium", Constants.ANQPElementType.ANQPRoamingConsortium);
+        sWpsNames.put("anqp_ip_addr_type_availability",
+                Constants.ANQPElementType.ANQPIPAddrAvailability);
+        sWpsNames.put("anqp_nai_realm", Constants.ANQPElementType.ANQPNAIRealm);
+        sWpsNames.put("anqp_3gpp", Constants.ANQPElementType.ANQP3GPPNetwork);
+        sWpsNames.put("anqp_domain_name", Constants.ANQPElementType.ANQPDomName);
+        sWpsNames.put("hs20_operator_friendly_name", Constants.ANQPElementType.HSFriendlyName);
+        sWpsNames.put("hs20_wan_metrics", Constants.ANQPElementType.HSWANMetrics);
+        sWpsNames.put("hs20_connection_capability", Constants.ANQPElementType.HSConnCapability);
+        sWpsNames.put("hs20_osu_providers_list", Constants.ANQPElementType.HSOSUProviders);
+    }
+
+    /**
+     * Bssid of the access point.
+     */
+    private final long mBssid;
+
+    /**
+     * Map of ANQP element type to the data retrieved from the access point.
+     */
+    private final Map<Constants.ANQPElementType, ANQPElement> mElements;
+
+    public AnqpEvent(long bssid, Map<Constants.ANQPElementType, ANQPElement> elements) {
+        mBssid = bssid;
+        mElements = elements;
+    }
+
+    /**
+     * Get the bssid of the access point from which this ANQP result was created.
+     */
+    public long getBssid() {
+        return mBssid;
+    }
+
+    /**
+     * Get the map of ANQP elements retrieved from the access point.
+     */
+    public Map<Constants.ANQPElementType, ANQPElement> getElements() {
+        return mElements;
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/AuthMatch.java b/service/java/com/android/server/wifi/hotspot2/AuthMatch.java
index cd988b5..3abf35f 100644
--- a/service/java/com/android/server/wifi/hotspot2/AuthMatch.java
+++ b/service/java/com/android/server/wifi/hotspot2/AuthMatch.java
@@ -1,3 +1,18 @@
+/*
+ * 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.hotspot2;
 
 /**
@@ -9,13 +24,13 @@
  * must be maintained accordingly.
  */
 public abstract class AuthMatch {
-    public static final int None = -1;
-    public static final int Indeterminate = 0;
-    public static final int Realm = 0x04;
-    public static final int Method = 0x02;
-    public static final int Param = 0x01;
-    public static final int MethodParam = Method | Param;
-    public static final int Exact = Realm | Method | Param;
+    public static final int NONE = -1;
+    public static final int INDETERMINATE = 0;
+    public static final int REALM = 0x04;
+    public static final int METHOD = 0x02;
+    public static final int PARAM = 0x01;
+    public static final int METHOD_PARAM = METHOD | PARAM;
+    public static final int EXACT = REALM | METHOD | PARAM;
 
     public static String toString(int match) {
         if (match < 0) {
@@ -26,13 +41,13 @@
         }
 
         StringBuilder sb = new StringBuilder();
-        if ((match & Realm) != 0) {
+        if ((match & REALM) != 0) {
             sb.append("Realm");
         }
-        if ((match & Method) != 0) {
+        if ((match & METHOD) != 0) {
             sb.append("Method");
         }
-        if ((match & Param) != 0) {
+        if ((match & PARAM) != 0) {
             sb.append("Param");
         }
         return sb.toString();
diff --git a/service/java/com/android/server/wifi/hotspot2/CertificateVerifier.java b/service/java/com/android/server/wifi/hotspot2/CertificateVerifier.java
new file mode 100644
index 0000000..004a32f
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/CertificateVerifier.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertificateFactory;
+import java.security.cert.PKIXParameters;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+/**
+ * Utility class used for verifying certificates against the pre-loaded public CAs in the
+ * system key store.  This class is created to allow the certificate verification to be mocked in
+ * unit tests.
+ */
+public class CertificateVerifier {
+
+    /**
+     * Verify that the given certificate is trusted by one of the pre-loaded public CAs in the
+     * system key store.
+     *
+     * @param caCert The CA Certificate to verify
+     * @throws GeneralSecurityException
+     * @throws IOException
+     */
+    public void verifyCaCert(X509Certificate caCert)
+            throws GeneralSecurityException, IOException {
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        CertPathValidator validator =
+                CertPathValidator.getInstance(CertPathValidator.getDefaultType());
+        CertPath path = factory.generateCertPath(
+                Arrays.asList(caCert));
+        KeyStore ks = KeyStore.getInstance("AndroidCAStore");
+        ks.load(null, null);
+        PKIXParameters params = new PKIXParameters(ks);
+        params.setRevocationEnabled(false);
+        validator.validate(path, params);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/DomainMatcher.java b/service/java/com/android/server/wifi/hotspot2/DomainMatcher.java
new file mode 100644
index 0000000..ce60c55
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/DomainMatcher.java
@@ -0,0 +1,215 @@
+/*
+ * 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.hotspot2;
+
+import android.text.TextUtils;
+
+import com.android.server.wifi.hotspot2.Utils;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class for matching domain names.
+ */
+public class DomainMatcher {
+    public static final int MATCH_NONE = 0;
+    public static final int MATCH_PRIMARY = 1;
+    public static final int MATCH_SECONDARY = 2;
+
+    /**
+     * The root of the Label tree.
+     */
+    private final Label mRoot;
+
+    /**
+     * Label tree representation for the domain name.  Labels are delimited by "." in the domain
+     * name.
+     *
+     * For example, the tree representation of "android.google.com" as a primary domain:
+     * [com, None] -> [google, None] -> [android, Primary]
+     *
+     */
+    private static class Label {
+        private final Map<String, Label> mSubDomains;
+        private int mMatch;
+
+        Label(int match) {
+            mMatch = match;
+            mSubDomains = new HashMap<String, Label>();
+        }
+
+        /**
+         * Add sub-domains to this label.
+         *
+         * @param labels The iterator of domain label strings
+         * @param match The match status of the domain
+         */
+        public void addDomain(Iterator<String> labels, int match) {
+            String labelName = labels.next();
+            // Create the Label object if it doesn't exist yet.
+            Label subLabel = mSubDomains.get(labelName);
+            if (subLabel == null) {
+                subLabel = new Label(MATCH_NONE);
+                mSubDomains.put(labelName, subLabel);
+            }
+
+            if (labels.hasNext()) {
+                // Adding sub-domain.
+                subLabel.addDomain(labels, match);
+            } else {
+                // End of the domain, update the match status.
+                subLabel.mMatch = match;
+            }
+        }
+
+        /**
+         * Return the Label for the give label string.
+         * @param labelString The label string to look for
+         * @return {@link Label}
+         */
+        public Label getSubLabel(String labelString) {
+            return mSubDomains.get(labelString);
+        }
+
+        /**
+         * Return the match status
+         *
+         * @return The match status
+         */
+        public int getMatch() {
+            return mMatch;
+        }
+
+        private void toString(StringBuilder sb) {
+            if (mSubDomains != null) {
+                sb.append(".{");
+                for (Map.Entry<String, Label> entry : mSubDomains.entrySet()) {
+                    sb.append(entry.getKey());
+                    entry.getValue().toString(sb);
+                }
+                sb.append('}');
+            } else {
+                sb.append('=').append(mMatch);
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            toString(sb);
+            return sb.toString();
+        }
+    }
+
+    public DomainMatcher(String primaryDomain, List<String> secondaryDomains) {
+        // Create the root label.
+        mRoot = new Label(MATCH_NONE);
+
+        // Add secondary domains.
+        if (secondaryDomains != null) {
+            for (String domain : secondaryDomains) {
+                if (!TextUtils.isEmpty(domain)) {
+                    List<String> secondaryLabel = Utils.splitDomain(domain);
+                    mRoot.addDomain(secondaryLabel.iterator(), MATCH_SECONDARY);
+                }
+            }
+        }
+
+        // Add primary domain, primary overwrites secondary.
+        if (!TextUtils.isEmpty(primaryDomain)) {
+            List<String> primaryLabel = Utils.splitDomain(primaryDomain);
+            mRoot.addDomain(primaryLabel.iterator(), MATCH_PRIMARY);
+        }
+    }
+
+    /**
+     * Check if domain is either the same or a sub-domain of any of the domains in the
+     * domain tree in this matcher, i.e. all or a sub-set of the labels in domain matches
+     * a path in the tree.
+     *
+     * This will have precedence for matching primary domain over secondary domain if both
+     * are found.
+     *
+     * For example, with primary domain set to "test.google.com" and secondary domain set to
+     * "google.com":
+     * "test2.test.google.com" -> Match.Primary
+     * "test1.google.com" -> Match.Secondary
+     *
+     * @param domainName Domain name to be checked.
+     * @return The match status
+     */
+    public int isSubDomain(String domainName) {
+        if (TextUtils.isEmpty(domainName)) {
+            return MATCH_NONE;
+        }
+        List<String> domainLabels = Utils.splitDomain(domainName);
+
+        Label label = mRoot;
+        int match = MATCH_NONE;
+        for (String labelString : domainLabels) {
+            label = label.getSubLabel(labelString);
+            if (label == null) {
+                break;
+            } else if (label.getMatch() != MATCH_NONE) {
+                match = label.getMatch();
+                if (match == MATCH_PRIMARY) {
+                    break;
+                }
+            }
+        }
+        return match;
+    }
+
+    /**
+     * Check if domain2 is a sub-domain of domain1.
+     *
+     * @param domain1 The string of the first domain
+     * @param domain2 The string of the second domain
+     * @return true if the second domain is the sub-domain of the first
+     */
+    public static boolean arg2SubdomainOfArg1(String domain1, String domain2) {
+        if (TextUtils.isEmpty(domain1) || TextUtils.isEmpty(domain2)) {
+            return false;
+        }
+
+        List<String> labels1 = Utils.splitDomain(domain1);
+        List<String> labels2 = Utils.splitDomain(domain2);
+
+        // domain2 must be the same or longer than domain1 in order to be a sub-domain.
+        if (labels2.size() < labels1.size()) {
+            return false;
+        }
+
+        Iterator<String> l1 = labels1.iterator();
+        Iterator<String> l2 = labels2.iterator();
+
+        while(l1.hasNext()) {
+            if (!TextUtils.equals(l1.next(), l2.next())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "Domain matcher " + mRoot;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/IconEvent.java b/service/java/com/android/server/wifi/hotspot2/IconEvent.java
index 69a3b27..406c03c 100644
--- a/service/java/com/android/server/wifi/hotspot2/IconEvent.java
+++ b/service/java/com/android/server/wifi/hotspot2/IconEvent.java
@@ -4,11 +4,13 @@
     private final long mBSSID;
     private final String mFileName;
     private final int mSize;
+    private final byte[] mData;
 
-    public IconEvent(long bssid, String fileName, int size) {
+    public IconEvent(long bssid, String fileName, int size, byte[] data) {
         mBSSID = bssid;
         mFileName = fileName;
         mSize = size;
+        mData = data;
     }
 
     public long getBSSID() {
@@ -23,6 +25,10 @@
         return mSize;
     }
 
+    public byte[] getData() {
+        return mData;
+    }
+
     @Override
     public String toString() {
         return "IconEvent: " +
diff --git a/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfig.java b/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfig.java
new file mode 100644
index 0000000..5f455f5
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfig.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import android.text.TextUtils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Class representing the relevant configurations in the legacy Passpoint (N and older)
+ * configuration file (/data/misc/wifi/PerProviderSubscription.conf).  Most of the configurations
+ * (e.g. user credential) are saved elsewhere, the relevant configurations in this file are:
+ * - FQDN
+ * - Friendly Name
+ * - Roaming Consortium
+ * - Realm
+ * - IMSI (for SIM credential)
+ */
+public class LegacyPasspointConfig {
+    public String mFqdn;
+    public String mFriendlyName;
+    public long[] mRoamingConsortiumOis;
+    public String mRealm;
+    public String mImsi;
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof LegacyPasspointConfig)) {
+            return false;
+        }
+        LegacyPasspointConfig that = (LegacyPasspointConfig) thatObject;
+        return TextUtils.equals(mFqdn, that.mFqdn)
+                && TextUtils.equals(mFriendlyName, that.mFriendlyName)
+                && Arrays.equals(mRoamingConsortiumOis, that.mRoamingConsortiumOis)
+                && TextUtils.equals(mRealm, that.mRealm)
+                && TextUtils.equals(mImsi, that.mImsi);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFqdn, mFriendlyName, mRoamingConsortiumOis, mRealm, mImsi);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfigParser.java b/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfigParser.java
new file mode 100644
index 0000000..31795f1
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/LegacyPasspointConfigParser.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class for parsing legacy (N and older) Passpoint configuration file content
+ * (/data/misc/wifi/PerProviderSubscription.conf).  In N and older, only Release 1 is supported.
+ *
+ * This class only retrieve the relevant Release 1 configuration fields that are not backed
+ * elsewhere.  Below are relevant fields:
+ * - FQDN (used for linking with configuration data stored elsewhere)
+ * - Friendly Name
+ * - Roaming Consortium
+ * - Realm
+ * - IMSI (for SIM credential)
+ *
+ * Below is an example content of a Passpoint configuration file:
+ *
+ * tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)
+ * 8:MgmtTree+
+ * 17:PerProviderSubscription+
+ * 4:r1i1+
+ * 6:HomeSP+
+ * c:FriendlyName=d:Test Provider
+ * 4:FQDN=8:test.net
+ * 13:RoamingConsortiumOI=9:1234,5678
+ * .
+ * a:Credential+
+ * 10:UsernamePassword+
+ * 8:Username=4:user
+ * 8:Password=4:pass
+ *
+ * 9:EAPMethod+
+ * 7:EAPType=2:21
+ * b:InnerMethod=3:PAP
+ * .
+ * .
+ * 5:Realm=a:boingo.com
+ * .
+ * .
+ * .
+ * .
+ *
+ * Each string is prefixed with a "|StringBytesInHex|:".
+ * '+' indicates start of a new internal node.
+ * '.' indicates end of the current internal node.
+ * '=' indicates "value" of a leaf node.
+ *
+ */
+public class LegacyPasspointConfigParser {
+    private static final String TAG = "LegacyPasspointConfigParser";
+
+    private static final String TAG_MANAGEMENT_TREE = "MgmtTree";
+    private static final String TAG_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
+    private static final String TAG_HOMESP = "HomeSP";
+    private static final String TAG_FQDN = "FQDN";
+    private static final String TAG_FRIENDLY_NAME = "FriendlyName";
+    private static final String TAG_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
+    private static final String TAG_CREDENTIAL = "Credential";
+    private static final String TAG_REALM = "Realm";
+    private static final String TAG_SIM = "SIM";
+    private static final String TAG_IMSI = "IMSI";
+
+    private static final String LONG_ARRAY_SEPARATOR = ",";
+    private static final String END_OF_INTERNAL_NODE_INDICATOR = ".";
+    private static final char START_OF_INTERNAL_NODE_INDICATOR = '+';
+    private static final char STRING_PREFIX_INDICATOR = ':';
+    private static final char STRING_VALUE_INDICATOR = '=';
+
+    /**
+     * An abstraction for a node within a tree.  A node can be an internal node (contained
+     * children nodes) or a leaf node (contained a String value).
+     */
+    private abstract static class Node {
+        private final String mName;
+        Node(String name) {
+            mName = name;
+        }
+
+        /**
+         * @return the name of the node
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Applies for internal node only.
+         *
+         * @return the list of children nodes.
+         */
+        public abstract List<Node> getChildren();
+
+        /**
+         * Applies for leaf node only.
+         *
+         * @return the string value of the node
+         */
+        public abstract String getValue();
+    }
+
+    /**
+     * Class representing an internal node of a tree.  It contained a list of child nodes.
+     */
+    private static class InternalNode extends Node {
+        private final List<Node> mChildren;
+        InternalNode(String name, List<Node> children) {
+            super(name);
+            mChildren = children;
+        }
+
+        @Override
+        public List<Node> getChildren() {
+            return mChildren;
+        }
+
+        @Override
+        public String getValue() {
+            return null;
+        }
+    }
+
+    /**
+     * Class representing a leaf node of a tree.  It contained a String type value.
+     */
+    private static class LeafNode extends Node {
+        private final String mValue;
+        LeafNode(String name, String value) {
+            super(name);
+            mValue = value;
+        }
+
+        @Override
+        public List<Node> getChildren() {
+            return null;
+        }
+
+        @Override
+        public String getValue() {
+            return mValue;
+        }
+    }
+
+    public LegacyPasspointConfigParser() {}
+
+    /**
+     * Parse the legacy Passpoint configuration file content, only retrieve the relevant
+     * configurations that are not saved elsewhere.
+     *
+     * For both N and M, only Release 1 is supported. Most of the configurations are saved
+     * elsewhere as part of the {@link android.net.wifi.WifiConfiguration} data.
+     * The configurations needed from the legacy Passpoint configuration file are:
+     *
+     * - FQDN - needed to be able to link to the associated {@link WifiConfiguration} data
+     * - Friendly Name
+     * - Roaming Consortium OIs
+     * - Realm
+     * - IMSI (for SIM credential)
+     *
+     * Make this function non-static so that it can be mocked during unit test.
+     *
+     * @param fileName The file name of the configuration file
+     * @return Map of FQDN to {@link LegacyPasspointConfig}
+     * @throws IOException
+     */
+    public Map<String, LegacyPasspointConfig> parseConfig(String fileName)
+            throws IOException {
+        Map<String, LegacyPasspointConfig> configs = new HashMap<>();
+        BufferedReader in = new BufferedReader(new FileReader(fileName));
+        in.readLine();      // Ignore the first line which contained the header.
+
+        // Convert the configuration data to a management tree represented by a root {@link Node}.
+        Node root = buildNode(in);
+
+        if (root == null || root.getChildren() == null) {
+            Log.d(TAG, "Empty configuration data");
+            return configs;
+        }
+
+        // Verify root node name.
+        if (!TextUtils.equals(TAG_MANAGEMENT_TREE, root.getName())) {
+            throw new IOException("Unexpected root node: " + root.getName());
+        }
+
+        // Process and retrieve the configuration from each PPS (PerProviderSubscription) node.
+        List<Node> ppsNodes = root.getChildren();
+        for (Node ppsNode : ppsNodes) {
+            LegacyPasspointConfig config = processPpsNode(ppsNode);
+            configs.put(config.mFqdn, config);
+        }
+        return configs;
+    }
+
+    /**
+     * Build a {@link Node} from the current line in the buffer.  A node can be an internal
+     * node (ends with '+') or a leaf node.
+     *
+     * @param in Input buffer to read data from
+     * @return {@link Node} representing the current line
+     * @throws IOException
+     */
+    private static Node buildNode(BufferedReader in) throws IOException {
+        // Read until non-empty line.
+        String currentLine = null;
+        while ((currentLine = in.readLine()) != null) {
+            if (!currentLine.isEmpty()) {
+                break;
+            }
+        }
+
+        // Return null if EOF is reached.
+        if (currentLine == null) {
+            return null;
+        }
+
+        // Remove the leading and the trailing whitespaces.
+        currentLine = currentLine.trim();
+
+        // Check for the internal node terminator.
+        if (TextUtils.equals(END_OF_INTERNAL_NODE_INDICATOR, currentLine)) {
+            return null;
+        }
+
+        // Parse the name-value of the current line.  The value will be null if the current line
+        // is not a leaf node (e.g. line ends with a '+').
+        // Each line is encoded in UTF-8.
+        Pair<String, String> nameValuePair =
+                parseLine(currentLine.getBytes(StandardCharsets.UTF_8));
+        if (nameValuePair.second != null) {
+            return new LeafNode(nameValuePair.first, nameValuePair.second);
+        }
+
+        // Parse the children contained under this internal node.
+        List<Node> children = new ArrayList<>();
+        Node child = null;
+        while ((child = buildNode(in)) != null) {
+            children.add(child);
+        }
+        return new InternalNode(nameValuePair.first, children);
+    }
+
+    /**
+     * Process a PPS (PerProviderSubscription) node to retrieve Passpoint configuration data.
+     *
+     * @param ppsNode The PPS node to process
+     * @return {@link LegacyPasspointConfig}
+     * @throws IOException
+     */
+    private static LegacyPasspointConfig processPpsNode(Node ppsNode) throws IOException {
+        if (ppsNode.getChildren() == null || ppsNode.getChildren().size() != 1) {
+            throw new IOException("PerProviderSubscription node should contain "
+                    + "one instance node");
+        }
+
+        if (!TextUtils.equals(TAG_PER_PROVIDER_SUBSCRIPTION, ppsNode.getName())) {
+            throw new IOException("Unexpected name for PPS node: " + ppsNode.getName());
+        }
+
+        // Retrieve the PPS instance node.
+        Node instanceNode = ppsNode.getChildren().get(0);
+        if (instanceNode.getChildren() == null) {
+            throw new IOException("PPS instance node doesn't contained any children");
+        }
+
+        // Process and retrieve the relevant configurations under the PPS instance node.
+        LegacyPasspointConfig config = new LegacyPasspointConfig();
+        for (Node node : instanceNode.getChildren()) {
+            switch (node.getName()) {
+                case TAG_HOMESP:
+                    processHomeSPNode(node, config);
+                    break;
+                case TAG_CREDENTIAL:
+                    processCredentialNode(node, config);
+                    break;
+                default:
+                    Log.d(TAG, "Ignore uninterested field under PPS instance: " + node.getName());
+                    break;
+            }
+        }
+        if (config.mFqdn == null) {
+            throw new IOException("PPS instance missing FQDN");
+        }
+        return config;
+    }
+
+    /**
+     * Process a HomeSP node to retrieve configuration data into the given |config|.
+     *
+     * @param homeSpNode The HomeSP node to process
+     * @param config The config object to fill in the data
+     * @throws IOException
+     */
+    private static void processHomeSPNode(Node homeSpNode, LegacyPasspointConfig config)
+            throws IOException {
+        if (homeSpNode.getChildren() == null) {
+            throw new IOException("HomeSP node should contain at least one child node");
+        }
+
+        for (Node node : homeSpNode.getChildren()) {
+            switch (node.getName()) {
+                case TAG_FQDN:
+                    config.mFqdn = getValue(node);
+                    break;
+                case TAG_FRIENDLY_NAME:
+                    config.mFriendlyName = getValue(node);
+                    break;
+                case TAG_ROAMING_CONSORTIUM_OI:
+                    config.mRoamingConsortiumOis = parseLongArray(getValue(node));
+                    break;
+                default:
+                    Log.d(TAG, "Ignore uninterested field under HomeSP: " + node.getName());
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Process a Credential node to retrieve configuration data into the given |config|.
+     *
+     * @param credentialNode The Credential node to process
+     * @param config The config object to fill in the data
+     * @throws IOException
+     */
+    private static void processCredentialNode(Node credentialNode,
+            LegacyPasspointConfig config)
+            throws IOException {
+        if (credentialNode.getChildren() == null) {
+            throw new IOException("Credential node should contain at least one child node");
+        }
+
+        for (Node node : credentialNode.getChildren()) {
+            switch (node.getName()) {
+                case TAG_REALM:
+                    config.mRealm = getValue(node);
+                    break;
+                case TAG_SIM:
+                    processSimNode(node, config);
+                    break;
+                default:
+                    Log.d(TAG, "Ignore uninterested field under Credential: " + node.getName());
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Process a SIM node to retrieve configuration data into the given |config|.
+     *
+     * @param simNode The SIM node to process
+     * @param config The config object to fill in the data
+     * @throws IOException
+     */
+    private static void processSimNode(Node simNode, LegacyPasspointConfig config)
+            throws IOException {
+        if (simNode.getChildren() == null) {
+            throw new IOException("SIM node should contain at least one child node");
+        }
+
+        for (Node node : simNode.getChildren()) {
+            switch (node.getName()) {
+                case TAG_IMSI:
+                    config.mImsi = getValue(node);
+                    break;
+                default:
+                    Log.d(TAG, "Ignore uninterested field under SIM: " + node.getName());
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Parse the given line in the legacy Passpoint configuration file.
+     * A line can be in the following formats:
+     * 2:ab+         // internal node
+     * 2:ab=2:bc     // leaf node
+     * .             // end of internal node
+     *
+     * @param line The line to parse
+     * @return name-value pair, a value of null indicates internal node
+     * @throws IOException
+     */
+    private static Pair<String, String> parseLine(byte[] lineBytes) throws IOException {
+        Pair<String, Integer> nameIndexPair = parseString(lineBytes, 0);
+        int currentIndex = nameIndexPair.second;
+        try {
+            if (lineBytes[currentIndex] == START_OF_INTERNAL_NODE_INDICATOR) {
+                return Pair.create(nameIndexPair.first, null);
+            }
+
+            if (lineBytes[currentIndex] != STRING_VALUE_INDICATOR) {
+                throw new IOException("Invalid line - missing both node and value indicator: "
+                        + new String(lineBytes, StandardCharsets.UTF_8));
+            }
+        } catch (IndexOutOfBoundsException e) {
+            throw new IOException("Invalid line - " + e.getMessage() + ": "
+                    + new String(lineBytes, StandardCharsets.UTF_8));
+        }
+        Pair<String, Integer> valueIndexPair = parseString(lineBytes, currentIndex + 1);
+        return Pair.create(nameIndexPair.first, valueIndexPair.first);
+    }
+
+    /**
+     * Parse a string value in the given line from the given start index.
+     * A string value is in the following format:
+     * |HexByteLength|:|String|
+     *
+     * The length value indicates the number of UTF-8 bytes in hex for the given string.
+     *
+     * For example: 3:abc
+     *
+     * @param lineBytes The UTF-8 bytes of the line to parse
+     * @param startIndex The start index from the given line to parse from
+     * @return Pair of a string value and an index pointed to character after the string value
+     * @throws IOException
+     */
+    private static Pair<String, Integer> parseString(byte[] lineBytes, int startIndex)
+            throws IOException {
+        // Locate the index that separate length and the string value.
+        int prefixIndex = -1;
+        for (int i = startIndex; i < lineBytes.length; i++) {
+            if (lineBytes[i] == STRING_PREFIX_INDICATOR) {
+                prefixIndex = i;
+                break;
+            }
+        }
+        if (prefixIndex == -1) {
+            throw new IOException("Invalid line - missing string prefix: "
+                    + new String(lineBytes, StandardCharsets.UTF_8));
+        }
+
+        try {
+            String lengthStr = new String(lineBytes, startIndex, prefixIndex - startIndex,
+                    StandardCharsets.UTF_8);
+            int length = Integer.parseInt(lengthStr, 16);
+            int strStartIndex = prefixIndex + 1;
+            // The length might account for bytes for the whitespaces, since the whitespaces are
+            // already trimmed, ignore them.
+            if ((strStartIndex + length) > lineBytes.length) {
+                length = lineBytes.length - strStartIndex;
+            }
+            return Pair.create(
+                    new String(lineBytes, strStartIndex, length, StandardCharsets.UTF_8),
+                    strStartIndex + length);
+        } catch (NumberFormatException | IndexOutOfBoundsException e) {
+            throw new IOException("Invalid line - " + e.getMessage() + ": "
+                    + new String(lineBytes, StandardCharsets.UTF_8));
+        }
+    }
+
+    /**
+     * Parse a long array from the given string.
+     *
+     * @param str The string to parse
+     * @return long[]
+     * @throws IOException
+     */
+    private static long[] parseLongArray(String str)
+            throws IOException {
+        String[] strArray = str.split(LONG_ARRAY_SEPARATOR);
+        long[] longArray = new long[strArray.length];
+        for (int i = 0; i < longArray.length; i++) {
+            try {
+                longArray[i] = Long.parseLong(strArray[i], 16);
+            } catch (NumberFormatException e) {
+                throw new IOException("Invalid long integer value: " + strArray[i]);
+            }
+        }
+        return longArray;
+    }
+
+    /**
+     * Get the String value of the given node.  An IOException will be thrown if the given
+     * node doesn't contain a String value (internal node).
+     *
+     * @param node The node to get the value from
+     * @return String
+     * @throws IOException
+     */
+    private static String getValue(Node node) throws IOException {
+        if (node.getValue() == null) {
+            throw new IOException("Attempt to retreive value from non-leaf node: "
+                    + node.getName());
+        }
+        return node.getValue();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
index 321254c..19af85f 100644
--- a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
+++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
@@ -1,15 +1,14 @@
 package com.android.server.wifi.hotspot2;
 
-import static com.android.server.wifi.anqp.Constants.BYTES_IN_EUI48;
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.hotspot2.anqp.Constants.BYTES_IN_EUI48;
+import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK;
 
 import android.net.wifi.ScanResult;
 import android.util.Log;
 
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.anqp.RawByteElement;
-import com.android.server.wifi.anqp.VenueNameElement;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.RawByteElement;
 import com.android.server.wifi.util.InformationElementUtil;
 
 import java.nio.BufferUnderflowException;
@@ -24,9 +23,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:";
 
@@ -95,12 +92,9 @@
     /*
      * From Interworking element:
      * mAnt non null indicates the presence of Interworking, i.e. 802.11u
-     * mVenueGroup and mVenueType may be null if not present in the Interworking element.
      */
     private final Ant mAnt;
     private final boolean mInternet;
-    private final VenueNameElement.VenueGroup mVenueGroup;
-    private final VenueNameElement.VenueType mVenueType;
 
     /*
      * From HS20 Indication element:
@@ -256,14 +250,12 @@
         mCapacity = bssLoad.capacity;
         mAnt = interworking.ant;
         mInternet = interworking.internet;
-        mVenueGroup = interworking.venueGroup;
-        mVenueType = interworking.venueType;
         mHSRelease = vsa.hsRelease;
         mAnqpDomainID = vsa.anqpDomainID;
         mAnqpOICount = roamingConsortium.anqpOICount;
         mRoamingConsortiums = roamingConsortium.roamingConsortiums;
         mExtendedCapabilities = extendedCapabilities;
-        mANQPElements = SupplicantBridge.parseANQPLines(anqpLines);
+        mANQPElements = null;
         //set up channel info
         mPrimaryFreq = freq;
 
@@ -303,10 +295,10 @@
             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"
+                    + (extendedCapabilities.is80211McRTTResponder() ? "Support RTT responder"
                     : "Do not support RTT responder"));
             Log.v("WifiMode", mSSID
                     + ", WifiMode: " + InformationElementUtil.WifiMode.toString(mWifiMode)
@@ -339,8 +331,6 @@
         mCapacity = base.mCapacity;
         mAnt = base.mAnt;
         mInternet = base.mInternet;
-        mVenueGroup = base.mVenueGroup;
-        mVenueType = base.mVenueType;
         mHSRelease = base.mHSRelease;
         mAnqpDomainID = base.mAnqpDomainID;
         mAnqpOICount = base.mAnqpOICount;
@@ -380,9 +370,11 @@
     }
 
     public String getTrimmedSSID() {
-        for (int n = 0; n < mSSID.length(); n++) {
-            if (mSSID.charAt(n) != 0) {
-                return mSSID;
+        if (mSSID != null) {
+            for (int n = 0; n < mSSID.length(); n++) {
+                if (mSSID.charAt(n) != 0) {
+                    return mSSID;
+                }
             }
         }
         return "";
@@ -420,14 +412,6 @@
         return mInternet;
     }
 
-    public VenueNameElement.VenueGroup getVenueGroup() {
-        return mVenueGroup;
-    }
-
-    public VenueNameElement.VenueType getVenueType() {
-        return mVenueType;
-    }
-
     public HSRelease getHSRelease() {
         return mHSRelease;
     }
@@ -452,10 +436,6 @@
         return mRoamingConsortiums;
     }
 
-    public Long getExtendedCapabilities() {
-        return mExtendedCapabilities.extendedCapabilities;
-    }
-
     public Map<Constants.ANQPElementType, ANQPElement> getANQPElements() {
         return mANQPElements;
     }
@@ -481,7 +461,7 @@
     }
 
     public boolean is80211McResponderSupport() {
-        return mExtendedCapabilities.is80211McRTTResponder;
+        return mExtendedCapabilities.is80211McRTTResponder();
     }
 
     public boolean isSSID_UTF8() {
@@ -511,11 +491,11 @@
     public String toString() {
         return String.format("NetworkInfo{SSID='%s', HESSID=%x, BSSID=%x, StationCount=%d, " +
                 "ChannelUtilization=%d, Capacity=%d, Ant=%s, Internet=%s, " +
-                "VenueGroup=%s, VenueType=%s, HSRelease=%s, AnqpDomainID=%d, " +
+                "HSRelease=%s, AnqpDomainID=%d, " +
                 "AnqpOICount=%d, RoamingConsortiums=%s}",
                 mSSID, mHESSID, mBSSID, mStationCount,
                 mChannelUtilization, mCapacity, mAnt, mInternet,
-                mVenueGroup, mVenueType, mHSRelease, mAnqpDomainID,
+                mHSRelease, mAnqpDomainID,
                 mAnqpOICount, Utils.roamingConsortiumsToString(mRoamingConsortiums));
     }
 
diff --git a/service/java/com/android/server/wifi/hotspot2/OMADMAdapter.java b/service/java/com/android/server/wifi/hotspot2/OMADMAdapter.java
deleted file mode 100644
index 9e1a582..0000000
--- a/service/java/com/android/server/wifi/hotspot2/OMADMAdapter.java
+++ /dev/null
@@ -1,593 +0,0 @@
-package com.android.server.wifi.hotspot2;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.net.wifi.WifiManager;
-import android.os.SystemProperties;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.server.wifi.anqp.eap.EAP;
-import com.android.server.wifi.hotspot2.omadm.MOTree;
-import com.android.server.wifi.hotspot2.omadm.OMAConstants;
-import com.android.server.wifi.hotspot2.omadm.OMAConstructed;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static com.android.server.wifi.anqp.eap.NonEAPInnerAuth.NonEAPType;
-import static com.android.server.wifi.anqp.eap.NonEAPInnerAuth.mapInnerType;
-
-public class OMADMAdapter {
-    private final Context mContext;
-    private final String mImei;
-    private final String mImsi;
-    private final String mDevID;
-    private final List<PathAccessor> mDevInfo;
-    private final List<PathAccessor> mDevDetail;
-
-    private static final int IMEI_Length = 14;
-
-    private static final String[] ExtWiFiPath = {"DevDetail", "Ext", "org.wi-fi", "Wi-Fi"};
-
-    private static final Map<String, String> RTProps = new HashMap<>();
-
-    private MOTree mDevInfoTree;
-    private MOTree mDevDetailTree;
-
-    private static OMADMAdapter sInstance;
-
-    static {
-        RTProps.put(ExtWiFiPath[2], "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext:1.0");
-    }
-
-    private static abstract class PathAccessor {
-        private final String[] mPath;
-        private final int mHashCode;
-
-        protected PathAccessor(Object ... path) {
-            int length = 0;
-            for (Object o : path) {
-                if (o.getClass() == String[].class) {
-                    length += ((String[]) o).length;
-                }
-                else {
-                    length++;
-                }
-            }
-            mPath = new String[length];
-            int n = 0;
-            for (Object o : path) {
-                if (o.getClass() == String[].class) {
-                    for (String element : (String[]) o) {
-                        mPath[n++] = element;
-                    }
-                }
-                else if (o.getClass() == Integer.class) {
-                    mPath[n++] = "x" + o.toString();
-                }
-                else {
-                    mPath[n++] = o.toString();
-                }
-            }
-            mHashCode = Arrays.hashCode(mPath);
-        }
-
-        @Override
-        public int hashCode() {
-            return mHashCode;
-        }
-
-        @Override
-        public boolean equals(Object thatObject) {
-            return thatObject == this || (thatObject instanceof ConstPathAccessor &&
-                    Arrays.equals(mPath, ((PathAccessor) thatObject).mPath));
-        }
-
-        private String[] getPath() {
-            return mPath;
-        }
-
-        protected abstract Object getValue();
-    }
-
-    private static class ConstPathAccessor<T> extends PathAccessor {
-        private final T mValue;
-
-        protected ConstPathAccessor(T value, Object ... path) {
-            super(path);
-            mValue = value;
-        }
-
-        protected Object getValue() {
-            return mValue;
-        }
-    }
-
-    public static OMADMAdapter getInstance(Context context) {
-        synchronized (OMADMAdapter.class) {
-            if (sInstance == null) {
-                sInstance = new OMADMAdapter(context);
-            }
-            return sInstance;
-        }
-    }
-
-    private OMADMAdapter(Context context) {
-        mContext = context;
-
-        TelephonyManager tm = (TelephonyManager) context
-                .getSystemService(Context.TELEPHONY_SERVICE);
-        String simOperator = tm.getSimOperator();
-        mImsi = tm.getSubscriberId();
-        mImei = tm.getImei();
-        String strDevId;
-
-        /* Use MEID for sprint */
-        if ("310120".equals(simOperator) || (mImsi != null && mImsi.startsWith("310120"))) {
-                /* MEID is 14 digits. If IMEI is returned as DevId, MEID can be extracted by taking
-                 * first 14 characters. This is not always true but should be the case for sprint */
-            strDevId = tm.getDeviceId().toUpperCase(Locale.US);
-            if (strDevId != null && strDevId.length() >= IMEI_Length) {
-                strDevId = strDevId.substring(0, IMEI_Length);
-            } else {
-                Log.w(Utils.hs2LogTag(getClass()),
-                        "MEID cannot be extracted from DeviceId " + strDevId);
-            }
-        } else {
-            if (isPhoneTypeLTE()) {
-                strDevId = mImei;
-            } else {
-                strDevId = tm.getDeviceId();
-            }
-            if (strDevId == null) {
-                strDevId = "unknown";
-            }
-            strDevId = strDevId.toUpperCase(Locale.US);
-
-            if (!isPhoneTypeLTE()) {
-                strDevId = strDevId.substring(0, IMEI_Length);
-            }
-        }
-        mDevID = strDevId;
-
-        mDevInfo = new ArrayList<>();
-        mDevInfo.add(new ConstPathAccessor<>(strDevId, "DevInfo", "DevID"));
-        mDevInfo.add(new ConstPathAccessor<>(getProperty(context, "Man", "ro.product.manufacturer", "unknown"), "DevInfo", "Man"));
-        mDevInfo.add(new ConstPathAccessor<>(getProperty(context, "Mod", "ro.product.model", "generic"), "DevInfo", "Mod"));
-        mDevInfo.add(new ConstPathAccessor<>(getLocale(context), "DevInfo", "Lang"));
-        mDevInfo.add(new ConstPathAccessor<>("1.2", "DevInfo", "DmV"));
-
-        mDevDetail = new ArrayList<>();
-        mDevDetail.add(new ConstPathAccessor<>(getDeviceType(), "DevDetail", "DevType"));
-        mDevDetail.add(new ConstPathAccessor<>(SystemProperties.get("ro.product.brand"), "DevDetail", "OEM"));
-        mDevDetail.add(new ConstPathAccessor<>(getVersion(context, false), "DevDetail", "FwV"));
-        mDevDetail.add(new ConstPathAccessor<>(getVersion(context, true), "DevDetail", "SwV"));
-        mDevDetail.add(new ConstPathAccessor<>(getHwV(), "DevDetail", "HwV"));
-        mDevDetail.add(new ConstPathAccessor<>("TRUE", "DevDetail", "LrgObj"));
-
-        mDevDetail.add(new ConstPathAccessor<>(32, "DevDetail", "URI", "MaxDepth"));
-        mDevDetail.add(new ConstPathAccessor<>(2048, "DevDetail", "URI", "MaxTotLen"));
-        mDevDetail.add(new ConstPathAccessor<>(64, "DevDetail", "URI", "MaxSegLen"));
-
-        AtomicInteger index = new AtomicInteger(1);
-        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
-        mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAPv2), ExtWiFiPath, "EAPMethodList", index, "InnerMethod"));
-
-        index.incrementAndGet();
-        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
-        mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.PAP), ExtWiFiPath, "EAPMethodList", index, "InnerMethod"));
-
-        index.incrementAndGet();
-        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
-        mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAP), ExtWiFiPath, "EAPMethodList", index, "InnerMethod"));
-
-        index.incrementAndGet();
-        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TLS, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
-        index.incrementAndGet();
-        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKA, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
-        index.incrementAndGet();
-        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKAPrim, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
-        index.incrementAndGet();
-        mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_SIM, ExtWiFiPath, "EAPMethodList", index, "EAPType"));
-
-        mDevDetail.add(new ConstPathAccessor<>("FALSE", ExtWiFiPath, "ManufacturingCertificate"));
-        mDevDetail.add(new ConstPathAccessor<>(mImsi, ExtWiFiPath, "IMSI"));
-        mDevDetail.add(new ConstPathAccessor<>(mImei, ExtWiFiPath, "IMEI_MEID"));
-        mDevDetail.add(new PathAccessor(ExtWiFiPath, "Wi-FiMACAddress") {
-            @Override
-            protected String getValue() {
-                return getMAC();
-            }
-        });
-    }
-
-    private static void buildNode(PathAccessor pathAccessor, int depth, OMAConstructed parent)
-            throws IOException {
-        String[] path = pathAccessor.getPath();
-        String name = path[depth];
-        if (depth < path.length - 1) {
-            OMAConstructed node = (OMAConstructed) parent.getChild(name);
-            if (node == null) {
-                node = (OMAConstructed) parent.addChild(name, RTProps.get(name),
-                        null, null);
-            }
-            buildNode(pathAccessor, depth + 1, node);
-        }
-        else if (pathAccessor.getValue() != null) {
-            parent.addChild(name, null, pathAccessor.getValue().toString(), null);
-        }
-    }
-
-    public String getMAC() {
-        WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-        return wifiManager != null ?
-                String.format("%012x",
-                        Utils.parseMac(wifiManager.getConnectionInfo().getMacAddress())) :
-                null;
-    }
-
-    public String getImei() {
-        return mImei;
-    }
-
-    public byte[] getMeid() {
-        return Arrays.copyOf(mImei.getBytes(StandardCharsets.ISO_8859_1), IMEI_Length);
-    }
-
-    public String getDevID() {
-        return mDevID;
-    }
-
-    public MOTree getMO(String urn) {
-        try {
-            switch (urn) {
-                case OMAConstants.DevInfoURN:
-                    if (mDevInfoTree == null) {
-                        OMAConstructed root = new OMAConstructed(null, "DevInfo", urn);
-                        for (PathAccessor pathAccessor : mDevInfo) {
-                            buildNode(pathAccessor, 1, root);
-                        }
-                        mDevInfoTree = MOTree.buildMgmtTree(OMAConstants.DevInfoURN,
-                                OMAConstants.OMAVersion, root);
-                    }
-                    return mDevInfoTree;
-                case OMAConstants.DevDetailURN:
-                    if (mDevDetailTree == null) {
-                        OMAConstructed root = new OMAConstructed(null, "DevDetail", urn);
-                        for (PathAccessor pathAccessor : mDevDetail) {
-                            buildNode(pathAccessor, 1, root);
-                        }
-                        mDevDetailTree = MOTree.buildMgmtTree(OMAConstants.DevDetailURN,
-                                OMAConstants.OMAVersion, root);
-                    }
-                    return mDevDetailTree;
-                default:
-                    throw new IllegalArgumentException(urn);
-            }
-        }
-        catch (IOException ioe) {
-            Log.e(Utils.hs2LogTag(getClass()), "Caught exception building OMA Tree: " + ioe, ioe);
-            return null;
-        }
-
-        /*
-        switch (urn) {
-            case DevInfoURN: return DevInfo;
-            case DevDetailURN: return DevDetail;
-            default: throw new IllegalArgumentException(urn);
-        }
-        */
-    }
-
-    // TODO: For now, assume the device supports LTE.
-    private static boolean isPhoneTypeLTE() {
-        return true;
-    }
-
-    private static String getHwV() {
-        try {
-            return SystemProperties.get("ro.hardware", "Unknown")
-                    + "." + SystemProperties.get("ro.revision", "Unknown");
-        } catch (RuntimeException e) {
-            return  "Unknown";
-        }
-    }
-
-    private static String getDeviceType() {
-        String devicetype = SystemProperties.get("ro.build.characteristics");
-        if ((((TextUtils.isEmpty(devicetype)) || (!devicetype.equals("tablet"))))) {
-            devicetype = "phone";
-        }
-        return devicetype;
-    }
-
-    private static String getVersion(Context context, boolean swv) {
-        String version;
-        try {
-            if (!isSprint(context) && swv) {
-                return "Android " + SystemProperties.get("ro.build.version.release");
-            } else {
-                version = SystemProperties.get("ro.build.version.full");
-                if (null == version || version.equals("")) {
-                    return SystemProperties.get("ro.build.id", null) + "~"
-                            + SystemProperties.get("ro.build.config.version", null) + "~"
-                            + SystemProperties.get("gsm.version.baseband", null) + "~"
-                            + SystemProperties.get("ro.gsm.flexversion", null);
-                }
-            }
-        } catch (RuntimeException e) {
-            return "Unknown";
-        }
-        return version;
-    }
-
-    private static boolean isSprint(Context context) {
-        TelephonyManager tm = (TelephonyManager) context
-                .getSystemService(Context.TELEPHONY_SERVICE);
-        String simOperator = tm.getSimOperator();
-        String imsi = tm.getSubscriberId();
-        /* Use MEID for sprint */
-        if ("310120".equals(simOperator) || (imsi != null && imsi.startsWith("310120"))) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    private static String getLocale(Context context) {
-        String strLang = readValueFromFile(context, "Lang");
-        if (strLang == null) {
-            strLang = Locale.getDefault().toString();
-        }
-        return strLang;
-    }
-
-    private static String getProperty(Context context, String key, String propKey, String dflt) {
-        String strMan = readValueFromFile(context, key);
-        if (strMan == null) {
-            strMan = SystemProperties.get(propKey, dflt);
-        }
-        return strMan;
-    }
-
-    private static String readValueFromFile(Context context, String propName) {
-        String ret = null;
-        // use preference instead of the system property
-        SharedPreferences prefs = context.getSharedPreferences("dmconfig", 0);
-        if (prefs.contains(propName)) {
-            ret = prefs.getString(propName, "");
-            if (ret.length() == 0) {
-                ret = null;
-            }
-        }
-        return ret;
-    }
-
-    private static final String DevDetail =
-        "<MgmtTree>" +
-            "<VerDTD>1.2</VerDTD>" +
-            "<Node>" +
-                "<NodeName>DevDetail</NodeName>" +
-                "<RTProperties>" +
-                    "<Type>" +
-                        "<DDFName>urn:oma:mo:oma-dm-devdetail:1.0</DDFName>" +
-                    "</Type>" +
-                "</RTProperties>" +
-                "<Node>" +
-                    "<NodeName>Ext</NodeName>" +
-                    "<Node>" +
-                        "<NodeName>org.wi-fi</NodeName>" +
-                        "<RTProperties>" +
-                            "<Type>" +
-                                "<DDFName>" +
-                                    "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext :1.0" +
-                                "</DDFName>" +
-                            "</Type>" +
-                        "</RTProperties>" +
-                        "<Node>" +
-                            "<NodeName>Wi-Fi</NodeName>" +
-                            "<Node>" +
-                                "<NodeName>EAPMethodList</NodeName>" +
-                                "<Node>" +
-                                    "<NodeName>Method01</NodeName>" +
-                                    "<!-- EAP-TTLS/MS-CHAPv2 -->" +
-                                    "<Node>" +
-                                        "<NodeName>EAPType</NodeName>" +
-                                        "<Value>21</Value>" +
-                                    "</Node>" +
-                                    "<Node>" +
-                                        "<NodeName>InnerMethod</NodeName>" +
-                                        "<Value>MS-CHAP-V2</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>Method02</NodeName>" +
-                                    "<!-- EAP-TLS -->" +
-                                    "<Node>" +
-                                        "<NodeName>EAPType</NodeName>" +
-                                        "<Value>13</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>Method03</NodeName>" +
-                                    "<!-- EAP-SIM -->" +
-                                    "<Node>" +
-                                        "<NodeName>EAPType</NodeName>" +
-                                        "<Value>18</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>Method04</NodeName>" +
-                                    "<!-- EAP-AKA -->" +
-                                    "<Node>" +
-                                        "<NodeName>EAPType</NodeName>" +
-                                        "<Value>23</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>Method05</NodeName>" +
-                                    "<!-- EAP-AKA' -->" +
-                                    "<Node>" +
-                                        "<NodeName>EAPType</NodeName>" +
-                                        "<Value>50</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>Method06</NodeName>" +
-                                    "<!-- Supported method (EAP-TTLS/PAP) not mandated by Hotspot2.0-->" +
-                                    "<Node>" +
-                                        "<NodeName>EAPType</NodeName>" +
-                                        "<Value>21</Value>" +
-                                    "</Node>" +
-                                    "<Node>" +
-                                        "<NodeName>InnerMethod</NodeName>" +
-                                        "<Value>PAP</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>Method07</NodeName>" +
-                                    "<!-- Supported method (PEAP/EAP-GTC) not mandated by Hotspot 2.0-->" +
-                                    "<Node>" +
-                                        "<NodeName>EAPType</NodeName>" +
-                                        "<Value>25</Value>" +
-                                    "</Node>" +
-                                    "<Node>" +
-                                        "<NodeName>InnerEAPType</NodeName>" +
-                                        "<Value>6</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                            "</Node>" +
-                            "<Node>" +
-                                "<NodeName>SPCertificate</NodeName>" +
-                                "<Node>" +
-                                    "<NodeName>Cert01</NodeName>" +
-                                    "<Node>" +
-                                        "<NodeName>CertificateIssuerName</NodeName>" +
-                                        "<Value>CN=RuckusCA</Value>" +
-                                    "</Node>" +
-                                "</Node>" +
-                            "</Node>" +
-                            "<Node>" +
-                                "<NodeName>ManufacturingCertificate</NodeName>" +
-                                "<Value>FALSE</Value>" +
-                            "</Node>" +
-                            "<Node>" +
-                                "<NodeName>Wi-FiMACAddress</NodeName>" +
-                                "<Value>001d2e112233</Value>" +
-                            "</Node>" +
-                            "<Node>" +
-                                "<NodeName>ClientTriggerRedirectURI</NodeName>" +
-                                "<Value>http://127.0.0.1:12345/index.htm</Value>" +
-                            "</Node>" +
-                            "<Node>" +
-                                "<NodeName>Ops</NodeName>" +
-                                "<Node>" +
-                                    "<NodeName>launchBrowserToURI</NodeName>" +
-                                    "<Value></Value>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>negotiateClientCertTLS</NodeName>" +
-                                    "<Value></Value>" +
-                                "</Node>" +
-                                "<Node>" +
-                                    "<NodeName>getCertificate</NodeName>" +
-                                    "<Value></Value>" +
-                                "</Node>" +
-                            "</Node>" +
-                        "</Node>" +
-                        "<!-- End of Wi-Fi node -->" +
-                    "</Node>" +
-                    "<!-- End of org.wi-fi node -->" +
-                "</Node>" +
-                "<!-- End of Ext node -->" +
-                "<Node>" +
-                    "<NodeName>URI</NodeName>" +
-                    "<Node>" +
-                        "<NodeName>MaxDepth</NodeName>" +
-                        "<Value>32</Value>" +
-                    "</Node>" +
-                    "<Node>" +
-                        "<NodeName>MaxTotLen</NodeName>" +
-                        "<Value>2048</Value>" +
-                    "</Node>" +
-                    "<Node>" +
-                        "<NodeName>MaxSegLen</NodeName>" +
-                        "<Value>64</Value>" +
-                    "</Node>" +
-                "</Node>" +
-                "<Node>" +
-                    "<NodeName>DevType</NodeName>" +
-                    "<Value>Smartphone</Value>" +
-                "</Node>" +
-                "<Node>" +
-                    "<NodeName>OEM</NodeName>" +
-                    "<Value>ACME</Value>" +
-                "</Node>" +
-                "<Node>" +
-                    "<NodeName>FwV</NodeName>" +
-                    "<Value>1.2.100.5</Value>" +
-                "</Node>" +
-                "<Node>" +
-                    "<NodeName>SwV</NodeName>" +
-                    "<Value>9.11.130</Value>" +
-                "</Node>" +
-                "<Node>" +
-                    "<NodeName>HwV</NodeName>" +
-                    "<Value>1.0</Value>" +
-                "</Node>" +
-                "<Node>" +
-                    "<NodeName>LrgObj</NodeName>" +
-                    "<Value>TRUE</Value>" +
-                "</Node>" +
-            "</Node>" +
-        "</MgmtTree>";
-
-
-    private static final String DevInfo =
-        "<MgmtTree>" +
-            "<VerDTD>1.2</VerDTD>" +
-            "<Node>" +
-                "<NodeName>DevInfo</NodeName>" +
-                "<RTProperties>" +
-                    "<Type>" +
-                        "<DDFName>urn:oma:mo:oma-dm-devinfo:1.0" +
-                        "</DDFName>" +
-                    "</Type>" +
-                "</RTProperties>" +
-            "</Node>" +
-            "<Node>" +
-                "<NodeName>DevID</NodeName>" +
-                "<Path>DevInfo</Path>" +
-                "<Value>urn:acme:00-11-22-33-44-55</Value>" +
-            "</Node>" +
-            "<Node>" +
-                "<NodeName>Man</NodeName>" +
-                "<Path>DevInfo</Path>" +
-                "<Value>ACME</Value>" +
-            "</Node>" +
-            "<Node>" +
-                "<NodeName>Mod</NodeName>" +
-                "<Path>DevInfo</Path>" +
-                "<Value>HS2.0-01</Value>" +
-            "</Node>" +
-            "<Node>" +
-                "<NodeName>DmV</NodeName>" +
-                "<Path>DevInfo</Path>" +
-                "<Value>1.2</Value>" +
-            "</Node>" +
-            "<Node>" +
-                "<NodeName>Lang</NodeName>" +
-                "<Path>DevInfo</Path>" +
-                "<Value>en-US</Value>" +
-            "</Node>" +
-        "</MgmtTree>";
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/OWNERS b/service/java/com/android/server/wifi/hotspot2/OWNERS
new file mode 100644
index 0000000..bea5780
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/OWNERS
@@ -0,0 +1 @@
+zqiu@google.com
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointConfigStoreData.java b/service/java/com/android/server/wifi/hotspot2/PasspointConfigStoreData.java
new file mode 100644
index 0000000..74a4760
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointConfigStoreData.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.text.TextUtils;
+
+import com.android.internal.util.XmlUtils;
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.WifiConfigStore;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.util.XmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Responsible for Passpoint specific configuration store data.  There are two types of
+ * configuration data, system wide and user specific.  The system wide configurations are stored
+ * in the share store and user specific configurations are store in the user store.
+ *
+ * Below are the current configuration data for each respective store file, the list will
+ * probably grow in the future.
+ *
+ * Share Store (system wide configurations)
+ * - Current provider index - use for assigning provider ID during provider creation, to make
+ *                            sure each provider will have an unique ID across all users.
+ *
+ * User Store (user specific configurations)
+ * - Provider list - list of Passpoint provider configurations
+ *
+ */
+public class PasspointConfigStoreData implements WifiConfigStore.StoreData {
+    private static final String XML_TAG_SECTION_HEADER_PASSPOINT_CONFIG_DATA =
+            "PasspointConfigData";
+    private static final String XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER_LIST =
+            "ProviderList";
+    private static final String XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER =
+            "Provider";
+    private static final String XML_TAG_SECTION_HEADER_PASSPOINT_CONFIGURATION =
+            "Configuration";
+
+    private static final String XML_TAG_PROVIDER_ID = "ProviderID";
+    private static final String XML_TAG_CREATOR_UID = "CreatorUID";
+    private static final String XML_TAG_CA_CERTIFICATE_ALIAS = "CaCertificateAlias";
+    private static final String XML_TAG_CLIENT_CERTIFICATE_ALIAS = "ClientCertificateAlias";
+    private static final String XML_TAG_CLIENT_PRIVATE_KEY_ALIAS = "ClientPrivateKeyAlias";
+
+    private static final String XML_TAG_PROVIDER_INDEX = "ProviderIndex";
+
+    private final WifiKeyStore mKeyStore;
+    private final SIMAccessor mSimAccessor;
+    private final DataSource mDataSource;
+
+    /**
+     * Interface define the data source for the Passpoint configuration store data.
+     */
+    public interface DataSource {
+        /**
+         * Retrieve the provider list from the data source.
+         *
+         * @return List of {@link PasspointProvider}
+         */
+        List<PasspointProvider> getProviders();
+
+        /**
+         * Set the provider list in the data source.
+         *
+         * @param providers The list of providers
+         */
+        void setProviders(List<PasspointProvider> providers);
+
+        /**
+         * Retrieve the current provider index.
+         *
+         * @return long
+         */
+        long getProviderIndex();
+
+        /**
+         * Set the current provider index.
+         *
+         * @param providerIndex The provider index used for provider creation
+         */
+        void setProviderIndex(long providerIndex);
+    }
+
+    PasspointConfigStoreData(WifiKeyStore keyStore, SIMAccessor simAccessor,
+            DataSource dataSource) {
+        mKeyStore = keyStore;
+        mSimAccessor = simAccessor;
+        mDataSource = dataSource;
+    }
+
+    @Override
+    public void serializeData(XmlSerializer out, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            serializeShareData(out);
+        } else {
+            serializeUserData(out);
+        }
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            deserializeShareData(in, outerTagDepth);
+        } else {
+            deserializeUserData(in, outerTagDepth);
+        }
+    }
+
+    @Override
+    public void resetData(boolean shared) {
+        if (shared) {
+            resetShareData();
+        } else {
+            resetUserData();
+        }
+    }
+
+    @Override
+    public String getName() {
+        return XML_TAG_SECTION_HEADER_PASSPOINT_CONFIG_DATA;
+    }
+
+    @Override
+    public boolean supportShareData() {
+        return true;
+    }
+
+    /**
+     * Serialize share data (system wide Passpoint configurations) to a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeShareData(XmlSerializer out) throws XmlPullParserException, IOException {
+        XmlUtil.writeNextValue(out, XML_TAG_PROVIDER_INDEX, mDataSource.getProviderIndex());
+    }
+
+    /**
+     * Serialize user data (user specific Passpoint configurations) to a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeUserData(XmlSerializer out) throws XmlPullParserException, IOException {
+        serializeProviderList(out, mDataSource.getProviders());
+    }
+
+    /**
+     * Serialize the list of Passpoint providers from the data source to a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @param providerList The list of providers to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeProviderList(XmlSerializer out, List<PasspointProvider> providerList)
+            throws XmlPullParserException, IOException {
+        if (providerList == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER_LIST);
+        for (PasspointProvider provider : providerList) {
+            serializeProvider(out, provider);
+        }
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER_LIST);
+    }
+
+    /**
+     * Serialize a Passpoint provider to a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @param provider The provider to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeProvider(XmlSerializer out, PasspointProvider provider)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER);
+        XmlUtil.writeNextValue(out, XML_TAG_PROVIDER_ID, provider.getProviderId());
+        XmlUtil.writeNextValue(out, XML_TAG_CREATOR_UID, provider.getCreatorUid());
+        XmlUtil.writeNextValue(out, XML_TAG_CA_CERTIFICATE_ALIAS,
+                provider.getCaCertificateAlias());
+        XmlUtil.writeNextValue(out, XML_TAG_CLIENT_CERTIFICATE_ALIAS,
+                provider.getClientCertificateAlias());
+        XmlUtil.writeNextValue(out, XML_TAG_CLIENT_PRIVATE_KEY_ALIAS,
+                provider.getClientPrivateKeyAlias());
+        if (provider.getConfig() != null) {
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PASSPOINT_CONFIGURATION);
+            PasspointXmlUtils.serializePasspointConfiguration(out, provider.getConfig());
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_PASSPOINT_CONFIGURATION);
+        }
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER);
+    }
+
+    /**
+     * Deserialize share data (system wide Passpoint configurations) from the input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void deserializeShareData(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        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_PROVIDER_INDEX:
+                    mDataSource.setProviderIndex((long) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown value under share store data "
+                            + valueName[0]);
+            }
+        }
+    }
+
+    /**
+     * Deserialize user data (user specific Passpoint configurations) from the input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void deserializeUserData(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        String[] headerName = new String[1];
+        while (XmlUtil.gotoNextSectionOrEnd(in, headerName, outerTagDepth)) {
+            switch (headerName[0]) {
+                case XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER_LIST:
+                    mDataSource.setProviders(deserializeProviderList(in, outerTagDepth + 1));
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown Passpoint user store data "
+                            + headerName[0]);
+            }
+        }
+    }
+
+    /**
+     * Deserialize a list of Passpoint providers from the input stream.
+     *
+     * @param in The input stream to read data form
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return List of {@link PasspointProvider}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private List<PasspointProvider> deserializeProviderList(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        List<PasspointProvider> providerList = new ArrayList<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_PASSPOINT_PROVIDER,
+                outerTagDepth)) {
+            providerList.add(deserializeProvider(in, outerTagDepth + 1));
+        }
+        return providerList;
+    }
+
+    /**
+     * Deserialize a Passpoint provider from the input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link PasspointProvider}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private PasspointProvider deserializeProvider(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        long providerId = Long.MIN_VALUE;
+        int creatorUid = Integer.MIN_VALUE;
+        String caCertificateAlias = null;
+        String clientCertificateAlias = null;
+        String clientPrivateKeyAlias = null;
+        PasspointConfiguration config = null;
+        while (XmlUtils.nextElementWithin(in, outerTagDepth)) {
+            if (in.getAttributeValue(null, "name") != null) {
+                // Value elements.
+                String[] name = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, name);
+                switch (name[0]) {
+                    case XML_TAG_PROVIDER_ID:
+                        providerId = (long) value;
+                        break;
+                    case XML_TAG_CREATOR_UID:
+                        creatorUid = (int) value;
+                        break;
+                    case XML_TAG_CA_CERTIFICATE_ALIAS:
+                        caCertificateAlias = (String) value;
+                        break;
+                    case XML_TAG_CLIENT_CERTIFICATE_ALIAS:
+                        clientCertificateAlias = (String) value;
+                        break;
+                    case XML_TAG_CLIENT_PRIVATE_KEY_ALIAS:
+                        clientPrivateKeyAlias = (String) value;
+                        break;
+                }
+            } else {
+                if (!TextUtils.equals(in.getName(),
+                        XML_TAG_SECTION_HEADER_PASSPOINT_CONFIGURATION)) {
+                    throw new XmlPullParserException("Unexpected section under Provider: "
+                            + in.getName());
+                }
+                config = PasspointXmlUtils.deserializePasspointConfiguration(in,
+                        outerTagDepth + 1);
+            }
+        }
+        if (providerId == Long.MIN_VALUE) {
+            throw new XmlPullParserException("Missing provider ID");
+        }
+        if (config == null) {
+            throw new XmlPullParserException("Missing Passpoint configuration");
+        }
+        return new PasspointProvider(config, mKeyStore, mSimAccessor, providerId, creatorUid,
+                caCertificateAlias, clientCertificateAlias, clientPrivateKeyAlias);
+    }
+
+    /**
+     * Reset share data (system wide Passpoint configurations).
+     */
+    private void resetShareData() {
+        mDataSource.setProviderIndex(0);
+    }
+
+    /**
+     * Reset user data (user specific Passpoint configurations).
+     */
+    private void resetUserData() {
+        mDataSource.setProviders(new ArrayList<PasspointProvider>());
+    }
+}
+
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
new file mode 100644
index 0000000..6a7d0af
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java
@@ -0,0 +1,159 @@
+/*
+ * 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.hotspot2;
+
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class handles passpoint specific interactions with the AP, such as ANQP
+ * elements requests, passpoint icon requests, and wireless network management
+ * event notifications.
+ */
+public class PasspointEventHandler {
+    private final WifiNative mSupplicantHook;
+    private final Callbacks mCallbacks;
+
+    /**
+     * Interface to be implemented by the client to receive callbacks for passpoint
+     * related events.
+     */
+    public interface Callbacks {
+        /**
+         * Invoked on received of ANQP response. |anqpElements| will be null on failure.
+         * @param bssid BSSID of the AP
+         * @param anqpElements ANQP elements to be queried
+         */
+        void onANQPResponse(long bssid,
+                            Map<Constants.ANQPElementType, ANQPElement> anqpElements);
+
+        /**
+         * Invoked on received of icon response. |filename| and |data| will be null
+         * on failure.
+         * @param bssid BSSID of the AP
+         * @param filename Name of the icon file
+         * @data icon data bytes
+         */
+        void onIconResponse(long bssid, String filename, byte[] data);
+
+        /**
+         * Invoked on received of Hotspot 2.0 Wireless Network Management frame.
+         * @param data Wireless Network Management frame data
+         */
+        void onWnmFrameReceived(WnmData data);
+    }
+
+    public PasspointEventHandler(WifiNative supplicantHook, Callbacks callbacks) {
+        mSupplicantHook = supplicantHook;
+        mCallbacks = callbacks;
+    }
+
+    /**
+     * Request the specified ANQP elements |elements| from the specified AP |bssid|.
+     * @param bssid BSSID of the AP
+     * @param elements ANQP elements to be queried
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean requestANQP(long bssid, List<Constants.ANQPElementType> elements) {
+        Pair<Set<Integer>, Set<Integer>> querySets = buildAnqpIdSet(elements);
+        if (bssid == 0 || querySets == null) return false;
+        if (!mSupplicantHook.requestAnqp(
+                Utils.macToString(bssid), querySets.first, querySets.second)) {
+            Log.d(Utils.hs2LogTag(getClass()), "ANQP failed on " + Utils.macToString(bssid));
+            return false;
+        }
+        Log.d(Utils.hs2LogTag(getClass()), "ANQP initiated on " + Utils.macToString(bssid));
+        return true;
+    }
+
+    /**
+     * Request a passpoint icon file |filename| from the specified AP |bssid|.
+     * @param bssid BSSID of the AP
+     * @param fileName name of the icon file
+     * @return true if request is sent successfully, false otherwise
+     */
+    public boolean requestIcon(long bssid, String fileName) {
+        if (bssid == 0 || fileName == null) return false;
+        return mSupplicantHook.requestIcon(Utils.macToString(bssid), fileName);
+    }
+
+    /**
+     * Invoked when ANQP query is completed.
+     * TODO(zqiu): currently ANQP completion notification is through WifiMonitor,
+     * this shouldn't be needed once we switch over to wificond for ANQP requests.
+     * @param anqpEvent ANQP result data retrieved. ANQP elements could be empty in the event to
+     *                  indicate any failures.
+     */
+    public void notifyANQPDone(AnqpEvent anqpEvent) {
+        if (anqpEvent == null) return;
+        mCallbacks.onANQPResponse(anqpEvent.getBssid(), anqpEvent.getElements());
+    }
+
+    /**
+     * Invoked when icon query is completed.
+     * TODO(zqiu): currently icon completion notification is through WifiMonitor,
+     * this shouldn't be needed once we switch over to wificond for icon requests.
+     * @param iconEvent icon event data
+     */
+    public void notifyIconDone(IconEvent iconEvent) {
+        if (iconEvent == null) return;
+        mCallbacks.onIconResponse(
+                iconEvent.getBSSID(), iconEvent.getFileName(), iconEvent.getData());
+    }
+
+    /**
+     * Invoked when a Wireless Network Management (WNM) frame is received.
+     * TODO(zqiu): currently WNM frame notification is through WifiMonitor,
+     * this shouldn't be needed once we switch over to wificond for WNM frame monitoring.
+     * @param data WNM frame data
+     */
+    public void notifyWnmFrameReceived(WnmData data) {
+        mCallbacks.onWnmFrameReceived(data);
+    }
+
+    /**
+     * Create the set of ANQP ID's to query.
+     *
+     * @param querySet elements to query
+     * @return Pair of <set of ANQP ID's, set of HS20 subtypes>
+     */
+    private static Pair<Set<Integer>, Set<Integer>> buildAnqpIdSet(
+            List<Constants.ANQPElementType> querySet) {
+        Set<Integer> anqpIds = new HashSet<>();
+        Set<Integer> hs20Subtypes = new HashSet<>();
+        for (Constants.ANQPElementType elementType : querySet) {
+            Integer id = Constants.getANQPElementID(elementType);
+            if (id != null) {
+                anqpIds.add(id);
+            } else {
+                id = Constants.getHS20ElementID(elementType);
+                hs20Subtypes.add(id);
+            }
+        }
+        return Pair.create(anqpIds, hs20Subtypes);
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
new file mode 100644
index 0000000..9000d43
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -0,0 +1,551 @@
+/*
+ * 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.hotspot2;
+
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT;
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON;
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_OSU_PROVIDERS_LIST;
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION;
+import static android.net.wifi.WifiManager.EXTRA_ANQP_ELEMENT_DATA;
+import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG;
+import static android.net.wifi.WifiManager.EXTRA_DELAY;
+import static android.net.wifi.WifiManager.EXTRA_ESS;
+import static android.net.wifi.WifiManager.EXTRA_FILENAME;
+import static android.net.wifi.WifiManager.EXTRA_ICON;
+import static android.net.wifi.WifiManager.EXTRA_SUBSCRIPTION_REMEDIATION_METHOD;
+import static android.net.wifi.WifiManager.EXTRA_URL;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.WifiConfigManager;
+import com.android.server.wifi.WifiConfigStore;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.RawByteElement;
+import com.android.server.wifi.util.InformationElementUtil;
+import com.android.server.wifi.util.ScanResultUtil;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class provides the APIs to manage Passpoint provider configurations.
+ * It deals with the following:
+ * - Maintaining a list of configured Passpoint providers for provider matching.
+ * - Persisting the providers configurations to store when required.
+ * - matching Passpoint providers based on the scan results
+ * - Supporting WifiManager Public API calls:
+ *   > addOrUpdatePasspointConfiguration()
+ *   > removePasspointConfiguration()
+ *   > getPasspointConfigurations()
+ *
+ * The provider matching requires obtaining additional information from the AP (ANQP elements).
+ * The ANQP elements will be cached using {@link AnqpCache} to avoid unnecessary requests.
+ *
+ * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
+ */
+public class PasspointManager {
+    private static final String TAG = "PasspointManager";
+
+    /**
+     * Handle for the current {@link PasspointManager} instance.  This is needed to avoid
+     * circular dependency with the WifiConfigManger, it will be used for adding the
+     * legacy Passpoint configurations.
+     *
+     * This can be eliminated once we can remove the dependency for WifiConfigManager (for
+     * triggering config store write) from this class.
+     */
+    private static PasspointManager sPasspointManager;
+
+    private final PasspointEventHandler mHandler;
+    private final SIMAccessor mSimAccessor;
+    private final WifiKeyStore mKeyStore;
+    private final PasspointObjectFactory mObjectFactory;
+    private final Map<String, PasspointProvider> mProviders;
+    private final AnqpCache mAnqpCache;
+    private final ANQPRequestManager mAnqpRequestManager;
+    private final WifiConfigManager mWifiConfigManager;
+    private final CertificateVerifier mCertVerifier;
+
+    // Counter used for assigning unique identifier to each provider.
+    private long mProviderIndex;
+
+    private class CallbackHandler implements PasspointEventHandler.Callbacks {
+        private final Context mContext;
+        CallbackHandler(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public void onANQPResponse(long bssid,
+                Map<Constants.ANQPElementType, ANQPElement> anqpElements) {
+            // Notify request manager for the completion of a request.
+            ANQPNetworkKey anqpKey =
+                    mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null);
+            if (anqpElements == null || anqpKey == null) {
+                // Query failed or the request wasn't originated from us (not tracked by the
+                // request manager). Nothing to be done.
+                return;
+            }
+
+            // Add new entry to the cache.
+            mAnqpCache.addEntry(anqpKey, anqpElements);
+
+            // Broadcast OSU providers info.
+            if (anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) {
+                RawByteElement osuProviders = (RawByteElement) anqpElements.get(
+                        Constants.ANQPElementType.HSOSUProviders);
+                Intent intent = new Intent(ACTION_PASSPOINT_OSU_PROVIDERS_LIST);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                intent.putExtra(EXTRA_BSSID_LONG, bssid);
+                intent.putExtra(EXTRA_ANQP_ELEMENT_DATA, osuProviders.getPayload());
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                        android.Manifest.permission.ACCESS_WIFI_STATE);
+            }
+        }
+
+        @Override
+        public void onIconResponse(long bssid, String fileName, byte[] data) {
+            Intent intent = new Intent(ACTION_PASSPOINT_ICON);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            intent.putExtra(EXTRA_BSSID_LONG, bssid);
+            intent.putExtra(EXTRA_FILENAME, fileName);
+            if (data != null) {
+                intent.putExtra(EXTRA_ICON, Icon.createWithData(data, 0, data.length));
+            }
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    android.Manifest.permission.ACCESS_WIFI_STATE);
+        }
+
+        @Override
+        public void onWnmFrameReceived(WnmData event) {
+            // %012x HS20-SUBSCRIPTION-REMEDIATION "%u %s", osu_method, url
+            // %012x HS20-DEAUTH-IMMINENT-NOTICE "%u %u %s", code, reauth_delay, url
+            Intent intent;
+            if (event.isDeauthEvent()) {
+                intent = new Intent(ACTION_PASSPOINT_DEAUTH_IMMINENT);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                intent.putExtra(EXTRA_BSSID_LONG, event.getBssid());
+                intent.putExtra(EXTRA_URL, event.getUrl());
+                intent.putExtra(EXTRA_ESS, event.isEss());
+                intent.putExtra(EXTRA_DELAY, event.getDelay());
+            } else {
+                intent = new Intent(ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                intent.putExtra(EXTRA_BSSID_LONG, event.getBssid());
+                intent.putExtra(EXTRA_SUBSCRIPTION_REMEDIATION_METHOD, event.getMethod());
+                intent.putExtra(EXTRA_URL, event.getUrl());
+            }
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    android.Manifest.permission.ACCESS_WIFI_STATE);
+        }
+    }
+
+    /**
+     * Data provider for the Passpoint configuration store data {@link PasspointConfigStoreData}.
+     */
+    private class DataSourceHandler implements PasspointConfigStoreData.DataSource {
+        @Override
+        public List<PasspointProvider> getProviders() {
+            List<PasspointProvider> providers = new ArrayList<>();
+            for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
+                providers.add(entry.getValue());
+            }
+            return providers;
+        }
+
+        @Override
+        public void setProviders(List<PasspointProvider> providers) {
+            mProviders.clear();
+            for (PasspointProvider provider : providers) {
+                mProviders.put(provider.getConfig().getHomeSp().getFqdn(), provider);
+            }
+        }
+
+        @Override
+        public long getProviderIndex() {
+            return mProviderIndex;
+        }
+
+        @Override
+        public void setProviderIndex(long providerIndex) {
+            mProviderIndex = providerIndex;
+        }
+    }
+
+    public PasspointManager(Context context, WifiNative wifiNative, WifiKeyStore keyStore,
+            Clock clock, SIMAccessor simAccessor, PasspointObjectFactory objectFactory,
+            WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore) {
+        mHandler = objectFactory.makePasspointEventHandler(wifiNative,
+                new CallbackHandler(context));
+        mKeyStore = keyStore;
+        mSimAccessor = simAccessor;
+        mObjectFactory = objectFactory;
+        mProviders = new HashMap<>();
+        mAnqpCache = objectFactory.makeAnqpCache(clock);
+        mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock);
+        mCertVerifier = objectFactory.makeCertificateVerifier();
+        mWifiConfigManager = wifiConfigManager;
+        mProviderIndex = 0;
+        wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
+                mKeyStore, mSimAccessor, new DataSourceHandler()));
+        sPasspointManager = this;
+    }
+
+    /**
+     * Add or update a Passpoint provider with the given configuration.
+     *
+     * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
+     * In the case when there is an existing configuration with the same FQDN
+     * a provider with the new configuration will replace the existing provider.
+     *
+     * @param config Configuration of the Passpoint provider to be added
+     * @return true if provider is added, false otherwise
+     */
+    public boolean addOrUpdateProvider(PasspointConfiguration config, int uid) {
+        if (config == null) {
+            Log.e(TAG, "Configuration not provided");
+            return false;
+        }
+        if (!config.validate()) {
+            Log.e(TAG, "Invalid configuration");
+            return false;
+        }
+
+        // For Hotspot 2.0 Release 1, the CA Certificate must be trusted by one of the pre-loaded
+        // public CAs in the system key store on the device.  Since the provisioning method
+        // for Release 1 is not standardized nor trusted,  this is a reasonable restriction
+        // to improve security.  The presence of UpdateIdentifier is used to differentiate
+        // between R1 and R2 configuration.
+        if (config.getUpdateIdentifier() == Integer.MIN_VALUE
+                && config.getCredential().getCaCertificate() != null) {
+            try {
+                mCertVerifier.verifyCaCert(config.getCredential().getCaCertificate());
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to verify CA certificate: " + e.getMessage());
+                return false;
+            }
+        }
+
+        // Create a provider and install the necessary certificates and keys.
+        PasspointProvider newProvider = mObjectFactory.makePasspointProvider(
+                config, mKeyStore, mSimAccessor, mProviderIndex++, uid);
+
+        if (!newProvider.installCertsAndKeys()) {
+            Log.e(TAG, "Failed to install certificates and keys to keystore");
+            return false;
+        }
+
+        // Remove existing provider with the same FQDN.
+        if (mProviders.containsKey(config.getHomeSp().getFqdn())) {
+            Log.d(TAG, "Replacing configuration for " + config.getHomeSp().getFqdn());
+            mProviders.get(config.getHomeSp().getFqdn()).uninstallCertsAndKeys();
+            mProviders.remove(config.getHomeSp().getFqdn());
+        }
+
+        mProviders.put(config.getHomeSp().getFqdn(), newProvider);
+        mWifiConfigManager.saveToStore(true /* forceWrite */);
+        Log.d(TAG, "Added/updated Passpoint configuration: " + config.getHomeSp().getFqdn()
+                + " by " + uid);
+        return true;
+    }
+
+    /**
+     * Remove a Passpoint provider identified by the given FQDN.
+     *
+     * @param fqdn The FQDN of the provider to remove
+     * @return true if a provider is removed, false otherwise
+     */
+    public boolean removeProvider(String fqdn) {
+        if (!mProviders.containsKey(fqdn)) {
+            Log.e(TAG, "Config doesn't exist");
+            return false;
+        }
+
+        mProviders.get(fqdn).uninstallCertsAndKeys();
+        mProviders.remove(fqdn);
+        mWifiConfigManager.saveToStore(true /* forceWrite */);
+        Log.d(TAG, "Removed Passpoint configuration: " + fqdn);
+        return true;
+    }
+
+    /**
+     * Return the installed Passpoint provider configurations.
+     *
+     * An empty list will be returned when no provider is installed.
+     *
+     * @return A list of {@link PasspointConfiguration}
+     */
+    public List<PasspointConfiguration> getProviderConfigs() {
+        List<PasspointConfiguration> configs = new ArrayList<>();
+        for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
+            configs.add(entry.getValue().getConfig());
+        }
+        return configs;
+    }
+
+    /**
+     * Find the best provider that can provide service through the given AP, which means the
+     * provider contained credential to authenticate with the given AP.
+     *
+     * Here is the current precedence of the matching rule in descending order:
+     * 1. Home Provider
+     * 2. Roaming Provider
+     *
+     * A {code null} will be returned if no matching is found.
+     *
+     * @param scanResult The scan result associated with the AP
+     * @return A pair of {@link PasspointProvider} and match status.
+     */
+    public Pair<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) {
+        // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
+        // Vendor Specific IE.
+        InformationElementUtil.RoamingConsortium roamingConsortium =
+                InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
+        InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
+                scanResult.informationElements);
+
+        // Lookup ANQP data in the cache.
+        long bssid = Utils.parseMac(scanResult.BSSID);
+        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
+                vsa.anqpDomainID);
+        ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
+
+        if (anqpEntry == null) {
+            mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
+                    roamingConsortium.anqpOICount > 0,
+                    vsa.hsRelease  == NetworkDetail.HSRelease.R2);
+            Log.d(TAG, "ANQP entry not found for: " + anqpKey);
+            return null;
+        }
+
+        Pair<PasspointProvider, PasspointMatch> bestMatch = null;
+        for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
+            PasspointProvider provider = entry.getValue();
+            PasspointMatch matchStatus = provider.match(anqpEntry.getElements());
+            if (matchStatus == PasspointMatch.HomeProvider) {
+                bestMatch = Pair.create(provider, matchStatus);
+                break;
+            }
+            if (matchStatus == PasspointMatch.RoamingProvider && bestMatch == null) {
+                bestMatch = Pair.create(provider, matchStatus);
+            }
+        }
+        if (bestMatch != null) {
+            Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
+                    bestMatch.first.getConfig().getHomeSp().getFqdn(),
+                    bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
+                            : "Roaming Provider"));
+        } else {
+            Log.d(TAG, "Match not found for " + scanResult.SSID);
+        }
+        return bestMatch;
+    }
+
+    /**
+     * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration} to the
+     * current {@link PasspointManager}.
+     *
+     * This will not trigger a config store write, since this will be invoked as part of the
+     * configuration migration, the caller will be responsible for triggering store write
+     * after the migration is completed.
+     *
+     * @param config {@link WifiConfiguration} representation of the Passpoint configuration
+     * @return true on success
+     */
+    public static boolean addLegacyPasspointConfig(WifiConfiguration config) {
+        if (sPasspointManager == null) {
+            Log.e(TAG, "PasspointManager have not been initialized yet");
+            return false;
+        }
+        Log.d(TAG, "Installing legacy Passpoint configuration: " + config.FQDN);
+        return sPasspointManager.addWifiConfig(config);
+    }
+
+    /**
+     * Sweep the ANQP cache to remove expired entries.
+     */
+    public void sweepCache() {
+        mAnqpCache.sweep();
+    }
+
+    /**
+     * Notify the completion of an ANQP request.
+     * TODO(zqiu): currently the notification is done through WifiMonitor,
+     * will no longer be the case once we switch over to use wificond.
+     */
+    public void notifyANQPDone(AnqpEvent anqpEvent) {
+        mHandler.notifyANQPDone(anqpEvent);
+    }
+
+    /**
+     * Notify the completion of an icon request.
+     * TODO(zqiu): currently the notification is done through WifiMonitor,
+     * will no longer be the case once we switch over to use wificond.
+     */
+    public void notifyIconDone(IconEvent iconEvent) {
+        mHandler.notifyIconDone(iconEvent);
+    }
+
+    /**
+     * Notify the reception of a Wireless Network Management (WNM) frame.
+     * TODO(zqiu): currently the notification is done through WifiMonitor,
+     * will no longer be the case once we switch over to use wificond.
+     */
+    public void receivedWnmFrame(WnmData data) {
+        mHandler.notifyWnmFrameReceived(data);
+    }
+
+    /**
+     * Request the specified icon file |fileName| from the specified AP |bssid|.
+     * @return true if the request is sent successfully, false otherwise
+     */
+    public boolean queryPasspointIcon(long bssid, String fileName) {
+        return mHandler.requestIcon(bssid, fileName);
+    }
+
+    /**
+     * Lookup the ANQP elements associated with the given AP from the cache. An empty map
+     * will be returned if no match found in the cache.
+     *
+     * @param scanResult The scan result associated with the AP
+     * @return Map of ANQP elements
+     */
+    public Map<Constants.ANQPElementType, ANQPElement> getANQPElements(ScanResult scanResult) {
+        // Retrieve the Hotspot 2.0 Vendor Specific IE.
+        InformationElementUtil.Vsa vsa =
+                InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements);
+
+        // Lookup ANQP data in the cache.
+        long bssid = Utils.parseMac(scanResult.BSSID);
+        ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey(
+                scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID));
+        if (anqpEntry != null) {
+            return anqpEntry.getElements();
+        }
+        return new HashMap<Constants.ANQPElementType, ANQPElement>();
+    }
+
+    /**
+     * Match the given WiFi AP to an installed Passpoint provider.  A {@link WifiConfiguration}
+     * will be generated and returned if a match is found.  The returned {@link WifiConfiguration}
+     * will contained all the necessary credentials for connecting to the given WiFi AP.
+     *
+     * A {code null} will be returned if no matching provider is found.
+     *
+     * @param scanResult The scan result of the given AP
+     * @return {@link WifiConfiguration}
+     */
+    public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
+        if (scanResult == null) {
+            Log.e(TAG, "Attempt to get matching config for a null ScanResult");
+            return null;
+        }
+        Pair<PasspointProvider, PasspointMatch> matchedProvider = matchProvider(scanResult);
+        if (matchedProvider == null) {
+            return null;
+        }
+        WifiConfiguration config = matchedProvider.first.getWifiConfig();
+        config.SSID = ScanResultUtil.createQuotedSSID(scanResult.SSID);
+        if (matchedProvider.second == PasspointMatch.HomeProvider) {
+            config.isHomeProviderNetwork = true;
+        }
+        return config;
+    }
+
+    /**
+     * Dump the current state of PasspointManager to the provided output stream.
+     *
+     * @param pw The output stream to write to
+     */
+    public void dump(PrintWriter pw) {
+        pw.println("Dump of PasspointManager");
+        pw.println("PasspointManager - Providers Begin ---");
+        for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
+            pw.println(entry.getValue());
+        }
+        pw.println("PasspointManager - Providers End ---");
+        pw.println("PasspointManager - Next provider ID to be assigned " + mProviderIndex);
+        mAnqpCache.dump(pw);
+    }
+
+    /**
+     * Add a legacy Passpoint configuration represented by a {@link WifiConfiguration}.
+     *
+     * @param wifiConfig {@link WifiConfiguration} representation of the Passpoint configuration
+     * @return true on success
+     */
+    private boolean addWifiConfig(WifiConfiguration wifiConfig) {
+        if (wifiConfig == null) {
+            return false;
+        }
+
+        // Convert to PasspointConfiguration
+        PasspointConfiguration passpointConfig =
+                PasspointProvider.convertFromWifiConfig(wifiConfig);
+        if (passpointConfig == null) {
+            return false;
+        }
+
+        // Setup aliases for enterprise certificates and key.
+        WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig;
+        String caCertificateAliasSuffix = enterpriseConfig.getCaCertificateAlias();
+        String clientCertAndKeyAliasSuffix = enterpriseConfig.getClientCertificateAlias();
+        if (passpointConfig.getCredential().getUserCredential() != null
+                && TextUtils.isEmpty(caCertificateAliasSuffix)) {
+            Log.e(TAG, "Missing CA Certificate for user credential");
+            return false;
+        }
+        if (passpointConfig.getCredential().getCertCredential() != null) {
+            if (TextUtils.isEmpty(caCertificateAliasSuffix)) {
+                Log.e(TAG, "Missing CA certificate for Certificate credential");
+                return false;
+            }
+            if (TextUtils.isEmpty(clientCertAndKeyAliasSuffix)) {
+                Log.e(TAG, "Missing client certificate and key for certificate credential");
+                return false;
+            }
+        }
+
+        // Note that for legacy configuration, the alias for client private key is the same as the
+        // alias for the client certificate.
+        PasspointProvider provider = new PasspointProvider(passpointConfig, mKeyStore,
+                mSimAccessor, mProviderIndex++, wifiConfig.creatorUid,
+                enterpriseConfig.getCaCertificateAlias(),
+                enterpriseConfig.getClientCertificateAlias(),
+                enterpriseConfig.getClientCertificateAlias());
+        mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
+        return true;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java b/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java
deleted file mode 100644
index deb60ed..0000000
--- a/service/java/com/android/server/wifi/hotspot2/PasspointMatchInfo.java
+++ /dev/null
@@ -1,246 +0,0 @@
-package com.android.server.wifi.hotspot2;
-
-import com.android.server.wifi.ScanDetail;
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.HSConnectionCapabilityElement;
-import com.android.server.wifi.anqp.HSWanMetricsElement;
-import com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
-
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.android.server.wifi.anqp.Constants.ANQPElementType;
-import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv4Availability;
-import static com.android.server.wifi.anqp.IPAddressTypeAvailabilityElement.IPv6Availability;
-
-public class PasspointMatchInfo implements Comparable<PasspointMatchInfo> {
-    private final PasspointMatch mPasspointMatch;
-    private final ScanDetail mScanDetail;
-    private final HomeSP mHomeSP;
-    private final int mScore;
-
-    private static final Map<IPv4Availability, Integer> sIP4Scores =
-            new EnumMap<>(IPv4Availability.class);
-    private static final Map<IPv6Availability, Integer> sIP6Scores =
-            new EnumMap<>(IPv6Availability.class);
-
-    private static final Map<Integer, Map<Integer, Integer>> sPortScores = new HashMap<>();
-
-    private static final int IPPROTO_ICMP = 1;
-    private static final int IPPROTO_TCP = 6;
-    private static final int IPPROTO_UDP = 17;
-    private static final int IPPROTO_ESP = 50;
-    private static final Map<NetworkDetail.Ant, Integer> sAntScores = new HashMap<>();
-
-    static {
-        // These are all arbitrarily chosen scores, subject to tuning.
-
-        sAntScores.put(NetworkDetail.Ant.FreePublic, 4);
-        sAntScores.put(NetworkDetail.Ant.ChargeablePublic, 4);
-        sAntScores.put(NetworkDetail.Ant.PrivateWithGuest, 4);
-        sAntScores.put(NetworkDetail.Ant.Private, 4);
-        sAntScores.put(NetworkDetail.Ant.Personal, 2);
-        sAntScores.put(NetworkDetail.Ant.EmergencyOnly, 2);
-        sAntScores.put(NetworkDetail.Ant.Wildcard, 1);
-        sAntScores.put(NetworkDetail.Ant.TestOrExperimental, 0);
-
-        sIP4Scores.put(IPv4Availability.NotAvailable, 0);
-        sIP4Scores.put(IPv4Availability.PortRestricted, 1);
-        sIP4Scores.put(IPv4Availability.PortRestrictedAndSingleNAT, 1);
-        sIP4Scores.put(IPv4Availability.PortRestrictedAndDoubleNAT, 1);
-        sIP4Scores.put(IPv4Availability.Unknown, 1);
-        sIP4Scores.put(IPv4Availability.Public, 2);
-        sIP4Scores.put(IPv4Availability.SingleNAT, 2);
-        sIP4Scores.put(IPv4Availability.DoubleNAT, 2);
-
-        sIP6Scores.put(IPv6Availability.NotAvailable, 0);
-        sIP6Scores.put(IPv6Availability.Reserved, 1);
-        sIP6Scores.put(IPv6Availability.Unknown, 1);
-        sIP6Scores.put(IPv6Availability.Available, 2);
-
-        Map<Integer, Integer> tcpMap = new HashMap<>();
-        tcpMap.put(20, 1);
-        tcpMap.put(21, 1);
-        tcpMap.put(22, 3);
-        tcpMap.put(23, 2);
-        tcpMap.put(25, 8);
-        tcpMap.put(26, 8);
-        tcpMap.put(53, 3);
-        tcpMap.put(80, 10);
-        tcpMap.put(110, 6);
-        tcpMap.put(143, 6);
-        tcpMap.put(443, 10);
-        tcpMap.put(993, 6);
-        tcpMap.put(1723, 7);
-
-        Map<Integer, Integer> udpMap = new HashMap<>();
-        udpMap.put(53, 10);
-        udpMap.put(500, 7);
-        udpMap.put(5060, 10);
-        udpMap.put(4500, 4);
-
-        sPortScores.put(IPPROTO_TCP, tcpMap);
-        sPortScores.put(IPPROTO_UDP, udpMap);
-    }
-
-
-    public PasspointMatchInfo(PasspointMatch passpointMatch,
-                              ScanDetail scanDetail, HomeSP homeSP) {
-        mPasspointMatch = passpointMatch;
-        mScanDetail = scanDetail;
-        mHomeSP = homeSP;
-
-        int score;
-        if (passpointMatch == PasspointMatch.HomeProvider) {
-            score = 100;
-        }
-        else if (passpointMatch == PasspointMatch.RoamingProvider) {
-            score = 0;
-        }
-        else {
-            score = -1000;  // Don't expect to see anything not home or roaming.
-        }
-
-        if (getNetworkDetail().getHSRelease() != null) {
-            score += getNetworkDetail().getHSRelease() != NetworkDetail.HSRelease.Unknown ? 50 : 0;
-        }
-
-        if (getNetworkDetail().hasInterworking()) {
-            score += getNetworkDetail().isInternet() ? 20 : -20;
-        }
-
-        score += (Math.max(200-getNetworkDetail().getStationCount(), 0) *
-                (255-getNetworkDetail().getChannelUtilization()) *
-                getNetworkDetail().getCapacity()) >>> 26;
-                // Gives a value of 23 max capped at 200 stations and max cap 31250
-
-        if (getNetworkDetail().hasInterworking()) {
-            score += sAntScores.get(getNetworkDetail().getAnt());
-        }
-
-        Map<ANQPElementType, ANQPElement> anqp = getNetworkDetail().getANQPElements();
-
-        if (anqp != null) {
-            HSWanMetricsElement wm = (HSWanMetricsElement) anqp.get(ANQPElementType.HSWANMetrics);
-
-            if (wm != null) {
-                if (wm.getStatus() != HSWanMetricsElement.LinkStatus.Up || wm.isCapped()) {
-                    score -= 1000;
-                } else {
-                    long scaledSpeed =
-                            wm.getDlSpeed() * (255 - wm.getDlLoad()) * 8 +
-                                    wm.getUlSpeed() * (255 - wm.getUlLoad()) * 2;
-                    score += Math.min(scaledSpeed, 255000000L) >>> 23;
-                    // Max value is 30 capped at 100Mb/s
-                }
-            }
-
-            IPAddressTypeAvailabilityElement ipa =
-                    (IPAddressTypeAvailabilityElement) anqp.get(ANQPElementType.ANQPIPAddrAvailability);
-
-            if (ipa != null) {
-                Integer as14 = sIP4Scores.get(ipa.getV4Availability());
-                Integer as16 = sIP6Scores.get(ipa.getV6Availability());
-                as14 = as14 != null ? as14 : 1;
-                as16 = as16 != null ? as16 : 1;
-                // Is IPv4 twice as important as IPv6???
-                score += as14 * 2 + as16;
-            }
-
-            HSConnectionCapabilityElement cce =
-                    (HSConnectionCapabilityElement) anqp.get(ANQPElementType.HSConnCapability);
-
-            if (cce != null) {
-                score = Math.min(Math.max(protoScore(cce) >> 3, -10), 10);
-            }
-        }
-
-        mScore = score;
-    }
-
-    public PasspointMatch getPasspointMatch() {
-        return mPasspointMatch;
-    }
-
-    public ScanDetail getScanDetail() {
-        return mScanDetail; 
-    }
-
-    public NetworkDetail getNetworkDetail() {
-        return mScanDetail.getNetworkDetail(); 
-    }
-
-
-    public HomeSP getHomeSP() {
-        return mHomeSP;
-    }
-
-    public int getScore() {
-        return mScore;
-    }
-
-    @Override
-    public int compareTo(PasspointMatchInfo that) {
-        return getScore() - that.getScore();
-    }
-
-    private static int protoScore(HSConnectionCapabilityElement cce) {
-        int score = 0;
-        for (HSConnectionCapabilityElement.ProtocolTuple tuple : cce.getStatusList()) {
-            int sign = tuple.getStatus() == HSConnectionCapabilityElement.ProtoStatus.Open ?
-                    1 : -1;
-
-            int elementScore = 1;
-            if (tuple.getProtocol() == IPPROTO_ICMP) {
-                elementScore = 1;
-            }
-            else if (tuple.getProtocol() == IPPROTO_ESP) {
-                elementScore = 5;
-            }
-            else {
-                Map<Integer, Integer> protoMap = sPortScores.get(tuple.getProtocol());
-                if (protoMap != null) {
-                    Integer portScore = protoMap.get(tuple.getPort());
-                    elementScore = portScore != null ? portScore : 0;
-                }
-            }
-            score += elementScore * sign;
-        }
-        return score;
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (this == thatObject) {
-            return true;
-        }
-        if (thatObject == null || getClass() != thatObject.getClass()) {
-            return false;
-        }
-
-        PasspointMatchInfo that = (PasspointMatchInfo)thatObject;
-
-        return getNetworkDetail().equals(that.getNetworkDetail()) &&
-                getHomeSP().equals(that.getHomeSP()) &&
-                getPasspointMatch().equals(that.getPasspointMatch());
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mPasspointMatch != null ? mPasspointMatch.hashCode() : 0;
-        result = 31 * result + getNetworkDetail().hashCode();
-        result = 31 * result + (mHomeSP != null ? mHomeSP.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "PasspointMatchInfo{" +
-                ", mPasspointMatch=" + mPasspointMatch +
-                ", mNetworkInfo=" + getNetworkDetail().getSSID() +
-                ", mHomeSP=" + mHomeSP.getFQDN() +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
new file mode 100644
index 0000000..132a2f2
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
@@ -0,0 +1,189 @@
+/*
+ * 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.hotspot2;
+
+import android.net.wifi.WifiConfiguration;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Pair;
+
+import com.android.server.wifi.NetworkUpdateResult;
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.WifiConfigManager;
+import com.android.server.wifi.WifiNetworkSelector;
+import com.android.server.wifi.util.ScanResultUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class is the WifiNetworkSelector.NetworkEvaluator implementation for
+ * Passpoint networks.
+ */
+public class PasspointNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
+    private static final String NAME = "PasspointNetworkEvaluator";
+
+    private final PasspointManager mPasspointManager;
+    private final WifiConfigManager mWifiConfigManager;
+    private final LocalLog mLocalLog;
+
+    /**
+     * Contained information for a Passpoint network candidate.
+     */
+    private class PasspointNetworkCandidate {
+        PasspointNetworkCandidate(PasspointProvider provider, PasspointMatch matchStatus,
+                ScanDetail scanDetail) {
+            mProvider = provider;
+            mMatchStatus = matchStatus;
+            mScanDetail = scanDetail;
+        }
+        PasspointProvider mProvider;
+        PasspointMatch mMatchStatus;
+        ScanDetail mScanDetail;
+    }
+
+    public PasspointNetworkEvaluator(PasspointManager passpointManager,
+            WifiConfigManager wifiConfigManager, LocalLog localLog) {
+        mPasspointManager = passpointManager;
+        mWifiConfigManager = wifiConfigManager;
+        mLocalLog = localLog;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public void update(List<ScanDetail> scanDetails) {}
+
+    @Override
+    public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
+                    WifiConfiguration currentNetwork, String currentBssid,
+                    boolean connected, boolean untrustedNetworkAllowed,
+                    List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
+        // Sweep the ANQP cache to remove any expired ANQP entries.
+        mPasspointManager.sweepCache();
+
+        // Go through each ScanDetail and find the best provider for each ScanDetail.
+        List<PasspointNetworkCandidate> candidateList = new ArrayList<>();
+        for (ScanDetail scanDetail : scanDetails) {
+            // Skip non-Passpoint APs.
+            if (!scanDetail.getNetworkDetail().isInterworking()) {
+                continue;
+            }
+
+            // Find the best provider for this ScanDetail.
+            Pair<PasspointProvider, PasspointMatch> bestProvider =
+                    mPasspointManager.matchProvider(scanDetail.getScanResult());
+            if (bestProvider != null) {
+                candidateList.add(new PasspointNetworkCandidate(
+                        bestProvider.first, bestProvider.second, scanDetail));
+            }
+        }
+
+        // Done if no candidate is found.
+        if (candidateList.isEmpty()) {
+            localLog("No suitable Passpoint network found");
+            return null;
+        }
+
+        // Find the best Passpoint network among all candidates.
+        PasspointNetworkCandidate bestNetwork =
+                findBestNetwork(candidateList, currentNetwork == null ? null : currentNetwork.SSID);
+
+        // Return the configuration for the current connected network if it is the best network.
+        if (currentNetwork != null && TextUtils.equals(currentNetwork.SSID,
+                ScanResultUtil.createQuotedSSID(bestNetwork.mScanDetail.getSSID()))) {
+            localLog("Staying with current Passpoint network " + currentNetwork.SSID);
+            connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, currentNetwork));
+            return currentNetwork;
+        }
+
+        WifiConfiguration config = createWifiConfigForProvider(bestNetwork);
+        connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, config));
+        localLog("Passpoint network to connect to: " + config.SSID);
+        return config;
+    }
+
+    /**
+     * Create and return a WifiConfiguration for the given ScanDetail and PasspointProvider.
+     * The newly created WifiConfiguration will also be added to WifiConfigManager.
+     *
+     * @param networkInfo Contained information for the Passpoint network to connect to
+     * @return {@link WifiConfiguration}
+     */
+    private WifiConfiguration createWifiConfigForProvider(PasspointNetworkCandidate networkInfo) {
+        WifiConfiguration config = networkInfo.mProvider.getWifiConfig();
+        config.SSID = ScanResultUtil.createQuotedSSID(networkInfo.mScanDetail.getSSID());
+        if (networkInfo.mMatchStatus == PasspointMatch.HomeProvider) {
+            config.isHomeProviderNetwork = true;
+        }
+
+        // Add the newly created WifiConfiguration to WifiConfigManager.
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
+        if (!result.isSuccess()) {
+            localLog("Failed to add passpoint network");
+            return null;
+        }
+        mWifiConfigManager.enableNetwork(result.getNetworkId(), false, Process.WIFI_UID);
+        mWifiConfigManager.setNetworkCandidateScanResult(result.getNetworkId(),
+                networkInfo.mScanDetail.getScanResult(), 0);
+        mWifiConfigManager.updateScanDetailForNetwork(
+                result.getNetworkId(), networkInfo.mScanDetail);
+        return mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+    }
+
+    /**
+     * Given a list of Passpoint networks (with both provider and scan info), find and return
+     * the one with highest score.  The score is calculated using
+     * {@link PasspointNetworkScore#calculateScore}.
+     *
+     * @param networkList List of Passpoint networks
+     * @param currentNetworkSsid The SSID of the currently connected network, null if not connected
+     * @return {@link PasspointNetworkCandidate}
+     */
+    private PasspointNetworkCandidate findBestNetwork(
+            List<PasspointNetworkCandidate> networkList, String currentNetworkSsid) {
+        PasspointNetworkCandidate bestCandidate = null;
+        int bestScore = Integer.MIN_VALUE;
+        for (PasspointNetworkCandidate candidate : networkList) {
+            ScanDetail scanDetail = candidate.mScanDetail;
+            PasspointMatch match = candidate.mMatchStatus;
+
+            boolean isActiveNetwork = TextUtils.equals(currentNetworkSsid,
+                    ScanResultUtil.createQuotedSSID(scanDetail.getSSID()));
+            int score = PasspointNetworkScore.calculateScore(match == PasspointMatch.HomeProvider,
+                    scanDetail, mPasspointManager.getANQPElements(scanDetail.getScanResult()),
+                    isActiveNetwork);
+
+            if (score > bestScore) {
+                bestCandidate = candidate;
+                bestScore = score;
+            }
+        }
+        localLog("Best Passpoint network " + bestCandidate.mScanDetail.getSSID() + " provided by "
+                + bestCandidate.mProvider.getConfig().getHomeSp().getFqdn());
+        return bestCandidate;
+    }
+
+    private void localLog(String log) {
+        mLocalLog.log(log);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkScore.java b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkScore.java
new file mode 100644
index 0000000..03f2e4c
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkScore.java
@@ -0,0 +1,185 @@
+/*
+ * 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.hotspot2;
+
+import android.net.RssiCurve;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
+import com.android.server.wifi.hotspot2.anqp.HSWanMetricsElement;
+import com.android.server.wifi.hotspot2.anqp.IPAddressTypeAvailabilityElement;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This is an utility class for calculating score for Passpoint networks.
+ */
+public class PasspointNetworkScore {
+    /**
+     * Award points for network that's a Passpoint home provider.
+     */
+    @VisibleForTesting
+    public static final int HOME_PROVIDER_AWARD = 100;
+
+    /**
+     * Award points for network that provides Internet access.
+     */
+    @VisibleForTesting
+    public static final int INTERNET_ACCESS_AWARD = 50;
+
+    /**
+     * Award points for public or private network.
+     */
+    @VisibleForTesting
+    public static final int PUBLIC_OR_PRIVATE_NETWORK_AWARDS = 4;
+
+    /**
+     * Award points for personal or emergency network.
+     */
+    @VisibleForTesting
+    public static final int PERSONAL_OR_EMERGENCY_NETWORK_AWARDS = 2;
+
+    /**
+     * Award points for network providing restricted or unknown IP address.
+     */
+    @VisibleForTesting
+    public static final int RESTRICTED_OR_UNKNOWN_IP_AWARDS = 1;
+
+    /**
+     * Award points for network providing unrestricted IP address.
+     */
+    @VisibleForTesting
+    public static final int UNRESTRICTED_IP_AWARDS = 2;
+
+    /**
+     * Penalty points for network with WAN port that's down or the load already reached the max.
+     */
+    @VisibleForTesting
+    public static final int WAN_PORT_DOWN_OR_CAPPED_PENALTY = 50;
+
+    // Award points for availability of IPv4 and IPv6 addresses.
+    private static final Map<Integer, Integer> IPV4_SCORES = new HashMap<>();
+    private static final Map<Integer, Integer> IPV6_SCORES = new HashMap<>();
+
+    // Award points based on access network type.
+    private static final Map<NetworkDetail.Ant, Integer> NETWORK_TYPE_SCORES = new HashMap<>();
+
+    /**
+     * Curve for calculating score for RSSI level.
+     */
+    @VisibleForTesting
+    public static final RssiCurve RSSI_SCORE = new RssiCurve(-80 /* start */, 20 /* bucketWidth */,
+            new byte[] {-10, 0, 10, 20, 30, 40} /* rssiBuckets */,
+            20 /* activeNetworkRssiBoost */);
+
+    static {
+        // These are all arbitrarily chosen scores, subject to tuning.
+
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.FreePublic, PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.ChargeablePublic,
+                PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.PrivateWithGuest,
+                PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Private,
+                PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Personal, PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.EmergencyOnly,
+                PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Wildcard, 0);
+        NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.TestOrExperimental, 0);
+
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_NOT_AVAILABLE, 0);
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED,
+                RESTRICTED_OR_UNKNOWN_IP_AWARDS);
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED_AND_SINGLE_NAT,
+                RESTRICTED_OR_UNKNOWN_IP_AWARDS);
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PORT_RESTRICTED_AND_DOUBLE_NAT,
+                RESTRICTED_OR_UNKNOWN_IP_AWARDS);
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_UNKNOWN,
+                RESTRICTED_OR_UNKNOWN_IP_AWARDS);
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_PUBLIC, UNRESTRICTED_IP_AWARDS);
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_SINGLE_NAT, UNRESTRICTED_IP_AWARDS);
+        IPV4_SCORES.put(IPAddressTypeAvailabilityElement.IPV4_DOUBLE_NAT, UNRESTRICTED_IP_AWARDS);
+
+        IPV6_SCORES.put(IPAddressTypeAvailabilityElement.IPV6_NOT_AVAILABLE, 0);
+        IPV6_SCORES.put(IPAddressTypeAvailabilityElement.IPV6_UNKNOWN,
+                RESTRICTED_OR_UNKNOWN_IP_AWARDS);
+        IPV6_SCORES.put(IPAddressTypeAvailabilityElement.IPV6_AVAILABLE,
+                UNRESTRICTED_IP_AWARDS);
+    }
+
+
+    /**
+     * Calculate and return a score associated with the given Passpoint network.
+     * The score is calculated with the following preferences:
+     * - Prefer home provider
+     * - Prefer network that provides Internet access
+     * - Prefer network with active WAN port with available load
+     * - Prefer network that provides unrestricted IP address
+     * - Prefer currently active network
+     * - Prefer AP with higher RSSI
+     *
+     * This can be expanded for additional preference in the future (e.g. AP station count, link
+     * speed, and etc).
+     *
+     * @param isHomeProvider Flag indicating home provider
+     * @param scanDetail The ScanDetail associated with the AP
+     * @param isActiveNetwork Flag indicating current active network
+     * @return integer score
+     */
+    public static int calculateScore(boolean isHomeProvider, ScanDetail scanDetail,
+            Map<ANQPElementType, ANQPElement> anqpElements, boolean isActiveNetwork) {
+        NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+        int score = 0;
+        if (isHomeProvider) {
+            score += HOME_PROVIDER_AWARD;
+        }
+
+        // Adjust score based on Internet accessibility.
+        score += (networkDetail.isInternet() ? 1 : -1) * INTERNET_ACCESS_AWARD;
+
+        // Adjust score based on the network type.
+        score += NETWORK_TYPE_SCORES.get(networkDetail.getAnt());
+
+        if (anqpElements != null) {
+            HSWanMetricsElement wm =
+                    (HSWanMetricsElement) anqpElements.get(ANQPElementType.HSWANMetrics);
+            if (wm != null) {
+                if (wm.getStatus() != HSWanMetricsElement.LINK_STATUS_UP || wm.isCapped()) {
+                    score -= WAN_PORT_DOWN_OR_CAPPED_PENALTY;
+                }
+            }
+
+            IPAddressTypeAvailabilityElement ipa = (IPAddressTypeAvailabilityElement)
+                    anqpElements.get(ANQPElementType.ANQPIPAddrAvailability);
+
+            if (ipa != null) {
+                Integer v4Score = IPV4_SCORES.get(ipa.getV4Availability());
+                Integer v6Score = IPV6_SCORES.get(ipa.getV6Availability());
+                v4Score = v4Score != null ? v4Score : 0;
+                v6Score = v6Score != null ? v6Score : 0;
+                score += (v4Score + v6Score);
+            }
+        }
+
+        score += RSSI_SCORE.lookupScore(scanDetail.getScanResult().level, isActiveNetwork);
+        return score;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
new file mode 100644
index 0000000..c41c49a
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -0,0 +1,98 @@
+/*
+ * 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.hotspot2;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiNative;
+
+/**
+ * Factory class for creating Passpoint related objects. Useful for mocking object creations
+ * in the unit tests.
+ */
+public class PasspointObjectFactory{
+    /**
+     * Create a PasspointEventHandler instance.
+     *
+     * @param wifiNative Instance of {@link WifiNative}
+     * @param callbacks Instance of {@link PasspointEventHandler.Callbacks}
+     * @return {@link PasspointEventHandler}
+     */
+    public PasspointEventHandler makePasspointEventHandler(WifiNative wifiNative,
+            PasspointEventHandler.Callbacks callbacks) {
+        return new PasspointEventHandler(wifiNative, callbacks);
+    }
+
+    /**
+     * Create a PasspointProvider instance.
+     *
+     * @param keyStore Instance of {@link WifiKeyStore}
+     * @param config Configuration for the provider
+     * @param providerId Unique identifier for the provider
+     * @return {@link PasspointProvider}
+     */
+    public PasspointProvider makePasspointProvider(PasspointConfiguration config,
+            WifiKeyStore keyStore, SIMAccessor simAccessor, long providerId, int creatorUid) {
+        return new PasspointProvider(config, keyStore, simAccessor, providerId, creatorUid);
+    }
+
+    /**
+     * Create a {@link PasspointConfigStoreData} instance.
+     *
+     * @param keyStore Instance of {@link WifiKeyStore}
+     * @param simAccessor Instance of {@link SIMAccessor}
+     * @param dataSource Passpoint configuration data source
+     * @return {@link PasspointConfigStoreData}
+     */
+    public PasspointConfigStoreData makePasspointConfigStoreData(WifiKeyStore keyStore,
+            SIMAccessor simAccessor, PasspointConfigStoreData.DataSource dataSource) {
+        return new PasspointConfigStoreData(keyStore, simAccessor, dataSource);
+    }
+
+    /**
+     * Create a AnqpCache instance.
+     *
+     * @param clock Instance of {@link Clock}
+     * @return {@link AnqpCache}
+     */
+    public AnqpCache makeAnqpCache(Clock clock) {
+        return new AnqpCache(clock);
+    }
+
+    /**
+     * Create an instance of {@link ANQPRequestManager}.
+     *
+     * @param handler Instance of {@link PasspointEventHandler}
+     * @param clock Instance of {@link Clock}
+     * @return {@link ANQPRequestManager}
+     */
+    public ANQPRequestManager makeANQPRequestManager(PasspointEventHandler handler, Clock clock) {
+        return new ANQPRequestManager(handler, clock);
+    }
+
+    /**
+     * Create an instance of {@link CertificateVerifier}.
+     *
+     * @return {@link CertificateVerifier}
+     */
+    public CertificateVerifier makeCertificateVerifier() {
+        return new CertificateVerifier();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
new file mode 100644
index 0000000..33867bb
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
@@ -0,0 +1,620 @@
+/*
+ * 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.hotspot2;
+
+import android.net.wifi.EAPConstants;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.Credential.SimCredential;
+import android.net.wifi.hotspot2.pps.Credential.UserCredential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.security.Credentials;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
+import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
+import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
+import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
+import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
+import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
+import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Abstraction for Passpoint service provider.  This class contains the both static
+ * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
+ */
+public class PasspointProvider {
+    private static final String TAG = "PasspointProvider";
+
+    /**
+     * Used as part of alias string for certificates and keys.  The alias string is in the format
+     * of: [KEY_TYPE]_HS2_[ProviderID]
+     * For example: "CACERT_HS2_0", "USRCERT_HS2_0", "USRPKEY_HS2_0"
+     */
+    private static final String ALIAS_HS_TYPE = "HS2_";
+
+    private final PasspointConfiguration mConfig;
+    private final WifiKeyStore mKeyStore;
+
+    /**
+     * Aliases for the private keys and certificates installed in the keystore.  Each alias
+     * is a suffix of the actual certificate or key name installed in the keystore.  The
+     * certificate or key name in the keystore is consist of |Type|_|alias|.
+     * This will be consistent with the usage of the term "alias" in {@link WifiEnterpriseConfig}.
+     */
+    private String mCaCertificateAlias;
+    private String mClientPrivateKeyAlias;
+    private String mClientCertificateAlias;
+
+    private final long mProviderId;
+    private final int mCreatorUid;
+
+    private final IMSIParameter mImsiParameter;
+    private final List<String> mMatchingSIMImsiList;
+
+    private final int mEAPMethodID;
+    private final AuthParam mAuthParam;
+
+    public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
+            SIMAccessor simAccessor, long providerId, int creatorUid) {
+        this(config, keyStore, simAccessor, providerId, creatorUid, null, null, null);
+    }
+
+    public PasspointProvider(PasspointConfiguration config, WifiKeyStore keyStore,
+            SIMAccessor simAccessor, long providerId, int creatorUid, String caCertificateAlias,
+            String clientCertificateAlias, String clientPrivateKeyAlias) {
+        // Maintain a copy of the configuration to avoid it being updated by others.
+        mConfig = new PasspointConfiguration(config);
+        mKeyStore = keyStore;
+        mProviderId = providerId;
+        mCreatorUid = creatorUid;
+        mCaCertificateAlias = caCertificateAlias;
+        mClientCertificateAlias = clientCertificateAlias;
+        mClientPrivateKeyAlias = clientPrivateKeyAlias;
+
+        // Setup EAP method and authentication parameter based on the credential.
+        if (mConfig.getCredential().getUserCredential() != null) {
+            mEAPMethodID = EAPConstants.EAP_TTLS;
+            mAuthParam = new NonEAPInnerAuth(NonEAPInnerAuth.getAuthTypeID(
+                    mConfig.getCredential().getUserCredential().getNonEapInnerMethod()));
+            mImsiParameter = null;
+            mMatchingSIMImsiList = null;
+        } else if (mConfig.getCredential().getCertCredential() != null) {
+            mEAPMethodID = EAPConstants.EAP_TLS;
+            mAuthParam = null;
+            mImsiParameter = null;
+            mMatchingSIMImsiList = null;
+        } else {
+            mEAPMethodID = mConfig.getCredential().getSimCredential().getEapType();
+            mAuthParam = null;
+            mImsiParameter = IMSIParameter.build(
+                    mConfig.getCredential().getSimCredential().getImsi());
+            mMatchingSIMImsiList = simAccessor.getMatchingImsis(mImsiParameter);
+        }
+    }
+
+    public PasspointConfiguration getConfig() {
+        // Return a copy of the configuration to avoid it being updated by others.
+        return new PasspointConfiguration(mConfig);
+    }
+
+    public String getCaCertificateAlias() {
+        return mCaCertificateAlias;
+    }
+
+    public String getClientPrivateKeyAlias() {
+        return mClientPrivateKeyAlias;
+    }
+
+    public String getClientCertificateAlias() {
+        return mClientCertificateAlias;
+    }
+
+    public long getProviderId() {
+        return mProviderId;
+    }
+
+    public int getCreatorUid() {
+        return mCreatorUid;
+    }
+
+    /**
+     * Install certificates and key based on current configuration.
+     * Note: the certificates and keys in the configuration will get cleared once
+     * they're installed in the keystore.
+     *
+     * @return true on success
+     */
+    public boolean installCertsAndKeys() {
+        // Install CA certificate.
+        if (mConfig.getCredential().getCaCertificate() != null) {
+            String certName = Credentials.CA_CERTIFICATE + ALIAS_HS_TYPE + mProviderId;
+            if (!mKeyStore.putCertInKeyStore(certName,
+                    mConfig.getCredential().getCaCertificate())) {
+                Log.e(TAG, "Failed to install CA Certificate");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            mCaCertificateAlias = ALIAS_HS_TYPE + mProviderId;
+        }
+
+        // Install the client private key.
+        if (mConfig.getCredential().getClientPrivateKey() != null) {
+            String keyName = Credentials.USER_PRIVATE_KEY + ALIAS_HS_TYPE + mProviderId;
+            if (!mKeyStore.putKeyInKeyStore(keyName,
+                    mConfig.getCredential().getClientPrivateKey())) {
+                Log.e(TAG, "Failed to install client private key");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            mClientPrivateKeyAlias = ALIAS_HS_TYPE + mProviderId;
+        }
+
+        // Install the client certificate.
+        if (mConfig.getCredential().getClientCertificateChain() != null) {
+            X509Certificate clientCert = getClientCertificate(
+                    mConfig.getCredential().getClientCertificateChain(),
+                    mConfig.getCredential().getCertCredential().getCertSha256Fingerprint());
+            if (clientCert == null) {
+                Log.e(TAG, "Failed to locate client certificate");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            String certName = Credentials.USER_CERTIFICATE + ALIAS_HS_TYPE + mProviderId;
+            if (!mKeyStore.putCertInKeyStore(certName, clientCert)) {
+                Log.e(TAG, "Failed to install client certificate");
+                uninstallCertsAndKeys();
+                return false;
+            }
+            mClientCertificateAlias = ALIAS_HS_TYPE + mProviderId;
+        }
+
+        // Clear the keys and certificates in the configuration.
+        mConfig.getCredential().setCaCertificate(null);
+        mConfig.getCredential().setClientPrivateKey(null);
+        mConfig.getCredential().setClientCertificateChain(null);
+        return true;
+    }
+
+    /**
+     * Remove any installed certificates and key.
+     */
+    public void uninstallCertsAndKeys() {
+        if (mCaCertificateAlias != null) {
+            if (!mKeyStore.removeEntryFromKeyStore(
+                    Credentials.CA_CERTIFICATE + mCaCertificateAlias)) {
+                Log.e(TAG, "Failed to remove entry: " + mCaCertificateAlias);
+            }
+            mCaCertificateAlias = null;
+        }
+        if (mClientPrivateKeyAlias != null) {
+            if (!mKeyStore.removeEntryFromKeyStore(
+                    Credentials.USER_PRIVATE_KEY + mClientPrivateKeyAlias)) {
+                Log.e(TAG, "Failed to remove entry: " + mClientPrivateKeyAlias);
+            }
+            mClientPrivateKeyAlias = null;
+        }
+        if (mClientCertificateAlias != null) {
+            if (!mKeyStore.removeEntryFromKeyStore(
+                    Credentials.USER_CERTIFICATE + mClientCertificateAlias)) {
+                Log.e(TAG, "Failed to remove entry: " + mClientCertificateAlias);
+            }
+            mClientCertificateAlias = null;
+        }
+    }
+
+    /**
+     * Return the matching status with the given AP, based on the ANQP elements from the AP.
+     *
+     * @param anqpElements ANQP elements from the AP
+     * @return {@link PasspointMatch}
+     */
+    public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements) {
+        PasspointMatch providerMatch = matchProvider(anqpElements);
+
+        // Perform authentication match against the NAI Realm.
+        int authMatch = ANQPMatcher.matchNAIRealm(
+                (NAIRealmElement) anqpElements.get(ANQPElementType.ANQPNAIRealm),
+                mConfig.getCredential().getRealm(), mEAPMethodID, mAuthParam);
+
+        // Auth mismatch, demote provider match.
+        if (authMatch == AuthMatch.NONE) {
+            return PasspointMatch.None;
+        }
+
+        // No realm match, return provider match as is.
+        if ((authMatch & AuthMatch.REALM) == 0) {
+            return providerMatch;
+        }
+
+        // Realm match, promote provider match to roaming if no other provider match is found.
+        return providerMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider
+                : providerMatch;
+    }
+
+    /**
+     * Generate a WifiConfiguration based on the provider's configuration.  The generated
+     * WifiConfiguration will include all the necessary credentials for network connection except
+     * the SSID, which should be added by the caller when the config is being used for network
+     * connection.
+     *
+     * @return {@link WifiConfiguration}
+     */
+    public WifiConfiguration getWifiConfig() {
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = mConfig.getHomeSp().getFqdn();
+        if (mConfig.getHomeSp().getRoamingConsortiumOis() != null) {
+            wifiConfig.roamingConsortiumIds = Arrays.copyOf(
+                    mConfig.getHomeSp().getRoamingConsortiumOis(),
+                    mConfig.getHomeSp().getRoamingConsortiumOis().length);
+        }
+        wifiConfig.providerFriendlyName = mConfig.getHomeSp().getFriendlyName();
+        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        enterpriseConfig.setRealm(mConfig.getCredential().getRealm());
+        enterpriseConfig.setDomainSuffixMatch(mConfig.getHomeSp().getFqdn());
+        if (mConfig.getCredential().getUserCredential() != null) {
+            buildEnterpriseConfigForUserCredential(enterpriseConfig,
+                    mConfig.getCredential().getUserCredential());
+            setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
+        } else if (mConfig.getCredential().getCertCredential() != null) {
+            buildEnterpriseConfigForCertCredential(enterpriseConfig);
+            setAnonymousIdentityToNaiRealm(enterpriseConfig, mConfig.getCredential().getRealm());
+        } else {
+            buildEnterpriseConfigForSimCredential(enterpriseConfig,
+                    mConfig.getCredential().getSimCredential());
+        }
+        wifiConfig.enterpriseConfig = enterpriseConfig;
+        return wifiConfig;
+    }
+
+    /**
+     * Convert a legacy {@link WifiConfiguration} representation of a Passpoint configuration to
+     * a {@link PasspointConfiguration}.  This is used for migrating legacy Passpoint
+     * configuration (release N and older).
+     *
+     * @param wifiConfig The {@link WifiConfiguration} to convert
+     * @return {@link PasspointConfiguration}
+     */
+    public static PasspointConfiguration convertFromWifiConfig(WifiConfiguration wifiConfig) {
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+
+        // Setup HomeSP.
+        HomeSp homeSp = new HomeSp();
+        if (TextUtils.isEmpty(wifiConfig.FQDN)) {
+            Log.e(TAG, "Missing FQDN");
+            return null;
+        }
+        homeSp.setFqdn(wifiConfig.FQDN);
+        homeSp.setFriendlyName(wifiConfig.providerFriendlyName);
+        if (wifiConfig.roamingConsortiumIds != null) {
+            homeSp.setRoamingConsortiumOis(Arrays.copyOf(
+                    wifiConfig.roamingConsortiumIds, wifiConfig.roamingConsortiumIds.length));
+        }
+        passpointConfig.setHomeSp(homeSp);
+
+        // Setup Credential.
+        Credential credential = new Credential();
+        credential.setRealm(wifiConfig.enterpriseConfig.getRealm());
+        switch (wifiConfig.enterpriseConfig.getEapMethod()) {
+            case WifiEnterpriseConfig.Eap.TTLS:
+                credential.setUserCredential(buildUserCredentialFromEnterpriseConfig(
+                        wifiConfig.enterpriseConfig));
+                break;
+            case WifiEnterpriseConfig.Eap.TLS:
+                Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+                certCred.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
+                credential.setCertCredential(certCred);
+                break;
+            case WifiEnterpriseConfig.Eap.SIM:
+                credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
+                        EAPConstants.EAP_SIM, wifiConfig.enterpriseConfig));
+                break;
+            case WifiEnterpriseConfig.Eap.AKA:
+                credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
+                        EAPConstants.EAP_AKA, wifiConfig.enterpriseConfig));
+                break;
+            case WifiEnterpriseConfig.Eap.AKA_PRIME:
+                credential.setSimCredential(buildSimCredentialFromEnterpriseConfig(
+                        EAPConstants.EAP_AKA_PRIME, wifiConfig.enterpriseConfig));
+                break;
+            default:
+                Log.e(TAG, "Unsupport EAP method: " + wifiConfig.enterpriseConfig.getEapMethod());
+                return null;
+        }
+        if (credential.getUserCredential() == null && credential.getCertCredential() == null
+                && credential.getSimCredential() == null) {
+            Log.e(TAG, "Missing credential");
+            return null;
+        }
+        passpointConfig.setCredential(credential);
+
+        return passpointConfig;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof PasspointProvider)) {
+            return false;
+        }
+        PasspointProvider that = (PasspointProvider) thatObject;
+        return mProviderId == that.mProviderId
+                && TextUtils.equals(mCaCertificateAlias, that.mCaCertificateAlias)
+                && TextUtils.equals(mClientCertificateAlias, that.mClientCertificateAlias)
+                && TextUtils.equals(mClientPrivateKeyAlias, that.mClientPrivateKeyAlias)
+                && (mConfig == null ? that.mConfig == null : mConfig.equals(that.mConfig));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mProviderId, mCaCertificateAlias, mClientCertificateAlias,
+                mClientPrivateKeyAlias, mConfig);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("ProviderId: ").append(mProviderId).append("\n");
+        builder.append("CreatorUID: ").append(mCreatorUid).append("\n");
+        builder.append("Configuration Begin ---\n");
+        builder.append(mConfig);
+        builder.append("Configuration End ---\n");
+        return builder.toString();
+    }
+
+    /**
+     * Retrieve the client certificate from the certificates chain.  The certificate
+     * with the matching SHA256 digest is the client certificate.
+     *
+     * @param certChain The client certificates chain
+     * @param expectedSha256Fingerprint The expected SHA256 digest of the client certificate
+     * @return {@link java.security.cert.X509Certificate}
+     */
+    private static X509Certificate getClientCertificate(X509Certificate[] certChain,
+            byte[] expectedSha256Fingerprint) {
+        if (certChain == null) {
+            return null;
+        }
+        try {
+            MessageDigest digester = MessageDigest.getInstance("SHA-256");
+            for (X509Certificate certificate : certChain) {
+                digester.reset();
+                byte[] fingerprint = digester.digest(certificate.getEncoded());
+                if (Arrays.equals(expectedSha256Fingerprint, fingerprint)) {
+                    return certificate;
+                }
+            }
+        } catch (CertificateEncodingException | NoSuchAlgorithmException e) {
+            return null;
+        }
+
+        return null;
+    }
+
+    /**
+     * Perform a provider match based on the given ANQP elements.
+     *
+     * @param anqpElements List of ANQP elements
+     * @return {@link PasspointMatch}
+     */
+    private PasspointMatch matchProvider(Map<ANQPElementType, ANQPElement> anqpElements) {
+        // Domain name matching.
+        if (ANQPMatcher.matchDomainName(
+                (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName),
+                mConfig.getHomeSp().getFqdn(), mImsiParameter, mMatchingSIMImsiList)) {
+            return PasspointMatch.HomeProvider;
+        }
+
+        // Roaming Consortium OI matching.
+        if (ANQPMatcher.matchRoamingConsortium(
+                (RoamingConsortiumElement) anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
+                mConfig.getHomeSp().getRoamingConsortiumOis())) {
+            return PasspointMatch.RoamingProvider;
+        }
+
+        // 3GPP Network matching.
+        if (ANQPMatcher.matchThreeGPPNetwork(
+                (ThreeGPPNetworkElement) anqpElements.get(ANQPElementType.ANQP3GPPNetwork),
+                mImsiParameter, mMatchingSIMImsiList)) {
+            return PasspointMatch.RoamingProvider;
+        }
+        return PasspointMatch.None;
+    }
+
+    /**
+     * Fill in WifiEnterpriseConfig with information from an user credential.
+     *
+     * @param config Instance of {@link WifiEnterpriseConfig}
+     * @param credential Instance of {@link UserCredential}
+     */
+    private void buildEnterpriseConfigForUserCredential(WifiEnterpriseConfig config,
+            Credential.UserCredential credential) {
+        byte[] pwOctets = Base64.decode(credential.getPassword(), Base64.DEFAULT);
+        String decodedPassword = new String(pwOctets, StandardCharsets.UTF_8);
+        config.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+        config.setIdentity(credential.getUsername());
+        config.setPassword(decodedPassword);
+        config.setCaCertificateAlias(mCaCertificateAlias);
+        int phase2Method = WifiEnterpriseConfig.Phase2.NONE;
+        switch (credential.getNonEapInnerMethod()) {
+            case Credential.UserCredential.AUTH_METHOD_PAP:
+                phase2Method = WifiEnterpriseConfig.Phase2.PAP;
+                break;
+            case Credential.UserCredential.AUTH_METHOD_MSCHAP:
+                phase2Method = WifiEnterpriseConfig.Phase2.MSCHAP;
+                break;
+            case Credential.UserCredential.AUTH_METHOD_MSCHAPV2:
+                phase2Method = WifiEnterpriseConfig.Phase2.MSCHAPV2;
+                break;
+            default:
+                // Should never happen since this is already validated when the provider is
+                // added.
+                Log.wtf(TAG, "Unsupported Auth: " + credential.getNonEapInnerMethod());
+                break;
+        }
+        config.setPhase2Method(phase2Method);
+    }
+
+    /**
+     * Fill in WifiEnterpriseConfig with information from a certificate credential.
+     *
+     * @param config Instance of {@link WifiEnterpriseConfig}
+     */
+    private void buildEnterpriseConfigForCertCredential(WifiEnterpriseConfig config) {
+        config.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        config.setClientCertificateAlias(mClientCertificateAlias);
+        config.setCaCertificateAlias(mCaCertificateAlias);
+    }
+
+    /**
+     * Fill in WifiEnterpriseConfig with information from a SIM credential.
+     *
+     * @param config Instance of {@link WifiEnterpriseConfig}
+     * @param credential Instance of {@link SimCredential}
+     */
+    private void buildEnterpriseConfigForSimCredential(WifiEnterpriseConfig config,
+            Credential.SimCredential credential) {
+        int eapMethod = WifiEnterpriseConfig.Eap.NONE;
+        switch(credential.getEapType()) {
+            case EAPConstants.EAP_SIM:
+                eapMethod = WifiEnterpriseConfig.Eap.SIM;
+                break;
+            case EAPConstants.EAP_AKA:
+                eapMethod = WifiEnterpriseConfig.Eap.AKA;
+                break;
+            case EAPConstants.EAP_AKA_PRIME:
+                eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
+                break;
+            default:
+                // Should never happen since this is already validated when the provider is
+                // added.
+                Log.wtf(TAG, "Unsupported EAP Method: " + credential.getEapType());
+                break;
+        }
+        config.setEapMethod(eapMethod);
+        config.setPlmn(credential.getImsi());
+    }
+
+    private static void setAnonymousIdentityToNaiRealm(WifiEnterpriseConfig config, String realm) {
+        /**
+         * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so
+         * that this value will be sent to the EAP server as part of the EAP-Response/ Identity
+         * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity
+         * packet, and revert to using the (real) identity field for subsequent transactions that
+         * request an identity (e.g. in EAP-TTLS).
+         *
+         * This NAI realm value (the portion of the identity after the '@') is used to tell the
+         * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a
+         * placeholder that is not used--it is set to this value by convention. See Section 5.1 of
+         * RFC3748 for more details.
+         *
+         * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the
+         * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to
+         * identify the device.
+         */
+        config.setAnonymousIdentity("anonymous@" + realm);
+    }
+
+    /**
+     * Helper function for creating a
+     * {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} from the given
+     * {@link WifiEnterpriseConfig}
+     *
+     * @param config The enterprise configuration containing the credential
+     * @return {@link android.net.wifi.hotspot2.pps.Credential.UserCredential}
+     */
+    private static Credential.UserCredential buildUserCredentialFromEnterpriseConfig(
+            WifiEnterpriseConfig config) {
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setEapType(EAPConstants.EAP_TTLS);
+
+        if (TextUtils.isEmpty(config.getIdentity())) {
+            Log.e(TAG, "Missing username for user credential");
+            return null;
+        }
+        userCredential.setUsername(config.getIdentity());
+
+        if (TextUtils.isEmpty(config.getPassword())) {
+            Log.e(TAG, "Missing password for user credential");
+            return null;
+        }
+        String encodedPassword =
+                new String(Base64.encode(config.getPassword().getBytes(StandardCharsets.UTF_8),
+                        Base64.DEFAULT), StandardCharsets.UTF_8);
+        userCredential.setPassword(encodedPassword);
+
+        switch(config.getPhase2Method()) {
+            case WifiEnterpriseConfig.Phase2.PAP:
+                userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_PAP);
+                break;
+            case WifiEnterpriseConfig.Phase2.MSCHAP:
+                userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAP);
+                break;
+            case WifiEnterpriseConfig.Phase2.MSCHAPV2:
+                userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
+                break;
+            default:
+                Log.e(TAG, "Unsupported phase2 method for TTLS: " + config.getPhase2Method());
+                return null;
+        }
+        return userCredential;
+    }
+
+    /**
+     * Helper function for creating a
+     * {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} from the given
+     * {@link WifiEnterpriseConfig}
+     *
+     * @param eapType The EAP type of the SIM credential
+     * @param config The enterprise configuration containing the credential
+     * @return {@link android.net.wifi.hotspot2.pps.Credential.SimCredential}
+     */
+    private static Credential.SimCredential buildSimCredentialFromEnterpriseConfig(
+            int eapType, WifiEnterpriseConfig config) {
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        if (TextUtils.isEmpty(config.getPlmn())) {
+            Log.e(TAG, "Missing IMSI for SIM credential");
+            return null;
+        }
+        simCredential.setImsi(config.getPlmn());
+        simCredential.setEapType(eapType);
+        return simCredential;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointXmlUtils.java b/service/java/com/android/server/wifi/hotspot2/PasspointXmlUtils.java
new file mode 100644
index 0000000..de2b9c0
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointXmlUtils.java
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+
+import com.android.internal.util.XmlUtils;
+import com.android.server.wifi.util.XmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class for serialize and deserialize Passpoint related configurations to/from XML string.
+ */
+public class PasspointXmlUtils {
+    // XML section header tags.
+    private static final String XML_TAG_SECTION_HEADER_HOMESP = "HomeSP";
+    private static final String XML_TAG_SECTION_HEADER_CREDENTIAL = "Credential";
+    private static final String XML_TAG_SECTION_HEADER_USER_CREDENTIAL = "UserCredential";
+    private static final String XML_TAG_SECTION_HEADER_CERT_CREDENTIAL = "CertCredential";
+    private static final String XML_TAG_SECTION_HEADER_SIM_CREDENTIAL = "SimCredential";
+    private static final String XML_TAG_SECTION_HEADER_POLICY = "Policy";
+    private static final String XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST =
+            "RoamingPartnerList";
+    private static final String XML_TAG_SECTION_HEADER_ROAMING_PARTNER = "RoamingPartner";
+    private static final String XML_TAG_SECTION_HEADER_POLICY_UPDATE = "PolicyUpdate";
+    private static final String XML_TAG_SECTION_HEADER_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+    private static final String XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP =
+            "RequiredProtoPortMap";
+    private static final String XML_TAG_SECTION_HEADER_PROTO_PORT = "ProtoPort";
+
+    // XML value tags.
+    private static final String XML_TAG_FQDN = "FQDN";
+    private static final String XML_TAG_FRIENDLY_NAME = "FriendlyName";
+    private static final String XML_TAG_ICON_URL = "IconURL";
+    private static final String XML_TAG_HOME_NETWORK_IDS = "HomeNetworkIDs";
+    private static final String XML_TAG_MATCH_ALL_OIS = "MatchAllOIs";
+    private static final String XML_TAG_MATCH_ANY_OIS = "MatchAnyOIs";
+    private static final String XML_TAG_OTHER_HOME_PARTNERS = "OtherHomePartners";
+    private static final String XML_TAG_ROAMING_CONSORTIUM_OIS = "RoamingConsortiumOIs";
+    private static final String XML_TAG_CREATION_TIME = "CreationTime";
+    private static final String XML_TAG_EXPIRATION_TIME = "ExpirationTime";
+    private static final String XML_TAG_REALM = "Realm";
+    private static final String XML_TAG_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
+    private static final String XML_TAG_USERNAME = "Username";
+    private static final String XML_TAG_PASSWORD = "Password";
+    private static final String XML_TAG_MACHINE_MANAGED = "MachineManaged";
+    private static final String XML_TAG_SOFT_TOKEN_APP = "SoftTokenApp";
+    private static final String XML_TAG_ABLE_TO_SHARE = "AbleToShare";
+    private static final String XML_TAG_EAP_TYPE = "EAPType";
+    private static final String XML_TAG_NON_EAP_INNER_METHOD = "NonEAPInnerMethod";
+    private static final String XML_TAG_CERT_TYPE = "CertType";
+    private static final String XML_TAG_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
+    private static final String XML_TAG_IMSI = "IMSI";
+    private static final String XML_TAG_MIN_HOME_DOWNLINK_BANDWIDTH = "MinHomeDownlinkBandwidth";
+    private static final String XML_TAG_MIN_HOME_UPLINK_BANDWIDTH = "MinHomeUplinkBandwidth";
+    private static final String XML_TAG_MIN_ROAMING_DOWNLINK_BANDWIDTH =
+            "MinRoamingDownlinkBandwidth";
+    private static final String XML_TAG_MIN_ROAMING_UPLINK_BANDWIDTH =
+            "MinRoamingUplinkBandwidth";
+    private static final String XML_TAG_EXCLUDED_SSID_LIST = "ExcludedSSIDList";
+    private static final String XML_TAG_PROTO = "Proto";
+    private static final String XML_TAG_PORTS = "Ports";
+    private static final String XML_TAG_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+    private static final String XML_TAG_FQDN_EXACT_MATCH = "FQDNExactMatch";
+    private static final String XML_TAG_PRIORITY = "Priority";
+    private static final String XML_TAG_COUNTRIES = "Countries";
+    private static final String XML_TAG_UPDATE_INTERVAL = "UpdateInterval";
+    private static final String XML_TAG_UPDATE_METHOD = "UpdateMethod";
+    private static final String XML_TAG_RESTRICTION = "Restriction";
+    private static final String XML_TAG_SERVER_URI = "ServerURI";
+    private static final String XML_TAG_TRUST_ROOT_CERT_URL = "TrustRootCertURL";
+    private static final String XML_TAG_TRUST_ROOT_CERT_SHA256_FINGERPRINT =
+            "TrustRootCertSHA256Fingerprint";
+    private static final String XML_TAG_TRUST_ROOT_CERT_LIST = "TrustRootCertList";
+    private static final String XML_TAG_UPDATE_IDENTIFIER = "UpdateIdentifier";
+    private static final String XML_TAG_CREDENTIAL_PRIORITY = "CredentialPriority";
+    private static final String XML_TAG_SUBSCRIPTION_CREATION_TIME = "SubscriptionCreationTime";
+    private static final String XML_TAG_SUBSCRIPTION_EXPIRATION_TIME =
+            "SubscriptionExpirationTime";
+    private static final String XML_TAG_SUBSCRIPTION_TYPE = "SubscriptionType";
+    private static final String XML_TAG_USAGE_LIMIT_TIME_PERIOD = "UsageLimitTimePeriod";
+    private static final String XML_TAG_USAGE_LIMIT_START_TIME = "UsageLimitStartTime";
+    private static final String XML_TAG_USAGE_LIMIT_DATA_LIMIT = "UsageLimitDataLimit";
+    private static final String XML_TAG_USAGE_LIMIT_TIME_LIMIT = "UsageLimitTimeLimit";
+
+    /**
+     * Serialize a {@link PasspointConfiguration} to the output stream as a XML block.
+     *
+     * @param out The output stream to serialize to
+     * @param config The configuration to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    public static void serializePasspointConfiguration(XmlSerializer out,
+            PasspointConfiguration config) throws XmlPullParserException, IOException {
+        XmlUtil.writeNextValue(out, XML_TAG_UPDATE_IDENTIFIER, config.getUpdateIdentifier());
+        XmlUtil.writeNextValue(out, XML_TAG_CREDENTIAL_PRIORITY, config.getCredentialPriority());
+        XmlUtil.writeNextValue(out, XML_TAG_TRUST_ROOT_CERT_LIST, config.getTrustRootCertList());
+        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_CREATION_TIME,
+                config.getSubscriptionCreationTimeInMillis());
+        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_EXPIRATION_TIME,
+                config.getSubscriptionExpirationTimeInMillis());
+        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_TYPE, config.getSubscriptionType());
+        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_TIME_PERIOD,
+                config.getUsageLimitUsageTimePeriodInMinutes());
+        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_START_TIME,
+                config.getUsageLimitStartTimeInMillis());
+        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_DATA_LIMIT,
+                config.getUsageLimitDataLimit());
+        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_TIME_LIMIT,
+                config.getUsageLimitTimeLimitInMinutes());
+        serializeHomeSp(out, config.getHomeSp());
+        serializeCredential(out, config.getCredential());
+        serializePolicy(out, config.getPolicy());
+        serializeUpdateParameter(out, XML_TAG_SECTION_HEADER_SUBSCRIPTION_UPDATE,
+                config.getSubscriptionUpdate());
+    }
+
+    /**
+     * Deserialize a {@link PasspointConfiguration} from an input stream containing XML block.
+     *
+     * @param in The input stream to read from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link PasspointConfiguration}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    public static PasspointConfiguration deserializePasspointConfiguration(XmlPullParser in,
+            int outerTagDepth) throws XmlPullParserException, IOException {
+        PasspointConfiguration config = new PasspointConfiguration();
+        while (XmlUtils.nextElementWithin(in, outerTagDepth)) {
+            if (isValueElement(in)) {
+                // Value elements.
+                String[] name = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, name);
+                switch (name[0]) {
+                    case XML_TAG_UPDATE_IDENTIFIER:
+                        config.setUpdateIdentifier((int) value);
+                        break;
+                    case XML_TAG_CREDENTIAL_PRIORITY:
+                        config.setCredentialPriority((int) value);
+                        break;
+                    case XML_TAG_TRUST_ROOT_CERT_LIST:
+                        config.setTrustRootCertList((Map<String, byte[]>) value);
+                        break;
+                    case XML_TAG_SUBSCRIPTION_CREATION_TIME:
+                        config.setSubscriptionCreationTimeInMillis((long) value);
+                        break;
+                    case XML_TAG_SUBSCRIPTION_EXPIRATION_TIME:
+                        config.setSubscriptionExpirationTimeInMillis((long) value);
+                        break;
+                    case XML_TAG_SUBSCRIPTION_TYPE:
+                        config.setSubscriptionType((String) value);
+                        break;
+                    case XML_TAG_USAGE_LIMIT_TIME_PERIOD:
+                        config.setUsageLimitUsageTimePeriodInMinutes((long) value);
+                        break;
+                    case XML_TAG_USAGE_LIMIT_START_TIME:
+                        config.setUsageLimitStartTimeInMillis((long) value);
+                        break;
+                    case XML_TAG_USAGE_LIMIT_DATA_LIMIT:
+                        config.setUsageLimitDataLimit((long) value);
+                        break;
+                    case XML_TAG_USAGE_LIMIT_TIME_LIMIT:
+                        config.setUsageLimitTimeLimitInMinutes((long) value);
+                        break;
+                    default:
+                        throw new XmlPullParserException("Unknown value under "
+                                + "PasspointConfiguration: " + in.getName());
+                }
+            } else {
+                // Section elements.
+                switch (in.getName()) {
+                    case XML_TAG_SECTION_HEADER_HOMESP:
+                        config.setHomeSp(deserializeHomeSP(in, outerTagDepth + 1));
+                        break;
+                    case XML_TAG_SECTION_HEADER_CREDENTIAL:
+                        config.setCredential(deserializeCredential(in, outerTagDepth + 1));
+                        break;
+                    case XML_TAG_SECTION_HEADER_POLICY:
+                        config.setPolicy(deserializePolicy(in, outerTagDepth + 1));
+                        break;
+                    case XML_TAG_SECTION_HEADER_SUBSCRIPTION_UPDATE:
+                        config.setSubscriptionUpdate(
+                                deserializeUpdateParameter(in, outerTagDepth + 1));
+                        break;
+                    default:
+                        throw new XmlPullParserException("Unknown section under "
+                                + "PasspointConfiguration: " + in.getName());
+                }
+            }
+        }
+        return config;
+    }
+
+    /**
+     * Serialize a {@link HomeSp} to an output stream as a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @param homeSp The {@link HomeSp} to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializeHomeSp(XmlSerializer out, HomeSp homeSp)
+            throws XmlPullParserException, IOException {
+        if (homeSp == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_HOMESP);
+        XmlUtil.writeNextValue(out, XML_TAG_FQDN, homeSp.getFqdn());
+        XmlUtil.writeNextValue(out, XML_TAG_FRIENDLY_NAME, homeSp.getFriendlyName());
+        XmlUtil.writeNextValue(out, XML_TAG_ICON_URL, homeSp.getIconUrl());
+        XmlUtil.writeNextValue(out, XML_TAG_HOME_NETWORK_IDS, homeSp.getHomeNetworkIds());
+        XmlUtil.writeNextValue(out, XML_TAG_MATCH_ALL_OIS, homeSp.getMatchAllOis());
+        XmlUtil.writeNextValue(out, XML_TAG_MATCH_ANY_OIS, homeSp.getMatchAnyOis());
+        XmlUtil.writeNextValue(out, XML_TAG_OTHER_HOME_PARTNERS, homeSp.getOtherHomePartners());
+        XmlUtil.writeNextValue(out, XML_TAG_ROAMING_CONSORTIUM_OIS,
+                homeSp.getRoamingConsortiumOis());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_HOMESP);
+    }
+
+    /**
+     * Serialize a {@link Credential} to an output stream as a XML block.
+     *
+     * @param out The output stream to serialize to
+     * @param credential The {@link Credential} to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializeCredential(XmlSerializer out, Credential credential)
+            throws XmlPullParserException, IOException {
+        if (credential == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_CREDENTIAL);
+        XmlUtil.writeNextValue(out, XML_TAG_CREATION_TIME, credential.getCreationTimeInMillis());
+        XmlUtil.writeNextValue(out, XML_TAG_EXPIRATION_TIME,
+                credential.getExpirationTimeInMillis());
+        XmlUtil.writeNextValue(out, XML_TAG_REALM, credential.getRealm());
+        XmlUtil.writeNextValue(out, XML_TAG_CHECK_AAA_SERVER_CERT_STATUS,
+                credential.getCheckAaaServerCertStatus());
+        serializeUserCredential(out, credential.getUserCredential());
+        serializeCertCredential(out, credential.getCertCredential());
+        serializeSimCredential(out, credential.getSimCredential());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_CREDENTIAL);
+    }
+
+    /**
+     * Serialize a {@link Policy} to an output stream as a XML block.
+     *
+     * @param out The output stream to serialize to
+     * @param policy The {@link Policy} to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializePolicy(XmlSerializer out, Policy policy)
+            throws XmlPullParserException, IOException {
+        if (policy == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_POLICY);
+        XmlUtil.writeNextValue(out, XML_TAG_MIN_HOME_DOWNLINK_BANDWIDTH,
+                policy.getMinHomeDownlinkBandwidth());
+        XmlUtil.writeNextValue(out, XML_TAG_MIN_HOME_UPLINK_BANDWIDTH,
+                policy.getMinHomeUplinkBandwidth());
+        XmlUtil.writeNextValue(out, XML_TAG_MIN_ROAMING_DOWNLINK_BANDWIDTH,
+                policy.getMinRoamingDownlinkBandwidth());
+        XmlUtil.writeNextValue(out, XML_TAG_MIN_ROAMING_UPLINK_BANDWIDTH,
+                policy.getMinRoamingUplinkBandwidth());
+        XmlUtil.writeNextValue(out, XML_TAG_EXCLUDED_SSID_LIST, policy.getExcludedSsidList());
+        XmlUtil.writeNextValue(out, XML_TAG_MAXIMUM_BSS_LOAD_VALUE,
+                policy.getMaximumBssLoadValue());
+        serializeProtoPortMap(out, policy.getRequiredProtoPortMap());
+        serializeUpdateParameter(out, XML_TAG_SECTION_HEADER_POLICY_UPDATE,
+                policy.getPolicyUpdate());
+        serializePreferredRoamingPartnerList(out, policy.getPreferredRoamingPartnerList());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_POLICY);
+    }
+
+    /**
+     * Serialize a {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} to an output
+     * stream as a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @param userCredential The UserCredential to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializeUserCredential(XmlSerializer out,
+            Credential.UserCredential userCredential) throws XmlPullParserException, IOException {
+        if (userCredential == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_USER_CREDENTIAL);
+        XmlUtil.writeNextValue(out, XML_TAG_USERNAME, userCredential.getUsername());
+        XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, userCredential.getPassword());
+        XmlUtil.writeNextValue(out, XML_TAG_MACHINE_MANAGED, userCredential.getMachineManaged());
+        XmlUtil.writeNextValue(out, XML_TAG_SOFT_TOKEN_APP, userCredential.getSoftTokenApp());
+        XmlUtil.writeNextValue(out, XML_TAG_ABLE_TO_SHARE, userCredential.getAbleToShare());
+        XmlUtil.writeNextValue(out, XML_TAG_EAP_TYPE, userCredential.getEapType());
+        XmlUtil.writeNextValue(out, XML_TAG_NON_EAP_INNER_METHOD,
+                userCredential.getNonEapInnerMethod());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_USER_CREDENTIAL);
+    }
+
+    /**
+     * Serialize a {@link android.net.wifi.hotspot2.pps.Credential.CertificateCredential} to an
+     * output stream as a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @param certCredential The CertificateCredential to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializeCertCredential(XmlSerializer out,
+            Credential.CertificateCredential certCredential)
+                    throws XmlPullParserException, IOException {
+        if (certCredential == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_CERT_CREDENTIAL);
+        XmlUtil.writeNextValue(out, XML_TAG_CERT_TYPE, certCredential.getCertType());
+        XmlUtil.writeNextValue(out, XML_TAG_CERT_SHA256_FINGERPRINT,
+                certCredential.getCertSha256Fingerprint());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_CERT_CREDENTIAL);
+    }
+
+    /**
+     * Serialize a {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} to an
+     * output stream as a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @param simCredential The SimCredential to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializeSimCredential(XmlSerializer out,
+            Credential.SimCredential simCredential) throws XmlPullParserException, IOException {
+        if (simCredential == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_SIM_CREDENTIAL);
+        XmlUtil.writeNextValue(out, XML_TAG_IMSI, simCredential.getImsi());
+        XmlUtil.writeNextValue(out, XML_TAG_EAP_TYPE, simCredential.getEapType());
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_SIM_CREDENTIAL);
+    }
+
+    /**
+     * Serialize a preferred roaming partner list to an output stream as a XML block.
+     *
+     * @param out The output stream to serialize data to
+     * @param preferredRoamingPartnerList The partner list to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializePreferredRoamingPartnerList(XmlSerializer out,
+            List<Policy.RoamingPartner> preferredRoamingPartnerList)
+                    throws XmlPullParserException, IOException {
+        if (preferredRoamingPartnerList == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST);
+        for (Policy.RoamingPartner partner : preferredRoamingPartnerList) {
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_ROAMING_PARTNER);
+            XmlUtil.writeNextValue(out, XML_TAG_FQDN, partner.getFqdn());
+            XmlUtil.writeNextValue(out, XML_TAG_FQDN_EXACT_MATCH, partner.getFqdnExactMatch());
+            XmlUtil.writeNextValue(out, XML_TAG_PRIORITY, partner.getPriority());
+            XmlUtil.writeNextValue(out, XML_TAG_COUNTRIES, partner.getCountries());
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_ROAMING_PARTNER);
+        }
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST);
+    }
+
+    /**
+     * Serialize a {@link UpdateParameter} to an output stream as a XML block.  The
+     * {@link UpdateParameter} are used for describing Subscription Update and Policy Update.
+     *
+     * @param out The output stream to serialize data to
+     * @param type The type the {@link UpdateParameter} is used for
+     * @param param The {@link UpdateParameter} to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializeUpdateParameter(XmlSerializer out, String type,
+            UpdateParameter param) throws XmlPullParserException, IOException {
+        if (param == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, type);
+        XmlUtil.writeNextValue(out, XML_TAG_UPDATE_INTERVAL, param.getUpdateIntervalInMinutes());
+        XmlUtil.writeNextValue(out, XML_TAG_UPDATE_METHOD, param.getUpdateMethod());
+        XmlUtil.writeNextValue(out, XML_TAG_RESTRICTION, param.getRestriction());
+        XmlUtil.writeNextValue(out, XML_TAG_SERVER_URI, param.getServerUri());
+        XmlUtil.writeNextValue(out, XML_TAG_USERNAME, param.getUsername());
+        XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, param.getBase64EncodedPassword());
+        XmlUtil.writeNextValue(out, XML_TAG_TRUST_ROOT_CERT_URL, param.getTrustRootCertUrl());
+        XmlUtil.writeNextValue(out, XML_TAG_TRUST_ROOT_CERT_SHA256_FINGERPRINT,
+                param.getTrustRootCertSha256Fingerprint());
+        XmlUtil.writeNextSectionEnd(out, type);
+    }
+
+    /**
+     * Serialize a Protocol-to-Ports map to an output stream as a XML block.  We're not able
+     * to use {@link XmlUtil#writeNextValue} to write this map, since that function only works for
+     * maps with String key.
+     *
+     * @param out The output stream to serialize data to
+     * @param protoPortMap The proto port map to serialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static void serializeProtoPortMap(XmlSerializer out, Map<Integer, String> protoPortMap)
+            throws XmlPullParserException, IOException {
+        if (protoPortMap == null) {
+            return;
+        }
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP);
+        for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PROTO_PORT);
+            XmlUtil.writeNextValue(out, XML_TAG_PROTO, entry.getKey());
+            XmlUtil.writeNextValue(out, XML_TAG_PORTS, entry.getValue());
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_PROTO_PORT);
+        }
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP);
+    }
+
+    /**
+     * Deserialize a {@link HomeSp} from an input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link HomeSp}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static HomeSp deserializeHomeSP(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        HomeSp homeSp = new HomeSp();
+        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_FQDN:
+                    homeSp.setFqdn((String) value);
+                    break;
+                case XML_TAG_FRIENDLY_NAME:
+                    homeSp.setFriendlyName((String) value);
+                    break;
+                case XML_TAG_ICON_URL:
+                    homeSp.setIconUrl((String) value);
+                    break;
+                case XML_TAG_HOME_NETWORK_IDS:
+                    homeSp.setHomeNetworkIds((Map<String, Long>) value);
+                    break;
+                case XML_TAG_MATCH_ALL_OIS:
+                    homeSp.setMatchAllOis((long[]) value);
+                    break;
+                case XML_TAG_MATCH_ANY_OIS:
+                    homeSp.setMatchAnyOis((long[]) value);
+                    break;
+                case XML_TAG_ROAMING_CONSORTIUM_OIS:
+                    homeSp.setRoamingConsortiumOis((long[]) value);
+                    break;
+                case XML_TAG_OTHER_HOME_PARTNERS:
+                    homeSp.setOtherHomePartners((String[]) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown data under HomeSP: " + valueName[0]);
+            }
+        }
+        return homeSp;
+    }
+
+    /**
+     * Deserialize a {@link Credential} from an input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link Credential}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static Credential deserializeCredential(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        Credential credential = new Credential();
+        while (XmlUtils.nextElementWithin(in, outerTagDepth)) {
+            if (isValueElement(in)) {
+                // Value elements.
+                String[] name = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, name);
+                switch (name[0]) {
+                    case XML_TAG_CREATION_TIME:
+                        credential.setCreationTimeInMillis((long) value);
+                        break;
+                    case XML_TAG_EXPIRATION_TIME:
+                        credential.setExpirationTimeInMillis((long) value);
+                        break;
+                    case XML_TAG_REALM:
+                        credential.setRealm((String) value);
+                        break;
+                    case XML_TAG_CHECK_AAA_SERVER_CERT_STATUS:
+                        credential.setCheckAaaServerCertStatus((boolean) value);
+                        break;
+                    default:
+                        throw new XmlPullParserException("Unknown value under Credential: "
+                            + name[0]);
+                }
+            } else {
+                // Subsection elements.
+                switch (in.getName()) {
+                    case XML_TAG_SECTION_HEADER_USER_CREDENTIAL:
+                        credential.setUserCredential(
+                                deserializeUserCredential(in, outerTagDepth + 1));
+                        break;
+                    case XML_TAG_SECTION_HEADER_CERT_CREDENTIAL:
+                        credential.setCertCredential(
+                                deserializeCertCredential(in, outerTagDepth + 1));
+                        break;
+                    case XML_TAG_SECTION_HEADER_SIM_CREDENTIAL:
+                        credential.setSimCredential(
+                                deserializeSimCredential(in, outerTagDepth + 1));
+                        break;
+                    default:
+                        throw new XmlPullParserException("Unknown section under Credential: "
+                                + in.getName());
+                }
+            }
+        }
+        return credential;
+    }
+
+    /**
+     * Deserialize a {@link Policy} from an input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link Policy}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static Policy deserializePolicy(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        Policy policy = new Policy();
+        while (XmlUtils.nextElementWithin(in, outerTagDepth)) {
+            if (isValueElement(in)) {
+                // Value elements.
+                String[] name = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, name);
+                switch (name[0]) {
+                    case XML_TAG_MIN_HOME_DOWNLINK_BANDWIDTH:
+                        policy.setMinHomeDownlinkBandwidth((long) value);
+                        break;
+                    case XML_TAG_MIN_HOME_UPLINK_BANDWIDTH:
+                        policy.setMinHomeUplinkBandwidth((long) value);
+                        break;
+                    case XML_TAG_MIN_ROAMING_DOWNLINK_BANDWIDTH:
+                        policy.setMinRoamingDownlinkBandwidth((long) value);
+                        break;
+                    case XML_TAG_MIN_ROAMING_UPLINK_BANDWIDTH:
+                        policy.setMinRoamingUplinkBandwidth((long) value);
+                        break;
+                    case XML_TAG_EXCLUDED_SSID_LIST:
+                        policy.setExcludedSsidList((String[]) value);
+                        break;
+                    case XML_TAG_MAXIMUM_BSS_LOAD_VALUE:
+                        policy.setMaximumBssLoadValue((int) value);
+                        break;
+                }
+            } else {
+                // Subsection elements.
+                switch (in.getName()) {
+                    case XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP:
+                        policy.setRequiredProtoPortMap(
+                                deserializeProtoPortMap(in, outerTagDepth + 1));
+                        break;
+                    case XML_TAG_SECTION_HEADER_POLICY_UPDATE:
+                        policy.setPolicyUpdate(deserializeUpdateParameter(in, outerTagDepth + 1));
+                        break;
+                    case XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST:
+                        policy.setPreferredRoamingPartnerList(
+                                deserializePreferredRoamingPartnerList(in, outerTagDepth + 1));
+                        break;
+                    default:
+                        throw new XmlPullParserException("Unknown section under Policy: "
+                                + in.getName());
+                }
+            }
+        }
+        return policy;
+    }
+
+    /**
+     * Deserialize a {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} from an
+     * input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link android.net.wifi.hotspot2.pps.Credential.UserCredential}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static Credential.UserCredential deserializeUserCredential(XmlPullParser in,
+            int outerTagDepth) throws XmlPullParserException, IOException {
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        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_USERNAME:
+                    userCredential.setUsername((String) value);
+                    break;
+                case XML_TAG_PASSWORD:
+                    userCredential.setPassword((String) value);
+                    break;
+                case XML_TAG_MACHINE_MANAGED:
+                    userCredential.setMachineManaged((boolean) value);
+                    break;
+                case XML_TAG_SOFT_TOKEN_APP:
+                    userCredential.setSoftTokenApp((String) value);
+                    break;
+                case XML_TAG_ABLE_TO_SHARE:
+                    userCredential.setAbleToShare((boolean) value);
+                    break;
+                case XML_TAG_EAP_TYPE:
+                    userCredential.setEapType((int) value);
+                    break;
+                case XML_TAG_NON_EAP_INNER_METHOD:
+                    userCredential.setNonEapInnerMethod((String) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown value under UserCredential: "
+                            + valueName[0]);
+            }
+        }
+        return userCredential;
+    }
+
+    /**
+     * Deserialize a {@link android.net.wifi.hotspot2.pps.Credential.CertificateCredential}
+     * from an input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link android.net.wifi.hotspot2.pps.Credential.CertificateCredential}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static Credential.CertificateCredential deserializeCertCredential(XmlPullParser in,
+            int outerTagDepth) throws XmlPullParserException, IOException {
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        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_CERT_TYPE:
+                    certCredential.setCertType((String) value);
+                    break;
+                case XML_TAG_CERT_SHA256_FINGERPRINT:
+                    certCredential.setCertSha256Fingerprint((byte[]) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown value under CertCredential: "
+                            + valueName[0]);
+            }
+        }
+        return certCredential;
+    }
+
+    /**
+     * Deserialize a {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} from an
+     * input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link android.net.wifi.hotspot2.pps.Credential.SimCredential}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static Credential.SimCredential deserializeSimCredential(XmlPullParser in,
+            int outerTagDepth) throws XmlPullParserException, IOException {
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        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_IMSI:
+                    simCredential.setImsi((String) value);
+                    break;
+                case XML_TAG_EAP_TYPE:
+                    simCredential.setEapType((int) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown value under CertCredential: "
+                            + valueName[0]);
+            }
+        }
+        return simCredential;
+    }
+
+    /**
+     * Deserialize a list of {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner} from an
+     * input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return List of {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static List<Policy.RoamingPartner> deserializePreferredRoamingPartnerList(
+            XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException {
+        List<Policy.RoamingPartner> roamingPartnerList = new ArrayList<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_ROAMING_PARTNER,
+                outerTagDepth)) {
+            roamingPartnerList.add(deserializeRoamingPartner(in, outerTagDepth + 1));
+        }
+        return roamingPartnerList;
+    }
+
+    /**
+     * Deserialize a {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner} from an input
+     * stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static Policy.RoamingPartner deserializeRoamingPartner(XmlPullParser in,
+            int outerTagDepth) throws XmlPullParserException, IOException {
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        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_FQDN:
+                    partner.setFqdn((String) value);
+                    break;
+                case XML_TAG_FQDN_EXACT_MATCH:
+                    partner.setFqdnExactMatch((boolean) value);
+                    break;
+                case XML_TAG_PRIORITY:
+                    partner.setPriority((int) value);
+                    break;
+                case XML_TAG_COUNTRIES:
+                    partner.setCountries((String) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown value under RoamingPartner: "
+                            + valueName[0]);
+            }
+        }
+        return partner;
+    }
+
+    /**
+     * Deserialize a {@link UpdateParameter} from an input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return {@link UpdateParameter}
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static UpdateParameter deserializeUpdateParameter(XmlPullParser in,
+            int outerTagDepth) throws XmlPullParserException, IOException {
+        UpdateParameter param = new UpdateParameter();
+        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_UPDATE_INTERVAL:
+                    param.setUpdateIntervalInMinutes((long) value);
+                    break;
+                case XML_TAG_UPDATE_METHOD:
+                    param.setUpdateMethod((String) value);
+                    break;
+                case XML_TAG_RESTRICTION:
+                    param.setRestriction((String) value);
+                    break;
+                case XML_TAG_SERVER_URI:
+                    param.setServerUri((String) value);
+                    break;
+                case XML_TAG_USERNAME:
+                    param.setUsername((String) value);
+                    break;
+                case XML_TAG_PASSWORD:
+                    param.setBase64EncodedPassword((String) value);
+                    break;
+                case XML_TAG_TRUST_ROOT_CERT_URL:
+                    param.setTrustRootCertUrl((String) value);
+                    break;
+                case XML_TAG_TRUST_ROOT_CERT_SHA256_FINGERPRINT:
+                    param.setTrustRootCertSha256Fingerprint((byte[]) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown value under UpdateParameter: "
+                            + valueName[0]);
+            }
+        }
+        return param;
+    }
+
+    /**
+     * Deserialize a Protocol-Port map from an input stream.
+     *
+     * @param in The input stream to read data from
+     * @param outerTagDepth The tag depth of the current XML section
+     * @return Proocol-Port map
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private static Map<Integer, String> deserializeProtoPortMap(XmlPullParser in,
+            int outerTagDepth) throws XmlPullParserException, IOException {
+        Map<Integer, String> protoPortMap = new HashMap<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_PROTO_PORT,
+                outerTagDepth)) {
+            int proto = (int) XmlUtil.readNextValueWithName(in, XML_TAG_PROTO);
+            String ports = (String) XmlUtil.readNextValueWithName(in, XML_TAG_PORTS);
+            protoPortMap.put(proto, ports);
+        }
+        return protoPortMap;
+    }
+
+    /**
+     * Determine if the current element is a value or a section.  The "name" attribute of the
+     * element is used as the indicator, when it is present, the element is considered a value
+     * element.
+     *
+     * Value element:
+     * <int name="test">12</int>
+     *
+     * Section element:
+     * <Test>
+     * ...
+     * </Test>
+     *
+     * @param in XML input stream
+     * @return true if the current element is a value
+     */
+    private static boolean isValueElement(XmlPullParser in) {
+        return in.getAttributeValue(null, "name") != null;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java b/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java
deleted file mode 100644
index 698968e..0000000
--- a/service/java/com/android/server/wifi/hotspot2/SupplicantBridge.java
+++ /dev/null
@@ -1,489 +0,0 @@
-package com.android.server.wifi.hotspot2;
-
-import android.util.Base64;
-import android.util.Log;
-
-import com.android.server.wifi.ScanDetail;
-import com.android.server.wifi.WifiNative;
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.ANQPFactory;
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.anqp.eap.AuthParam;
-import com.android.server.wifi.anqp.eap.EAP;
-import com.android.server.wifi.anqp.eap.EAPMethod;
-import com.android.server.wifi.hotspot2.pps.Credential;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.net.ProtocolException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class SupplicantBridge {
-    private final WifiNative mSupplicantHook;
-    private final SupplicantBridgeCallbacks mCallbacks;
-    private final Map<Long, ScanDetail> mRequestMap = new HashMap<>();
-
-    private static final int IconChunkSize = 1400;  // 2K*3/4 - overhead
-    private static final Map<String, Constants.ANQPElementType> sWpsNames = new HashMap<>();
-
-    static {
-        sWpsNames.put("anqp_venue_name", Constants.ANQPElementType.ANQPVenueName);
-        sWpsNames.put("anqp_network_auth_type", Constants.ANQPElementType.ANQPNwkAuthType);
-        sWpsNames.put("anqp_roaming_consortium", Constants.ANQPElementType.ANQPRoamingConsortium);
-        sWpsNames.put("anqp_ip_addr_type_availability",
-                Constants.ANQPElementType.ANQPIPAddrAvailability);
-        sWpsNames.put("anqp_nai_realm", Constants.ANQPElementType.ANQPNAIRealm);
-        sWpsNames.put("anqp_3gpp", Constants.ANQPElementType.ANQP3GPPNetwork);
-        sWpsNames.put("anqp_domain_name", Constants.ANQPElementType.ANQPDomName);
-        sWpsNames.put("hs20_operator_friendly_name", Constants.ANQPElementType.HSFriendlyName);
-        sWpsNames.put("hs20_wan_metrics", Constants.ANQPElementType.HSWANMetrics);
-        sWpsNames.put("hs20_connection_capability", Constants.ANQPElementType.HSConnCapability);
-        sWpsNames.put("hs20_operating_class", Constants.ANQPElementType.HSOperatingclass);
-        sWpsNames.put("hs20_osu_providers_list", Constants.ANQPElementType.HSOSUProviders);
-    }
-
-    /**
-     * Interface to be implemented by the client to receive callbacks from SupplicantBridge.
-     */
-    public interface SupplicantBridgeCallbacks {
-        /**
-         * Response from supplicant bridge for the initiated request.
-         * @param scanDetail
-         * @param anqpElements
-         */
-        void notifyANQPResponse(
-                ScanDetail scanDetail,
-                Map<Constants.ANQPElementType, ANQPElement> anqpElements);
-
-        /**
-         * Notify failure.
-         * @param bssid
-         */
-        void notifyIconFailed(long bssid);
-    }
-
-    public static boolean isAnqpAttribute(String line) {
-        int split = line.indexOf('=');
-        return split >= 0 && sWpsNames.containsKey(line.substring(0, split));
-    }
-
-    public SupplicantBridge(WifiNative supplicantHook, SupplicantBridgeCallbacks callbacks) {
-        mSupplicantHook = supplicantHook;
-        mCallbacks = callbacks;
-    }
-
-    public static Map<Constants.ANQPElementType, ANQPElement> parseANQPLines(List<String> lines) {
-        if (lines == null) {
-            return null;
-        }
-        Map<Constants.ANQPElementType, ANQPElement> elements = new HashMap<>(lines.size());
-        for (String line : lines) {
-            try {
-                ANQPElement element = buildElement(line);
-                if (element != null) {
-                    elements.put(element.getID(), element);
-                }
-            }
-            catch (ProtocolException pe) {
-                Log.e(Utils.hs2LogTag(SupplicantBridge.class), "Failed to parse ANQP: " + pe);
-            }
-        }
-        return elements;
-    }
-
-    public boolean startANQP(ScanDetail scanDetail, List<Constants.ANQPElementType> elements) {
-        String anqpGet = buildWPSQueryRequest(scanDetail.getNetworkDetail(), elements);
-        if (anqpGet == null) {
-            return false;
-        }
-        synchronized (mRequestMap) {
-            mRequestMap.put(scanDetail.getNetworkDetail().getBSSID(), scanDetail);
-        }
-        String result = mSupplicantHook.doCustomSupplicantCommand(anqpGet);
-        if (result != null && result.startsWith("OK")) {
-            Log.d(Utils.hs2LogTag(getClass()), "ANQP initiated on "
-                    + scanDetail + " (" + anqpGet + ")");
-            return true;
-        }
-        else {
-            Log.d(Utils.hs2LogTag(getClass()), "ANQP failed on " +
-                    scanDetail + ": " + result);
-            return false;
-        }
-    }
-
-    public boolean doIconQuery(long bssid, String fileName) {
-        String result = mSupplicantHook.doCustomSupplicantCommand("REQ_HS20_ICON " +
-                Utils.macToString(bssid) + " " + fileName);
-        return result != null && result.startsWith("OK");
-    }
-
-    public byte[] retrieveIcon(IconEvent iconEvent) throws IOException {
-        byte[] iconData = new byte[iconEvent.getSize()];
-        try {
-            int offset = 0;
-            while (offset < iconEvent.getSize()) {
-                int size = Math.min(iconEvent.getSize() - offset, IconChunkSize);
-
-                String command = String.format("GET_HS20_ICON %s %s %d %d",
-                        Utils.macToString(iconEvent.getBSSID()), iconEvent.getFileName(),
-                        offset, size);
-                Log.d(Utils.hs2LogTag(getClass()), "Issuing '" + command + "'");
-                String response = mSupplicantHook.doCustomSupplicantCommand(command);
-                if (response == null) {
-                    throw new IOException("No icon data returned");
-                }
-
-                try {
-                    byte[] fragment = Base64.decode(response, Base64.DEFAULT);
-                    if (fragment.length == 0) {
-                        throw new IOException("Null data for '" + command + "': " + response);
-                    }
-                    if (fragment.length + offset > iconData.length) {
-                        throw new IOException("Icon chunk exceeds image size");
-                    }
-                    System.arraycopy(fragment, 0, iconData, offset, fragment.length);
-                    offset += fragment.length;
-                } catch (IllegalArgumentException iae) {
-                    throw new IOException("Failed to parse response to '" + command
-                            + "': " + response);
-                }
-            }
-            if (offset != iconEvent.getSize()) {
-                Log.w(Utils.hs2LogTag(getClass()), "Partial icon data: " + offset +
-                        ", expected " + iconEvent.getSize());
-            }
-        }
-        finally {
-            Log.d(Utils.hs2LogTag(getClass()), "Deleting icon for " + iconEvent);
-            String result = mSupplicantHook.doCustomSupplicantCommand("DEL_HS20_ICON " +
-                    Utils.macToString(iconEvent.getBSSID()) + " " + iconEvent.getFileName());
-        }
-
-        return iconData;
-    }
-
-    public void notifyANQPDone(Long bssid, boolean success) {
-        ScanDetail scanDetail;
-        synchronized (mRequestMap) {
-            scanDetail = mRequestMap.remove(bssid);
-        }
-
-        if (scanDetail == null) {
-            if (!success) {
-                mCallbacks.notifyIconFailed(bssid);
-            }
-            return;
-        }
-
-        String bssData = mSupplicantHook.scanResult(scanDetail.getBSSIDString());
-        try {
-            Map<Constants.ANQPElementType, ANQPElement> elements = parseWPSData(bssData);
-            Log.d(Utils.hs2LogTag(getClass()), String.format("%s ANQP response for %012x: %s",
-                    success ? "successful" : "failed", bssid, elements));
-            mCallbacks.notifyANQPResponse(scanDetail, success ? elements : null);
-        }
-        catch (IOException ioe) {
-            Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
-                    ioe.toString() + ": " + bssData);
-        }
-        catch (RuntimeException rte) {
-            Log.e(Utils.hs2LogTag(getClass()), "Failed to parse ANQP: " +
-                    rte.toString() + ": " + bssData, rte);
-        }
-        mCallbacks.notifyANQPResponse(scanDetail, null);
-    }
-
-    private static String escapeSSID(NetworkDetail networkDetail) {
-        return escapeString(networkDetail.getSSID(), networkDetail.isSSID_UTF8());
-    }
-
-    private static String escapeString(String s, boolean utf8) {
-        boolean asciiOnly = true;
-        for (int n = 0; n < s.length(); n++) {
-            char ch = s.charAt(n);
-            if (ch > 127) {
-                asciiOnly = false;
-                break;
-            }
-        }
-
-        if (asciiOnly) {
-            return '"' + s + '"';
-        }
-        else {
-            byte[] octets = s.getBytes(utf8 ? StandardCharsets.UTF_8 : StandardCharsets.ISO_8859_1);
-
-            StringBuilder sb = new StringBuilder();
-            for (byte octet : octets) {
-                sb.append(String.format("%02x", octet & Constants.BYTE_MASK));
-            }
-            return sb.toString();
-        }
-    }
-
-    /**
-     * Build a wpa_supplicant ANQP query command
-     * @param networkDetail The network to query.
-     * @param querySet elements to query
-     * @return A command string.
-     */
-    private static String buildWPSQueryRequest(NetworkDetail networkDetail,
-                                               List<Constants.ANQPElementType> querySet) {
-
-        boolean baseANQPElements = Constants.hasBaseANQPElements(querySet);
-        StringBuilder sb = new StringBuilder();
-        if (baseANQPElements) {
-            sb.append("ANQP_GET ");
-        }
-        else {
-            sb.append("HS20_ANQP_GET ");     // ANQP_GET does not work for a sole hs20:8 (OSU) query
-        }
-        sb.append(networkDetail.getBSSIDString()).append(' ');
-
-        boolean first = true;
-        for (Constants.ANQPElementType elementType : querySet) {
-            if (first) {
-                first = false;
-            }
-            else {
-                sb.append(',');
-            }
-
-            Integer id = Constants.getANQPElementID(elementType);
-            if (id != null) {
-                sb.append(id);
-            }
-            else {
-                id = Constants.getHS20ElementID(elementType);
-                if (baseANQPElements) {
-                    sb.append("hs20:");
-                }
-                sb.append(id);
-            }
-        }
-
-        return sb.toString();
-    }
-
-    private static List<String> getWPSNetCommands(String netID, NetworkDetail networkDetail,
-                                                 Credential credential) {
-
-        List<String> commands = new ArrayList<String>();
-
-        EAPMethod eapMethod = credential.getEAPMethod();
-        commands.add(String.format("SET_NETWORK %s key_mgmt WPA-EAP", netID));
-        commands.add(String.format("SET_NETWORK %s ssid %s", netID, escapeSSID(networkDetail)));
-        commands.add(String.format("SET_NETWORK %s bssid %s",
-                netID, networkDetail.getBSSIDString()));
-        commands.add(String.format("SET_NETWORK %s eap %s",
-                netID, mapEAPMethodName(eapMethod.getEAPMethodID())));
-
-        AuthParam authParam = credential.getEAPMethod().getAuthParam();
-        if (authParam == null) {
-            return null;            // TLS or SIM/AKA
-        }
-        switch (authParam.getAuthInfoID()) {
-            case NonEAPInnerAuthType:
-            case InnerAuthEAPMethodType:
-                commands.add(String.format("SET_NETWORK %s identity %s",
-                        netID, escapeString(credential.getUserName(), true)));
-                commands.add(String.format("SET_NETWORK %s password %s",
-                        netID, escapeString(credential.getPassword(), true)));
-                commands.add(String.format("SET_NETWORK %s anonymous_identity \"anonymous\"",
-                        netID));
-                break;
-            default:                // !!! Needs work.
-                return null;
-        }
-        commands.add(String.format("SET_NETWORK %s priority 0", netID));
-        commands.add(String.format("ENABLE_NETWORK %s", netID));
-        commands.add(String.format("SAVE_CONFIG"));
-        return commands;
-    }
-
-    private static Map<Constants.ANQPElementType, ANQPElement> parseWPSData(String bssInfo)
-            throws IOException {
-        Map<Constants.ANQPElementType, ANQPElement> elements = new HashMap<>();
-        if (bssInfo == null) {
-            return elements;
-        }
-        BufferedReader lineReader = new BufferedReader(new StringReader(bssInfo));
-        String line;
-        while ((line=lineReader.readLine()) != null) {
-            ANQPElement element = buildElement(line);
-            if (element != null) {
-                elements.put(element.getID(), element);
-            }
-        }
-        return elements;
-    }
-
-    private static ANQPElement buildElement(String text) throws ProtocolException {
-        int separator = text.indexOf('=');
-        if (separator < 0) {
-            return null;
-        }
-
-        String elementName = text.substring(0, separator);
-        Constants.ANQPElementType elementType = sWpsNames.get(elementName);
-        if (elementType == null) {
-            return null;
-        }
-
-        byte[] payload;
-        try {
-            payload = Utils.hexToBytes(text.substring(separator + 1));
-        }
-        catch (NumberFormatException nfe) {
-            Log.e(Utils.hs2LogTag(SupplicantBridge.class), "Failed to parse hex string");
-            return null;
-        }
-        return Constants.getANQPElementID(elementType) != null ?
-                ANQPFactory.buildElement(ByteBuffer.wrap(payload), elementType, payload.length) :
-                ANQPFactory.buildHS20Element(elementType,
-                        ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN));
-    }
-
-    private static String mapEAPMethodName(EAP.EAPMethodID eapMethodID) {
-        switch (eapMethodID) {
-            case EAP_AKA:
-                return "AKA";
-            case EAP_AKAPrim:
-                return "AKA'";  // eap.c:1514
-            case EAP_SIM:
-                return "SIM";
-            case EAP_TLS:
-                return "TLS";
-            case EAP_TTLS:
-                return "TTLS";
-            default:
-                throw new IllegalArgumentException("No mapping for " + eapMethodID);
-        }
-    }
-
-    private static final Map<Character,Integer> sMappings = new HashMap<Character, Integer>();
-
-    static {
-        sMappings.put('\\', (int)'\\');
-        sMappings.put('"', (int)'"');
-        sMappings.put('e', 0x1b);
-        sMappings.put('n', (int)'\n');
-        sMappings.put('r', (int)'\n');
-        sMappings.put('t', (int)'\t');
-    }
-
-    public static String unescapeSSID(String ssid) {
-
-        CharIterator chars = new CharIterator(ssid);
-        byte[] octets = new byte[ssid.length()];
-        int bo = 0;
-
-        while (chars.hasNext()) {
-            char ch = chars.next();
-            if (ch != '\\' || ! chars.hasNext()) {
-                octets[bo++] = (byte)ch;
-            }
-            else {
-                char suffix = chars.next();
-                Integer mapped = sMappings.get(suffix);
-                if (mapped != null) {
-                    octets[bo++] = mapped.byteValue();
-                }
-                else if (suffix == 'x' && chars.hasDoubleHex()) {
-                    octets[bo++] = (byte)chars.nextDoubleHex();
-                }
-                else {
-                    octets[bo++] = '\\';
-                    octets[bo++] = (byte)suffix;
-                }
-            }
-        }
-
-        boolean asciiOnly = true;
-        for (byte b : octets) {
-            if ((b&0x80) != 0) {
-                asciiOnly = false;
-                break;
-            }
-        }
-        if (asciiOnly) {
-            return new String(octets, 0, bo, StandardCharsets.UTF_8);
-        } else {
-            try {
-                // If UTF-8 decoding is successful it is almost certainly UTF-8
-                CharBuffer cb = StandardCharsets.UTF_8.newDecoder().decode(
-                        ByteBuffer.wrap(octets, 0, bo));
-                return cb.toString();
-            } catch (CharacterCodingException cce) {
-                return new String(octets, 0, bo, StandardCharsets.ISO_8859_1);
-            }
-        }
-    }
-
-    private static class CharIterator {
-        private final String mString;
-        private int mPosition;
-        private int mHex;
-
-        private CharIterator(String s) {
-            mString = s;
-        }
-
-        private boolean hasNext() {
-            return mPosition < mString.length();
-        }
-
-        private char next() {
-            return mString.charAt(mPosition++);
-        }
-
-        private boolean hasDoubleHex() {
-            if (mString.length() - mPosition < 2) {
-                return false;
-            }
-            int nh = Utils.fromHex(mString.charAt(mPosition), true);
-            if (nh < 0) {
-                return false;
-            }
-            int nl = Utils.fromHex(mString.charAt(mPosition + 1), true);
-            if (nl < 0) {
-                return false;
-            }
-            mPosition += 2;
-            mHex = (nh << 4) | nl;
-            return true;
-        }
-
-        private int nextDoubleHex() {
-            return mHex;
-        }
-    }
-
-    private static final String[] TestStrings = {
-            "test-ssid",
-            "test\\nss\\tid",
-            "test\\x2d\\x5f\\nss\\tid",
-            "test\\x2d\\x5f\\nss\\tid\\\\",
-            "test\\x2d\\x5f\\nss\\tid\\n",
-            "test\\x2d\\x5f\\nss\\tid\\x4a",
-            "another\\",
-            "an\\other",
-            "another\\x2"
-    };
-
-    public static void main(String[] args) {
-        for (String string : TestStrings) {
-            System.out.println(unescapeSSID(string));
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/Utils.java b/service/java/com/android/server/wifi/hotspot2/Utils.java
index 39488ff..dc04532 100644
--- a/service/java/com/android/server/wifi/hotspot2/Utils.java
+++ b/service/java/com/android/server/wifi/hotspot2/Utils.java
@@ -1,6 +1,6 @@
 package com.android.server.wifi.hotspot2;
 
-import com.android.server.wifi.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.Constants;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -10,8 +10,8 @@
 import java.util.List;
 import java.util.TimeZone;
 
-import static com.android.server.wifi.anqp.Constants.BYTE_MASK;
-import static com.android.server.wifi.anqp.Constants.NIBBLE_MASK;
+import static com.android.server.wifi.hotspot2.anqp.Constants.BYTE_MASK;
+import static com.android.server.wifi.hotspot2.anqp.Constants.NIBBLE_MASK;
 
 public abstract class Utils {
 
diff --git a/service/java/com/android/server/wifi/hotspot2/WnmData.java b/service/java/com/android/server/wifi/hotspot2/WnmData.java
new file mode 100644
index 0000000..97a7d11
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/WnmData.java
@@ -0,0 +1,74 @@
+/*
+ * 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.hotspot2;
+
+/**
+ * This class carries the payload of a Hotspot 2.0 Wireless Network Management (WNM) frame,
+ * described in the Hotspot 2.0 spec, section 3.2.
+ */
+public class WnmData {
+    public static final int ESS = 1;   // HS2.0 spec section 3.2.1.2, table 4
+
+    private final long mBssid;
+    private final String mUrl;
+    private final boolean mDeauthEvent;
+    private final int mMethod;
+    private final boolean mEss;
+    private final int mDelay;
+
+    public WnmData(long bssid, String url, int method) {
+        mBssid = bssid;
+        mUrl = url;
+        mMethod = method;
+        mEss = false;
+        mDelay = -1;
+        mDeauthEvent = false;
+    }
+
+    public WnmData(long bssid, String url, boolean ess, int delay) {
+        mBssid = bssid;
+        mUrl = url;
+        mEss = ess;
+        mDelay = delay;
+        mMethod = -1;
+        mDeauthEvent = true;
+    }
+
+    public long getBssid() {
+        return mBssid;
+    }
+
+    public String getUrl() {
+        return mUrl;
+    }
+
+    public boolean isDeauthEvent() {
+        return mDeauthEvent;
+    }
+
+    public int getMethod() {
+        return mMethod;
+    }
+
+    public boolean isEss() {
+        return mEss;
+    }
+
+    public int getDelay() {
+        return mDelay;
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/ANQPElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPElement.java
similarity index 86%
rename from service/java/com/android/server/wifi/anqp/ANQPElement.java
rename to service/java/com/android/server/wifi/hotspot2/anqp/ANQPElement.java
index aec9537..b237b77 100644
--- a/service/java/com/android/server/wifi/anqp/ANQPElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPElement.java
@@ -1,4 +1,4 @@
-package com.android.server.wifi.anqp;
+package com.android.server.wifi.hotspot2.anqp;
 
 /**
  * Base class for an IEEE802.11u ANQP element.
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
new file mode 100644
index 0000000..7a06ef4
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/ANQPParser.java
@@ -0,0 +1,135 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Factory to build a collection of 802.11u ANQP elements from a byte buffer.
+ */
+public class ANQPParser {
+    /**
+     * The OI value for Hotspot 2.0 ANQP-element.
+     */
+    @VisibleForTesting
+    public static final int VENDOR_SPECIFIC_HS20_OI = 0x506F9A;
+
+    /**
+     * The Type value for Hotspot 2.0 ANQP-element.
+     */
+    @VisibleForTesting
+    public static final int VENDOR_SPECIFIC_HS20_TYPE = 0x11;
+
+    /**
+     * Parse an ANQP element from the pass-in byte buffer.
+     *
+     * Note: Each Hotspot 2.0 Release 2 element will be wrapped inside a Vendor Specific element
+     * in the ANQP response from the AP.  However, the lower layer (e.g. wpa_supplicant) should
+     * already take care of parsing those elements out of Vendor Specific elements.  To be safe,
+     * we will parse the Vendor Specific elements for non-Hotspot 2.0 Release elements or in
+     * the case they're not parsed by the lower layer.
+     *
+     * @param infoID The ANQP element type
+     * @param payload The buffer to read from
+     * @return {@link com.android.server.wifi.hotspot2.anqp.ANQPElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static ANQPElement parseElement(Constants.ANQPElementType infoID, ByteBuffer payload)
+            throws ProtocolException {
+        switch (infoID) {
+            case ANQPVenueName:
+                return VenueNameElement.parse(payload);
+            case ANQPRoamingConsortium:
+                return RoamingConsortiumElement.parse(payload);
+            case ANQPIPAddrAvailability:
+                return IPAddressTypeAvailabilityElement.parse(payload);
+            case ANQPNAIRealm:
+                return NAIRealmElement.parse(payload);
+            case ANQP3GPPNetwork:
+                return ThreeGPPNetworkElement.parse(payload);
+            case ANQPDomName:
+                return DomainNameElement.parse(payload);
+            case ANQPVendorSpec:
+                return parseVendorSpecificElement(payload);
+            default:
+                throw new ProtocolException("Unknown element ID: " + infoID);
+        }
+    }
+
+    /**
+     * Parse a Hotspot 2.0 Release 2 ANQP element from the pass-in byte buffer.
+     *
+     * @param infoID The ANQP element ID
+     * @param payload The buffer to read from
+     * @return {@link com.android.server.wifi.hotspot2.anqp.ANQPElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static ANQPElement parseHS20Element(Constants.ANQPElementType infoID,
+            ByteBuffer payload) throws ProtocolException {
+        switch (infoID) {
+            case HSFriendlyName:
+                return HSFriendlyNameElement.parse(payload);
+            case HSWANMetrics:
+                return HSWanMetricsElement.parse(payload);
+            case HSConnCapability:
+                return HSConnectionCapabilityElement.parse(payload);
+            case HSOSUProviders:
+                return RawByteElement.parse(infoID, payload);
+            default:
+                throw new ProtocolException("Unknown element ID: " + infoID);
+        }
+    }
+
+    /**
+     * Parse the ANQP vendor specific element.  Currently only supports the vendor specific
+     * element that contained Hotspot 2.0 ANQP-element.
+     *
+     * Format of a ANQP Vendor Specific element:
+     * | OI | Type | Subtype | Reserved | Payload |
+     *   3     1        1         1       variable
+     *
+     * @param payload The buffer to read from
+     * @return {@link ANQPElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    private static ANQPElement parseVendorSpecificElement(ByteBuffer payload)
+            throws ProtocolException {
+        int oi = (int) ByteBufferReader.readInteger(payload, ByteOrder.BIG_ENDIAN, 3);
+        int type = payload.get() & 0xFF;
+
+        if (oi != VENDOR_SPECIFIC_HS20_OI || type != VENDOR_SPECIFIC_HS20_TYPE) {
+            throw new ProtocolException("Unsupported vendor specific OI=" + oi + " type=" + type);
+        }
+
+        int subType = payload.get() & 0xFF;
+        Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
+        if (hs20ID == null) {
+            throw new ProtocolException("Unsupported subtype: " + subType);
+        }
+        payload.get();     // Skip the reserved byte
+        return parseHS20Element(hs20ID, payload);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/CellularNetwork.java b/service/java/com/android/server/wifi/hotspot2/anqp/CellularNetwork.java
new file mode 100644
index 0000000..cc39b3f
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/CellularNetwork.java
@@ -0,0 +1,167 @@
+/*
+ * 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.hotspot2.anqp;
+
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The IEI (Information Element Identity) contained in the Generic Container for the
+ * 3GPP Cellular Network ANQP element.
+ *
+ * Refer to Annex A of 3GPP TS 24.234 version 11.3.0 for information on the data format:
+ * (http://www.etsi.org/deliver/etsi_ts/124200_124299/124234/11.03.00_60/ts_124234v110300p.pdf)
+ */
+public class CellularNetwork {
+    private static final String TAG = "CellularNetwork";
+
+    /**
+     * IEI type for PLMN (Public Land Mobile Network) list.
+     */
+    @VisibleForTesting
+    public static final int IEI_TYPE_PLMN_LIST = 0;
+
+    @VisibleForTesting
+    public static final int IEI_CONTENT_LENGTH_MASK = 0x7F;
+
+    /**
+     * Number of bytes for each PLMN (Public Land Mobile Network).
+     */
+    @VisibleForTesting
+    public static final int PLMN_DATA_BYTES = 3;
+
+    /**
+     * The value for comparing the third digit of MNC data with to determine if the MNC is
+     * two or three digits.
+     */
+    private static final int MNC_2DIGIT_VALUE = 0xF;
+
+    /**
+     * List of PLMN (Public Land Mobile Network) information.
+     */
+    private final List<String> mPlmnList;
+
+    @VisibleForTesting
+    public CellularNetwork(List<String> plmnList) {
+        mPlmnList = plmnList;
+    }
+
+    /**
+     * Parse a CellularNetwork from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link CellularNetwork}
+     * @throws ProtocolException
+     * @throws BufferUnderflowException
+     */
+    public static CellularNetwork parse(ByteBuffer payload) throws ProtocolException {
+        int ieiType = payload.get() & 0xFF;
+        int ieiSize = payload.get() & IEI_CONTENT_LENGTH_MASK;
+
+        // Skip this IEI if it is an unsupported type.
+        if (ieiType != IEI_TYPE_PLMN_LIST) {
+            Log.e(TAG, "Ignore unsupported IEI Type: " + ieiType);
+            // Advance the buffer position to the next IEI.
+            payload.position(payload.position() + ieiSize);
+            return null;
+        }
+
+        // Get PLMN count.
+        int plmnCount = payload.get() & 0xFF;
+
+        // Verify IEI size with PLMN count.  The IEI size contained the PLMN count field plus
+        // the bytes for the PLMNs.
+        if (ieiSize != (plmnCount * PLMN_DATA_BYTES + 1)) {
+            throw new ProtocolException("IEI size and PLMN count mismatched: IEI Size=" + ieiSize
+                    + " PLMN Count=" + plmnCount);
+        }
+
+        // Process each PLMN.
+        List<String> plmnList = new ArrayList<>();
+        while (plmnCount > 0) {
+            plmnList.add(parsePlmn(payload));
+            plmnCount--;
+        }
+        return new CellularNetwork(plmnList);
+    }
+
+    public List<String> getPlmns() {
+        return Collections.unmodifiableList(mPlmnList);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof CellularNetwork)) {
+            return false;
+        }
+        CellularNetwork that = (CellularNetwork) thatObject;
+        return mPlmnList.equals(that.mPlmnList);
+    }
+
+    @Override
+    public int hashCode() {
+        return mPlmnList.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "CellularNetwork{mPlmnList=" + mPlmnList + "}";
+    }
+
+    /**
+     * Parse the PLMN information from the given buffer.  A string representing a hex value
+     * of |MCC|MNC| will be returned.
+     *
+     * PLMN Coding Format:
+     * b7                         b0
+     * | MCC Digit 2 | MCC Digit 1 |
+     * | MNC Digit 3 | MCC Digit 3 |
+     * | MNC Digit 2 | MNC Digit 1 |
+     *
+     * @param payload The buffer to read from.
+     * @return {@Link String}
+     * @throws BufferUnderflowException
+     */
+    private static String parsePlmn(ByteBuffer payload) {
+        byte[] plmn = new byte[PLMN_DATA_BYTES];
+        payload.get(plmn);
+
+        // Formatted as | MCC Digit 1 | MCC Digit 2 | MCC Digit 3 |
+        int mcc = ((plmn[0] << 8) & 0xF00) | (plmn[0] & 0x0F0) | (plmn[1] & 0x00F);
+
+        // Formated as |MNC Digit 1 | MNC Digit 2 |
+        int mnc = ((plmn[2] << 4) & 0xF0) | ((plmn[2] >> 4) & 0x0F);
+
+        // The digit 3 of MNC decides if the MNC is 2 or 3 digits number.  When it is equal to
+        // 0xF, MNC is a 2 digit value. Otherwise, it is a 3 digit number.
+        int mncDigit3 = (plmn[1] >> 4) & 0x0F;
+        return (mncDigit3 != MNC_2DIGIT_VALUE)
+                ? String.format("%03x%03x", mcc, (mnc << 4) | mncDigit3)
+                : String.format("%03x%02x", mcc, mnc);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java b/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
new file mode 100644
index 0000000..7cf34c7
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/Constants.java
@@ -0,0 +1,134 @@
+package com.android.server.wifi.hotspot2.anqp;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ANQP related constants (802.11-2012)
+ */
+public class Constants {
+
+    public static final int NIBBLE_MASK = 0x0f;
+    public static final int BYTE_MASK = 0xff;
+    public static final int SHORT_MASK = 0xffff;
+    public static final long INT_MASK = 0xffffffffL;
+    public static final int BYTES_IN_SHORT = 2;
+    public static final int BYTES_IN_INT = 4;
+    public static final int BYTES_IN_EUI48 = 6;
+    public static final long MILLIS_IN_A_SEC = 1000L;
+
+    public static final int HS20_PREFIX = 0x119a6f50;   // Note that this is represented as a LE int
+    public static final int HS20_FRAME_PREFIX = 0x109a6f50;
+    public static final int UTF8_INDICATOR = 1;
+
+    public static final int LANG_CODE_LENGTH = 3;
+    // From IEEE802.11-2012 section 8.4.1.34.
+    public static final int VENUE_INFO_LENGTH = 2;
+
+    public static final int ANQP_QUERY_LIST = 256;
+    public static final int ANQP_VENUE_NAME = 258;
+    public static final int ANQP_ROAMING_CONSORTIUM = 261;
+    public static final int ANQP_IP_ADDR_AVAILABILITY = 262;
+    public static final int ANQP_NAI_REALM = 263;
+    public static final int ANQP_3GPP_NETWORK = 264;
+    public static final int ANQP_DOM_NAME = 268;
+    public static final int ANQP_VENDOR_SPEC = 56797;
+
+    public static final int HS_QUERY_LIST = 1;
+    public static final int HS_FRIENDLY_NAME = 3;
+    public static final int HS_WAN_METRICS = 4;
+    public static final int HS_CONN_CAPABILITY = 5;
+    public static final int HS_NAI_HOME_REALM_QUERY = 6;
+    public static final int HS_OSU_PROVIDERS = 8;
+    public static final int HS_ICON_REQUEST = 10;
+
+    public enum ANQPElementType {
+        ANQPQueryList,
+        ANQPVenueName,
+        ANQPRoamingConsortium,
+        ANQPIPAddrAvailability,
+        ANQPNAIRealm,
+        ANQP3GPPNetwork,
+        ANQPDomName,
+        ANQPVendorSpec,
+        HSQueryList,
+        HSFriendlyName,
+        HSWANMetrics,
+        HSConnCapability,
+        HSNAIHomeRealmQuery,
+        HSOSUProviders,
+        HSIconRequest
+    }
+
+    private static final Map<Integer, ANQPElementType> sAnqpMap = new HashMap<>();
+    private static final Map<Integer, ANQPElementType> sHs20Map = new HashMap<>();
+    private static final Map<ANQPElementType, Integer> sRevAnqpmap =
+            new EnumMap<>(ANQPElementType.class);
+    private static final Map<ANQPElementType, Integer> sRevHs20map =
+            new EnumMap<>(ANQPElementType.class);
+
+    static {
+        sAnqpMap.put(ANQP_QUERY_LIST, ANQPElementType.ANQPQueryList);
+        sAnqpMap.put(ANQP_VENUE_NAME, ANQPElementType.ANQPVenueName);
+        sAnqpMap.put(ANQP_ROAMING_CONSORTIUM, ANQPElementType.ANQPRoamingConsortium);
+        sAnqpMap.put(ANQP_IP_ADDR_AVAILABILITY, ANQPElementType.ANQPIPAddrAvailability);
+        sAnqpMap.put(ANQP_NAI_REALM, ANQPElementType.ANQPNAIRealm);
+        sAnqpMap.put(ANQP_3GPP_NETWORK, ANQPElementType.ANQP3GPPNetwork);
+        sAnqpMap.put(ANQP_DOM_NAME, ANQPElementType.ANQPDomName);
+        sAnqpMap.put(ANQP_VENDOR_SPEC, ANQPElementType.ANQPVendorSpec);
+
+        sHs20Map.put(HS_QUERY_LIST, ANQPElementType.HSQueryList);
+        sHs20Map.put(HS_FRIENDLY_NAME, ANQPElementType.HSFriendlyName);
+        sHs20Map.put(HS_WAN_METRICS, ANQPElementType.HSWANMetrics);
+        sHs20Map.put(HS_CONN_CAPABILITY, ANQPElementType.HSConnCapability);
+        sHs20Map.put(HS_NAI_HOME_REALM_QUERY, ANQPElementType.HSNAIHomeRealmQuery);
+        sHs20Map.put(HS_OSU_PROVIDERS, ANQPElementType.HSOSUProviders);
+        sHs20Map.put(HS_ICON_REQUEST, ANQPElementType.HSIconRequest);
+
+        for (Map.Entry<Integer, ANQPElementType> entry : sAnqpMap.entrySet()) {
+            sRevAnqpmap.put(entry.getValue(), entry.getKey());
+        }
+        for (Map.Entry<Integer, ANQPElementType> entry : sHs20Map.entrySet()) {
+            sRevHs20map.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    public static ANQPElementType mapANQPElement(int id) {
+        return sAnqpMap.get(id);
+    }
+
+    public static ANQPElementType mapHS20Element(int id) {
+        return sHs20Map.get(id);
+    }
+
+    public static Integer getANQPElementID(ANQPElementType elementType) {
+        return sRevAnqpmap.get(elementType);
+    }
+
+    public static Integer getHS20ElementID(ANQPElementType elementType) {
+        return sRevHs20map.get(elementType);
+    }
+
+    public static boolean hasBaseANQPElements(Collection<ANQPElementType> elements) {
+        if (elements == null) {
+            return false;
+        }
+        for (ANQPElementType element : elements) {
+            if (sRevAnqpmap.containsKey(element)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean hasR2Elements(List<ANQPElementType> elements) {
+        return elements.contains(ANQPElementType.HSOSUProviders);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/DomainNameElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/DomainNameElement.java
new file mode 100644
index 0000000..35b3956
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/DomainNameElement.java
@@ -0,0 +1,93 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Domain Name ANQP Element, IEEE802.11-2012 section 8.4.4.15.
+ *
+ * Format:
+ * | Domain Name Field #1 (optional) | ...
+ *            variable
+ *
+ * Domain Name Field Format:
+ * | Length | Domain Name |
+ *      1       variable
+ */
+public class DomainNameElement extends ANQPElement {
+    private final List<String> mDomains;
+
+    @VisibleForTesting
+    public DomainNameElement(List<String> domains) {
+        super(Constants.ANQPElementType.ANQPDomName);
+        mDomains = domains;
+    }
+
+    /**
+     * Parse a DomainNameElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link DomainNameElement}
+     * @throws BufferUnderflowException
+     */
+    public static DomainNameElement parse(ByteBuffer payload) {
+        List<String> domains = new ArrayList<>();
+        while (payload.hasRemaining()) {
+            // Use latin-1 to decode for now - safe for ASCII and retains encoding
+            domains.add(ByteBufferReader.readStringWithByteLength(
+                    payload, StandardCharsets.ISO_8859_1));
+        }
+        return new DomainNameElement(domains);
+    }
+
+    public List<String> getDomains() {
+        return Collections.unmodifiableList(mDomains);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof DomainNameElement)) {
+            return false;
+        }
+        DomainNameElement that = (DomainNameElement) thatObject;
+        return mDomains.equals(that.mDomains);
+    }
+
+    @Override
+    public int hashCode() {
+        return mDomains.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "DomainName{" +
+                "mDomains=" + mDomains +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/GenericBlobElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/GenericBlobElement.java
similarity index 92%
rename from service/java/com/android/server/wifi/anqp/GenericBlobElement.java
rename to service/java/com/android/server/wifi/hotspot2/anqp/GenericBlobElement.java
index eff130d..f3279c8 100644
--- a/service/java/com/android/server/wifi/anqp/GenericBlobElement.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/GenericBlobElement.java
@@ -1,4 +1,4 @@
-package com.android.server.wifi.anqp;
+package com.android.server.wifi.hotspot2.anqp;
 
 import com.android.server.wifi.hotspot2.Utils;
 
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/HSConnectionCapabilityElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/HSConnectionCapabilityElement.java
new file mode 100644
index 0000000..2c9a2b3
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/HSConnectionCapabilityElement.java
@@ -0,0 +1,87 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Connection Capability vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.5
+ *
+ * Format:
+ * | ProtoPort Tuple #1 (optiional) | ....
+ *                4
+ */
+public class HSConnectionCapabilityElement extends ANQPElement {
+    private final List<ProtocolPortTuple> mStatusList;
+
+    @VisibleForTesting
+    public HSConnectionCapabilityElement(List<ProtocolPortTuple> statusList) {
+        super(Constants.ANQPElementType.HSConnCapability);
+        mStatusList = statusList;
+    }
+
+    /**
+     * Parse a HSConnectionCapabilityElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link HSConnectionCapabilityElement}
+     * @throws BufferUnderflowException
+     */
+    public static HSConnectionCapabilityElement parse(ByteBuffer payload) {
+        List<ProtocolPortTuple> statusList = new ArrayList<>();
+        while (payload.hasRemaining()) {
+            statusList.add(ProtocolPortTuple.parse(payload));
+        }
+        return new HSConnectionCapabilityElement(statusList);
+    }
+
+    public List<ProtocolPortTuple> getStatusList() {
+        return Collections.unmodifiableList(mStatusList);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof HSConnectionCapabilityElement)) {
+            return false;
+        }
+        HSConnectionCapabilityElement that = (HSConnectionCapabilityElement) thatObject;
+        return mStatusList.equals(that.mStatusList);
+    }
+
+    @Override
+    public int hashCode() {
+        return mStatusList.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "HSConnectionCapability{" +
+                "mStatusList=" + mStatusList +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java
new file mode 100644
index 0000000..c6794c8
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElement.java
@@ -0,0 +1,94 @@
+package com.android.server.wifi.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Operator Friendly Name vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.3.
+ *
+ * Format:
+ *
+ * | Operator Name Duple #1 (optional) | ...
+ *          variable
+ *
+ * | Operator Name Duple #N (optional) |
+ *             variable
+ */
+public class HSFriendlyNameElement extends ANQPElement {
+    /**
+     * Maximum length for an Operator Name.  Refer to Hotspot 2.0 (Release 2) Technical
+     * Specification section 4.3 for more info.
+     */
+    @VisibleForTesting
+    public static final int MAXIMUM_OPERATOR_NAME_LENGTH = 252;
+
+    private final List<I18Name> mNames;
+
+    @VisibleForTesting
+    public HSFriendlyNameElement(List<I18Name> names) {
+        super(Constants.ANQPElementType.HSFriendlyName);
+        mNames = names;
+    }
+
+    /**
+     * Parse a HSFriendlyNameElement from the given buffer.
+     *
+     * @param payload The buffer to read from
+     * @return {@link HSFriendlyNameElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static HSFriendlyNameElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        List<I18Name> names = new ArrayList<I18Name>();
+        while (payload.hasRemaining()) {
+            I18Name name = I18Name.parse(payload);
+            // Verify that the number of bytes for the operator name doesn't exceed the max
+            // allowed.
+            int textBytes = name.getText().getBytes(StandardCharsets.UTF_8).length;
+            if (textBytes > MAXIMUM_OPERATOR_NAME_LENGTH) {
+                throw new ProtocolException("Operator Name exceeds the maximum allowed "
+                        + textBytes);
+            }
+            names.add(name);
+        }
+        return new HSFriendlyNameElement(names);
+    }
+
+    public List<I18Name> getNames() {
+        return Collections.unmodifiableList(mNames);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof HSFriendlyNameElement)) {
+            return false;
+        }
+        HSFriendlyNameElement that = (HSFriendlyNameElement) thatObject;
+        return mNames.equals(that.mNames);
+    }
+
+    @Override
+    public int hashCode() {
+        return mNames.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "HSFriendlyName{" +
+                "mNames=" + mNames +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElement.java
new file mode 100644
index 0000000..b55fefb
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElement.java
@@ -0,0 +1,177 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * The WAN Metrics vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.4
+ *
+ * Format:
+ * | WAN Info | Downlink Speed | Uplink Speed | Downlink Load | Uplink Load | LMD |
+ *      1             4               4               1              1         2
+ *
+ * WAN Info Format:
+ * | Link Status | Symmetric Link | At Capacity | Reserved |
+ *      B0 B1            B2             B3        B4 - B7
+ */
+public class HSWanMetricsElement extends ANQPElement {
+    public static final int LINK_STATUS_RESERVED = 0;
+    public static final int LINK_STATUS_UP = 1;
+    public static final int LINK_STATUS_DOWN = 2;
+    public static final int LINK_STATUS_TEST = 3;
+
+    @VisibleForTesting
+    public static final int EXPECTED_BUFFER_SIZE = 13;
+
+    @VisibleForTesting
+    public static final int LINK_STATUS_MASK = (1 << 0 | 1 << 1);
+
+    @VisibleForTesting
+    public static final int SYMMETRIC_LINK_MASK = 1 << 2;
+
+    @VisibleForTesting
+    public static final int AT_CAPACITY_MASK = 1 << 3;
+
+    private static final int MAX_LOAD = 256;
+
+    private final int mStatus;
+    private final boolean mSymmetric;
+    private final boolean mCapped;
+    private final long mDownlinkSpeed;
+    private final long mUplinkSpeed;
+    private final int mDownlinkLoad;
+    private final int mUplinkLoad;
+    private final int mLMD;     // Load Measurement Duration.
+
+    @VisibleForTesting
+    public HSWanMetricsElement(int status, boolean symmetric, boolean capped, long downlinkSpeed,
+            long uplinkSpeed, int downlinkLoad, int uplinkLoad, int lmd) {
+        super(Constants.ANQPElementType.HSWANMetrics);
+        mStatus = status;
+        mSymmetric = symmetric;
+        mCapped = capped;
+        mDownlinkSpeed = downlinkSpeed;
+        mUplinkSpeed = uplinkSpeed;
+        mDownlinkLoad = downlinkLoad;
+        mUplinkLoad = uplinkLoad;
+        mLMD = lmd;
+    }
+
+    /**
+     * Parse a HSWanMetricsElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link HSWanMetricsElement}
+     * @throws ProtocolException
+     */
+    public static HSWanMetricsElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        if (payload.remaining() != EXPECTED_BUFFER_SIZE) {
+            throw new ProtocolException("Unexpected buffer size: " + payload.remaining());
+        }
+
+        int wanInfo = payload.get() & 0xFF;
+        int status = wanInfo & LINK_STATUS_MASK;
+        boolean symmetric = (wanInfo & SYMMETRIC_LINK_MASK) != 0;
+        boolean capped = (wanInfo & AT_CAPACITY_MASK) != 0;
+        long downlinkSpeed = ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 4)
+                & 0xFFFFFFFFL;
+        long uplinkSpeed = ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 4)
+                & 0xFFFFFFFFL;
+        int downlinkLoad = payload.get() & 0xFF;
+        int uplinkLoad = payload.get() & 0xFF;
+        int lmd = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2) & 0xFFFF;
+        return new HSWanMetricsElement(status, symmetric, capped, downlinkSpeed, uplinkSpeed,
+                downlinkLoad, uplinkLoad, lmd);
+    }
+
+    public int getStatus() {
+        return mStatus;
+    }
+
+    public boolean isSymmetric() {
+        return mSymmetric;
+    }
+
+    public boolean isCapped() {
+        return mCapped;
+    }
+
+    public long getDownlinkSpeed() {
+        return mDownlinkSpeed;
+    }
+
+    public long getUplinkSpeed() {
+        return mUplinkSpeed;
+    }
+
+    public int getDownlinkLoad() {
+        return mDownlinkLoad;
+    }
+
+    public int getUplinkLoad() {
+        return mUplinkLoad;
+    }
+
+    public int getLMD() {
+        return mLMD;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof HSWanMetricsElement)) {
+            return false;
+        }
+        HSWanMetricsElement that = (HSWanMetricsElement) thatObject;
+        return mStatus == that.mStatus
+                && mSymmetric == that.mSymmetric
+                && mCapped == that.mCapped
+                && mDownlinkSpeed == that.mDownlinkSpeed
+                && mUplinkSpeed == that.mUplinkSpeed
+                && mDownlinkLoad == that.mDownlinkLoad
+                && mUplinkLoad == that.mUplinkLoad
+                && mLMD == that.mLMD;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) (mStatus + mDownlinkSpeed + mUplinkSpeed + mDownlinkLoad
+                + mUplinkLoad + mLMD);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("HSWanMetrics{mStatus=%s, mSymmetric=%s, mCapped=%s, " +
+                "mDlSpeed=%d, mUlSpeed=%d, mDlLoad=%f, mUlLoad=%f, mLMD=%d}",
+                mStatus, mSymmetric, mCapped,
+                mDownlinkSpeed, mUplinkSpeed,
+                mDownlinkLoad * 100.0 / MAX_LOAD,
+                mUplinkLoad * 100.0 / MAX_LOAD,
+                mLMD);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java b/service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java
new file mode 100644
index 0000000..3d44b0b
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/I18Name.java
@@ -0,0 +1,122 @@
+/*
+ * 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.hotspot2.anqp;
+
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+/**
+ * A generic Internationalized name field used in the Operator Friendly Name ANQP element
+ * (see HS2.0 R2 Spec 4.2) and the Venue Name ANQP element (see 802.11-2012 8.4.4.4).
+ *
+ * Format:
+ *
+ * | Length | Language Code |   Name   |
+ *      1           3         variable
+ */
+public class I18Name {
+    @VisibleForTesting
+    public static final int LANGUAGE_CODE_LENGTH = 3;
+
+    @VisibleForTesting
+    public static final int MINIMUM_LENGTH = LANGUAGE_CODE_LENGTH;
+
+    private final String mLanguage;
+    private final Locale mLocale;
+    private final String mText;
+
+    @VisibleForTesting
+    public I18Name(String language, Locale locale, String text) {
+        mLanguage = language;
+        mLocale = locale;
+        mText = text;
+    }
+
+    /**
+     * Parse a I18Name from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link I18Name}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static I18Name parse(ByteBuffer payload) throws ProtocolException {
+        // Retrieve the length field.
+        int length = payload.get() & 0xFF;
+
+        // Check for the minimum required length.
+        if (length < MINIMUM_LENGTH) {
+            throw new ProtocolException("Invalid length: " + length);
+        }
+
+        // Read the language string.
+        String language = ByteBufferReader.readString(
+                payload, LANGUAGE_CODE_LENGTH, StandardCharsets.US_ASCII).trim();
+        Locale locale = Locale.forLanguageTag(language);
+
+        // Read the text string.
+        String text = ByteBufferReader.readString(payload, length - LANGUAGE_CODE_LENGTH,
+                StandardCharsets.UTF_8);
+        return new I18Name(language, locale, text);
+    }
+
+    public String getLanguage() {
+        return mLanguage;
+    }
+
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof I18Name)) {
+            return false;
+        }
+
+        I18Name that = (I18Name) thatObject;
+        return TextUtils.equals(mLanguage, that.mLanguage)
+                && TextUtils.equals(mText, that.mText);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mLanguage.hashCode();
+        result = 31 * result + mText.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return mText + ':' + mLocale.getLanguage();
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java
new file mode 100644
index 0000000..ed8f8c1
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElement.java
@@ -0,0 +1,149 @@
+package com.android.server.wifi.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The IP Address Type availability ANQP Element, IEEE802.11-2012 section 8.4.4.9
+ *
+ * Format:
+ *
+ * | IP Address |
+ *       1
+ * b0                           b7
+ * | IPv6 Address | IPv4 Address |
+ *     2 bits          6 bits
+ *
+ * IPv4 Address field values:
+ * 0 - Address type not available
+ * 1 - Public IPv4 address available
+ * 2 - Port-restricted IPv4 address available
+ * 3 - Single NATed private IPv4 address available
+ * 4 - Single NATed private IPv4 address available
+ * 5 - Port-restricted IPv4 address and single NATed IPv4 address available
+ * 6 - Port-restricted IPv4 address and double NATed IPv4 address available
+ * 7 - Availability of the address type is not known
+ *
+ * IPv6 Address field values:
+ * 0 - Address type not available
+ * 1 - Address type not available
+ * 2 - Availability of the address type not known
+ *
+ */
+public class IPAddressTypeAvailabilityElement extends ANQPElement {
+    @VisibleForTesting
+    public static final int EXPECTED_BUFFER_LENGTH = 1;
+
+    /**
+     * Constants for IPv4 availability.
+     */
+    public static final int IPV4_NOT_AVAILABLE = 0;
+    public static final int IPV4_PUBLIC = 1;
+    public static final int IPV4_PORT_RESTRICTED = 2;
+    public static final int IPV4_SINGLE_NAT = 3;
+    public static final int IPV4_DOUBLE_NAT = 4;
+    public static final int IPV4_PORT_RESTRICTED_AND_SINGLE_NAT = 5;
+    public static final int IPV4_PORT_RESTRICTED_AND_DOUBLE_NAT = 6;
+    public static final int IPV4_UNKNOWN = 7;
+    private static final Set<Integer> IPV4_AVAILABILITY = new HashSet<Integer>();
+    static {
+        IPV4_AVAILABILITY.add(IPV4_NOT_AVAILABLE);
+        IPV4_AVAILABILITY.add(IPV4_PUBLIC);
+        IPV4_AVAILABILITY.add(IPV4_PORT_RESTRICTED);
+        IPV4_AVAILABILITY.add(IPV4_SINGLE_NAT);
+        IPV4_AVAILABILITY.add(IPV4_DOUBLE_NAT);
+        IPV4_AVAILABILITY.add(IPV4_PORT_RESTRICTED_AND_SINGLE_NAT);
+        IPV4_AVAILABILITY.add(IPV4_PORT_RESTRICTED_AND_DOUBLE_NAT);
+    }
+
+    /**
+     * Constants for IPv6 availability.
+     */
+    public static final int IPV6_NOT_AVAILABLE = 0;
+    public static final int IPV6_AVAILABLE = 1;
+    public static final int IPV6_UNKNOWN = 2;
+    private static final Set<Integer> IPV6_AVAILABILITY = new HashSet<Integer>();
+    static {
+        IPV6_AVAILABILITY.add(IPV6_NOT_AVAILABLE);
+        IPV6_AVAILABILITY.add(IPV6_AVAILABLE);
+        IPV6_AVAILABILITY.add(IPV6_UNKNOWN);
+    }
+
+    private static final int IPV4_AVAILABILITY_MASK = 0x3F;
+    private static final int IPV6_AVAILABILITY_MASK = 0x3;
+
+    private final int mV4Availability;
+    private final int mV6Availability;
+
+    @VisibleForTesting
+    public IPAddressTypeAvailabilityElement(int v4Availability, int v6Availability) {
+        super(Constants.ANQPElementType.ANQPIPAddrAvailability);
+        mV4Availability = v4Availability;
+        mV6Availability = v6Availability;
+    }
+
+    /**
+     * Parse an IPAddressTypeAvailabilityElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link IPAddressTypeAvailabilityElement}
+     * @throws ProtocolException
+     */
+    public static IPAddressTypeAvailabilityElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        if (payload.remaining() != EXPECTED_BUFFER_LENGTH) {
+            throw new ProtocolException("Unexpected buffer length: " + payload.remaining());
+        }
+
+        int ipField = payload.get() & 0xFF;
+
+        int v6Availability = ipField & IPV6_AVAILABILITY_MASK;
+        if (!IPV6_AVAILABILITY.contains(v6Availability)) {
+            v6Availability = IPV6_UNKNOWN;
+        }
+
+        int v4Availability = (ipField >> 2) & IPV4_AVAILABILITY_MASK;
+        if (!IPV4_AVAILABILITY.contains(v4Availability)) {
+            v4Availability = IPV4_UNKNOWN;
+        }
+
+        return new IPAddressTypeAvailabilityElement(v4Availability, v6Availability);
+    }
+
+    public int getV4Availability() {
+        return mV4Availability;
+    }
+
+    public int getV6Availability() {
+        return mV6Availability;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof IPAddressTypeAvailabilityElement)) {
+            return false;
+        }
+        IPAddressTypeAvailabilityElement that = (IPAddressTypeAvailabilityElement) thatObject;
+        return mV4Availability == that.mV4Availability && mV6Availability == that.mV6Availability;
+    }
+
+    @Override
+    public int hashCode() {
+        return mV4Availability << 2 + mV6Availability;
+    }
+
+    @Override
+    public String toString() {
+        return "IPAddressTypeAvailability{" +
+                "mV4Availability=" + mV4Availability +
+                ", mV6Availability=" + mV6Availability +
+                '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/anqp/IconInfo.java b/service/java/com/android/server/wifi/hotspot2/anqp/IconInfo.java
similarity index 81%
rename from service/java/com/android/server/wifi/anqp/IconInfo.java
rename to service/java/com/android/server/wifi/hotspot2/anqp/IconInfo.java
index 56fcf45..c961bbe 100644
--- a/service/java/com/android/server/wifi/anqp/IconInfo.java
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/IconInfo.java
@@ -1,11 +1,13 @@
-package com.android.server.wifi.anqp;
+package com.android.server.wifi.hotspot2.anqp;
 
 import java.net.ProtocolException;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 
-import static com.android.server.wifi.anqp.Constants.SHORT_MASK;
+import static com.android.server.wifi.hotspot2.anqp.Constants.SHORT_MASK;
+
+import com.android.server.wifi.ByteBufferReader;
 
 /**
  * The Icons available OSU Providers sub field, as specified in
@@ -26,10 +28,10 @@
 
         mWidth = payload.getShort() & SHORT_MASK;
         mHeight = payload.getShort() & SHORT_MASK;
-        mLanguage = Constants.getTrimmedString(payload,
-                Constants.LANG_CODE_LENGTH, StandardCharsets.US_ASCII);
-        mIconType = Constants.getPrefixedString(payload, 1, StandardCharsets.US_ASCII);
-        mFileName = Constants.getPrefixedString(payload, 1, StandardCharsets.UTF_8);
+        mLanguage = ByteBufferReader.readString(
+                payload, Constants.LANG_CODE_LENGTH, StandardCharsets.US_ASCII).trim();
+        mIconType = ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.US_ASCII);
+        mFileName = ByteBufferReader.readStringWithByteLength(payload, StandardCharsets.UTF_8);
     }
 
     public int getWidth() {
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java
new file mode 100644
index 0000000..7b31449
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmData.java
@@ -0,0 +1,122 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The NAI Realm Data ANQP sub-element, IEEE802.11-2012 section 8.4.4.10 figure 8-418.
+ *
+ * Format:
+ * | Length | Encoding | NAIRealm Length | NAIRealm | EAPMethod Count | EAPMethod #1 (optional) |
+ *     2         1               1         variable          1                  variable
+ */
+public class NAIRealmData {
+    /**
+     * Mask for determining NAI Realm String encoding type.
+     */
+    @VisibleForTesting
+    public static final int NAI_ENCODING_UTF8_MASK = 0x1;
+
+    @VisibleForTesting
+    public static final String NAI_REALM_STRING_SEPARATOR = ";";
+
+    private final List<String> mRealms;
+    private final List<EAPMethod> mEAPMethods;
+
+    @VisibleForTesting
+    public NAIRealmData(List<String> realms, List<EAPMethod> eapMethods) {
+        mRealms = realms;
+        mEAPMethods = eapMethods;
+    }
+
+    /**
+     * Parse a NAIRealmData from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link NAIRealmElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static NAIRealmData parse(ByteBuffer payload) throws ProtocolException {
+        // Read and verify the length field.
+        int length = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2)
+                & 0xFFFF;
+        if (length > payload.remaining()) {
+            throw new ProtocolException("Invalid data length: " + length);
+        }
+
+        // Read the encoding field.
+        boolean utf8 = (payload.get() & NAI_ENCODING_UTF8_MASK) != 0;
+
+        // Read the realm string.
+        String realm = ByteBufferReader.readStringWithByteLength(
+                payload, utf8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII);
+        List<String> realmList = Arrays.asList(realm.split(NAI_REALM_STRING_SEPARATOR));
+
+        // Read the EAP methods.
+        int methodCount = payload.get() & 0xFF;
+        List<EAPMethod> eapMethodList = new ArrayList<>();
+        while (methodCount > 0) {
+            eapMethodList.add(EAPMethod.parse(payload));
+            methodCount--;
+        }
+        return new NAIRealmData(realmList, eapMethodList);
+    }
+
+    public List<String> getRealms() {
+        return Collections.unmodifiableList(mRealms);
+    }
+
+    public List<EAPMethod> getEAPMethods() {
+        return Collections.unmodifiableList(mEAPMethods);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof NAIRealmData)) {
+            return false;
+        }
+        NAIRealmData that = (NAIRealmData) thatObject;
+        return mRealms.equals(that.mRealms) && mEAPMethods.equals(that.mEAPMethods);
+    }
+
+    @Override
+    public int hashCode() {
+        return mRealms.hashCode() * 31 + mEAPMethods.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "NAIRealmElement{mRealms=" + mRealms + " mEAPMethods=" + mEAPMethods + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java
new file mode 100644
index 0000000..6f18bad
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/NAIRealmElement.java
@@ -0,0 +1,93 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * The NAI (Network Access Identifier) Realm ANQP Element, IEEE802.11-2012 section 8.4.4.10.
+ *
+ * Format:
+ * | NAI Realm Count (optional) | NAI Realm Data #1 (optional) | ....
+ *             2                         variable
+ */
+public class NAIRealmElement extends ANQPElement {
+    private final List<NAIRealmData> mRealmDataList;
+
+    @VisibleForTesting
+    public NAIRealmElement(List<NAIRealmData> realmDataList) {
+        super(Constants.ANQPElementType.ANQPNAIRealm);
+        mRealmDataList = realmDataList;
+    }
+
+    /**
+     * Parse a NAIRealmElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link NAIRealmElement}
+     * @throws BufferUnderflowException
+     */
+    public static NAIRealmElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        List<NAIRealmData> realmDataList = new ArrayList<>();
+        if (payload.hasRemaining()) {
+            int count = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2)
+                    & 0xFFFF;
+            while (count > 0) {
+                realmDataList.add(NAIRealmData.parse(payload));
+                count--;
+            }
+        }
+        return new NAIRealmElement(realmDataList);
+    }
+
+    public List<NAIRealmData> getRealmDataList() {
+        return Collections.unmodifiableList(mRealmDataList);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof NAIRealmElement)) {
+            return false;
+        }
+        NAIRealmElement that = (NAIRealmElement) thatObject;
+        return mRealmDataList.equals(that.mRealmDataList);
+    }
+
+    @Override
+    public int hashCode() {
+        return mRealmDataList.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "NAIRealmElement{mRealmDataList=" + mRealmDataList + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ProtocolPortTuple.java b/service/java/com/android/server/wifi/hotspot2/anqp/ProtocolPortTuple.java
new file mode 100644
index 0000000..c097ad3
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/ProtocolPortTuple.java
@@ -0,0 +1,108 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * The ProtoPort Tuple used by Connection Capability vendor specific ANQP Element,
+ * Wi-Fi Alliance Hotspot 2.0 (Release 2) Technical Specification - Version 5.00,
+ * section 4.5
+ *
+ * Format:
+ * | IP Procotol | Port Number | Status |
+ *        1             2           1
+ */
+public class ProtocolPortTuple {
+    /**
+     * Number of raw bytes needed for the tuple.
+     */
+    @VisibleForTesting
+    public static final int RAW_BYTE_SIZE = 4;
+
+    public static final int PROTO_STATUS_CLOSED = 0;
+    public static final int PROTO_STATUS_OPEN = 1;
+    public static final int PROTO_STATUS_UNKNOWN = 2;
+
+    private final int mProtocol;
+    private final int mPort;
+    private final int mStatus;
+
+    @VisibleForTesting
+    public ProtocolPortTuple(int protocol, int port, int status) {
+        mProtocol = protocol;
+        mPort = port;
+        mStatus = status;
+    }
+
+    /**
+     * Parse a ProtocolPortTuple from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link ProtocolPortTuple}
+     * @throws BufferUnderflowException
+     */
+    public static ProtocolPortTuple parse(ByteBuffer payload) {
+        int protocol = payload.get();
+        int port = (int) ByteBufferReader.readInteger(payload, ByteOrder.LITTLE_ENDIAN, 2)
+                & 0xFFFF;
+        int status = payload.get() & 0xFF;
+        return new ProtocolPortTuple(protocol, port, status);
+    }
+
+    public int getProtocol() {
+        return mProtocol;
+    }
+
+    public int getPort() {
+        return mPort;
+    }
+
+    public int getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof ProtocolPortTuple)) {
+            return false;
+        }
+        ProtocolPortTuple that = (ProtocolPortTuple) thatObject;
+        return mProtocol == that.mProtocol
+                && mPort == that.mPort
+                && mStatus == that.mStatus;
+    }
+
+    @Override
+    public int hashCode() {
+        return (mProtocol * 31 + mPort) * 31 + mStatus;
+    }
+
+    @Override
+    public String toString() {
+        return "ProtocolTuple{" + "mProtocol=" + mProtocol + ", mPort=" + mPort
+                + ", mStatus=" + mStatus + '}';
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/RawByteElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/RawByteElement.java
new file mode 100644
index 0000000..633147d
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/RawByteElement.java
@@ -0,0 +1,70 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * An object holding the raw octets of an ANQP element as provided by the wpa_supplicant.
+ */
+public class RawByteElement extends ANQPElement {
+    private final byte[] mPayload;
+
+    @VisibleForTesting
+    public RawByteElement(Constants.ANQPElementType infoID, byte[] payload) {
+        super(infoID);
+        mPayload = payload;
+    }
+
+    /**
+     * Parse a RawByteElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link HSConnectionCapabilityElement}
+     */
+    public static RawByteElement parse(Constants.ANQPElementType infoID, ByteBuffer payload) {
+        byte[] rawBytes = new byte[payload.remaining()];
+        if (payload.hasRemaining()) {
+            payload.get(rawBytes);
+        }
+        return new RawByteElement(infoID, rawBytes);
+    }
+
+    public byte[] getPayload() {
+        return mPayload;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof RawByteElement)) {
+            return false;
+        }
+        RawByteElement that = (RawByteElement) thatObject;
+        return getID() == that.getID() && Arrays.equals(mPayload, that.mPayload);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mPayload);
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java
new file mode 100644
index 0000000..a40e9d6
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElement.java
@@ -0,0 +1,88 @@
+package com.android.server.wifi.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+import com.android.server.wifi.hotspot2.Utils;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Roaming Consortium ANQP Element, IEEE802.11-2012 section 8.4.4.7
+ *
+ ** Format:
+ *
+ * | OI Duple #1 (optional) | ...
+ *         variable
+ *
+ * | OI Length |     OI     |
+ *       1        variable
+ *
+ */
+public class RoamingConsortiumElement extends ANQPElement {
+    @VisibleForTesting
+    public static final int MINIMUM_OI_LENGTH = Byte.BYTES;
+
+    @VisibleForTesting
+    public static final int MAXIMUM_OI_LENGTH = Long.BYTES;
+
+    private final List<Long> mOIs;
+
+    @VisibleForTesting
+    public RoamingConsortiumElement(List<Long> ois) {
+        super(Constants.ANQPElementType.ANQPRoamingConsortium);
+        mOIs = ois;
+    }
+
+    /**
+     * Parse a VenueNameElement from the given payload.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link RoamingConsortiumElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static RoamingConsortiumElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        List<Long> OIs = new ArrayList<Long>();
+        while (payload.hasRemaining()) {
+            int length = payload.get() & 0xFF;
+            if (length < MINIMUM_OI_LENGTH || length > MAXIMUM_OI_LENGTH) {
+                throw new ProtocolException("Bad OI length: " + length);
+            }
+            OIs.add(ByteBufferReader.readInteger(payload, ByteOrder.BIG_ENDIAN, length));
+        }
+        return new RoamingConsortiumElement(OIs);
+    }
+
+    public List<Long> getOIs() {
+        return Collections.unmodifiableList(mOIs);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof RoamingConsortiumElement)) {
+            return false;
+        }
+        RoamingConsortiumElement that = (RoamingConsortiumElement) thatObject;
+        return mOIs.equals(that.mOIs);
+    }
+
+    @Override
+    public int hashCode() {
+        return mOIs.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "RoamingConsortium{mOis=[" + Utils.roamingConsortiumsToString(mOIs) + "]}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElement.java
new file mode 100644
index 0000000..d9795c6
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElement.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * The 3GPP Cellular Network ANQP Element, IEEE802.11-2012 section 8.4.4.11.
+ * The value is embedded in a Generic container User Data (GUD).
+ * Refer to Annex A of 3GPP TS 24.234 version 11.3.0 for more info:
+ * (http://www.etsi.org/deliver/etsi_ts/124200_124299/124234/11.03.00_60/ts_124234v110300p.pdf).
+ *
+ * Format:
+ * | GUD Version | Length | IEI 1 | ... | IEI N|
+ *        1           1    variable
+ *
+ */
+public class ThreeGPPNetworkElement extends ANQPElement {
+    /**
+     * The expected protocol version number of the Generic container User Data (GUD).
+     */
+    @VisibleForTesting
+    public static final int GUD_VERSION_1 = 0;
+
+    private final List<CellularNetwork> mNetworks;
+
+    @VisibleForTesting
+    public ThreeGPPNetworkElement(List<CellularNetwork> networks) {
+        super(Constants.ANQPElementType.ANQP3GPPNetwork);
+        mNetworks = networks;
+    }
+
+    /**
+     * Parse a ThreeGPPNetworkElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link ThreeGPPNetworkElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static ThreeGPPNetworkElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        // Verify version.
+        int gudVersion = payload.get() & 0xFF;
+        if (gudVersion != GUD_VERSION_1) {
+            throw new ProtocolException("Unsupported GUD version: " + gudVersion);
+        }
+
+        // Verify length.
+        int length = payload.get() & 0xFF;
+        if (length != payload.remaining()) {
+            throw new ProtocolException("Mismatch length and buffer size: length=" + length
+                    + " bufferSize=" + payload.remaining());
+        }
+
+        // Parse each IEI (Information Element Identity) content.
+        List<CellularNetwork> networks = new ArrayList<>();
+        while (payload.hasRemaining()) {
+            CellularNetwork network = CellularNetwork.parse(payload);
+            if (network != null) {
+                networks.add(network);
+            }
+        }
+        return new ThreeGPPNetworkElement(networks);
+    }
+
+    public List<CellularNetwork> getNetworks() {
+        return Collections.unmodifiableList(mNetworks);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof ThreeGPPNetworkElement)) {
+            return false;
+        }
+        ThreeGPPNetworkElement that = (ThreeGPPNetworkElement) thatObject;
+        return mNetworks.equals(that.mNetworks);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return mNetworks.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "ThreeGPPNetwork{mNetworks=" + mNetworks + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java b/service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java
new file mode 100644
index 0000000..9a4e64b
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/VenueNameElement.java
@@ -0,0 +1,115 @@
+/*
+ * 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.hotspot2.anqp;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The Venue Name ANQP Element, IEEE802.11-2012 section 8.4.4.4.
+ *
+ * Format:
+ *
+ * | Venue Info | Venue Name Duple #1 (optional) | ...
+ *      2                  variable
+ *
+ * | Venue Name Duple #N (optional) |
+ *             variable
+ *
+ * Refer to {@link I18Name} for the format of the Venue Name Duple
+ * fields.
+ */
+public class VenueNameElement extends ANQPElement {
+    @VisibleForTesting
+    public static final int VENUE_INFO_LENGTH = 2;
+
+    /**
+     * Maximum length for a Venue Name.  Refer to IEEE802.11-2012 section 8.4.4.4 for more info.
+     */
+    @VisibleForTesting
+    public static final int MAXIMUM_VENUE_NAME_LENGTH = 252;
+
+    private final List<I18Name> mNames;
+
+    @VisibleForTesting
+    public VenueNameElement(List<I18Name> names) {
+        super(Constants.ANQPElementType.ANQPVenueName);
+        mNames = names;
+    }
+
+    /**
+     * Parse a VenueNameElement from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link VenueNameElement}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    public static VenueNameElement parse(ByteBuffer payload)
+            throws ProtocolException {
+        // Skip the Venue Info field, which we don't use.
+        for (int i = 0; i < VENUE_INFO_LENGTH; ++i) {
+            payload.get();
+        }
+
+        List<I18Name> names = new ArrayList<I18Name>();
+        while (payload.hasRemaining()) {
+            I18Name name = I18Name.parse(payload);
+            // Verify that the number of octets for the venue name doesn't exceed the max allowed.
+            int textBytes = name.getText().getBytes(StandardCharsets.UTF_8).length;
+            if (textBytes > MAXIMUM_VENUE_NAME_LENGTH) {
+                throw new ProtocolException("Venue Name exceeds the maximum allowed " + textBytes);
+            }
+            names.add(name);
+        }
+        return new VenueNameElement(names);
+    }
+
+    public List<I18Name> getNames() {
+        return Collections.unmodifiableList(mNames);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof VenueNameElement)) {
+            return false;
+        }
+        VenueNameElement that = (VenueNameElement) thatObject;
+        return mNames.equals(that.mNames);
+    }
+
+    @Override
+    public int hashCode() {
+        return mNames.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "VenueName{ mNames=" + mNames + "}";
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/eap/AuthParam.java b/service/java/com/android/server/wifi/hotspot2/anqp/eap/AuthParam.java
new file mode 100644
index 0000000..ce4edb2
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/eap/AuthParam.java
@@ -0,0 +1,41 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+/**
+ * An Authentication parameter, part of the NAI Realm ANQP element, specified in
+ * IEEE802.11-2012 section 8.4.4.10, table 8-188
+ */
+public abstract class AuthParam {
+    public static final int PARAM_TYPE_EXPANDED_EAP_METHOD = 1;
+    public static final int PARAM_TYPE_NON_EAP_INNER_AUTH_TYPE = 2;
+    public static final int PARAM_TYPE_INNER_AUTH_EAP_METHOD_TYPE = 3;
+    public static final int PARAM_TYPE_EXPANDED_INNER_EAP_METHOD = 4;
+    public static final int PARAM_TYPE_CREDENTIAL_TYPE = 5;
+    public static final int PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE = 6;
+    public static final int PARAM_TYPE_VENDOR_SPECIFIC = 221;
+
+    private final int mAuthTypeID;
+
+    protected AuthParam(int authTypeID) {
+        mAuthTypeID = authTypeID;
+    }
+
+    public int getAuthTypeID() {
+        return mAuthTypeID;
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/eap/CredentialType.java b/service/java/com/android/server/wifi/hotspot2/anqp/eap/CredentialType.java
new file mode 100644
index 0000000..1efc00b
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/eap/CredentialType.java
@@ -0,0 +1,103 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * The Credential Type authentication parameter, IEEE802.11-2012, table 8-188.
+ * Used by both Credential Type and Tunneled EAP Method Credential Type authentication
+ * parameter.
+ *
+ * Format:
+ * | Type |
+ *    1
+ */
+public class CredentialType extends AuthParam {
+    public static final int CREDENTIAL_TYPE_SIM = 1;
+    public static final int CREDENTIAL_TYPE_USIM = 2;
+    public static final int CREDENTIAL_TYPE_NFC = 3;
+    public static final int CREDENTIAL_TYPE_HARDWARE_TOKEN = 4;
+    public static final int CREDENTIAL_TYPE_SOFTWARE_TOKEN = 5;
+    public static final int CREDENTIAL_TYPE_CERTIFICATE = 6;
+    public static final int CREDENTIAL_TYPE_USERNAME_PASSWORD = 7;
+    public static final int CREDENTIAL_TYPE_NONE = 8;
+    public static final int CREDENTIAL_TYPE_ANONYMOUS = 9;
+    public static final int CREDENTIAL_TYPE_VENDOR_SPECIFIC = 10;
+
+    @VisibleForTesting
+    public static final int EXPECTED_LENGTH_VALUE = 1;
+
+    private final int mType;
+
+    @VisibleForTesting
+    public CredentialType(int authType, int credType) {
+        super(authType);
+        mType = credType;
+    }
+
+    /**
+     * Parse a CredentialType from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @param length The length of the data
+     * @param tunneled Flag indicating if this is for a Tunneled EAP Method
+     * @return {@link CredentialType}
+     * @throws ProtocolException
+     * @throws BufferUnderflowException
+     */
+    public static CredentialType parse(ByteBuffer payload, int length, boolean tunneled)
+            throws ProtocolException {
+        if (length != EXPECTED_LENGTH_VALUE) {
+            throw new ProtocolException("Invalid length: " + length);
+        }
+        int credType = payload.get() & 0xFF;
+        int authType = tunneled ? AuthParam.PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE
+                : AuthParam.PARAM_TYPE_CREDENTIAL_TYPE;
+        return new CredentialType(authType, credType);
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        }
+        if (!(thatObject instanceof CredentialType)) {
+            return false;
+        }
+        CredentialType that = (CredentialType) thatObject;
+        return mType == that.mType;
+    }
+
+    @Override
+    public int hashCode() {
+        return mType;
+    }
+
+    @Override
+    public String toString() {
+        return "CredentialType{mType=" + mType + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/eap/EAPMethod.java b/service/java/com/android/server/wifi/hotspot2/anqp/eap/EAPMethod.java
new file mode 100644
index 0000000..6879d41
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/eap/EAPMethod.java
@@ -0,0 +1,154 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An EAP Method part of the NAI Realm ANQP element, specified in
+ * IEEE802.11-2012 section 8.4.4.10, figure 8-420
+ *
+ * Format:
+ * | Length | EAP Method | Auth Param Count | Auth Param #1 (optional) | ....
+ *     1          1               1                 variable
+ */
+public class EAPMethod {
+    private final int mEAPMethodID;
+    private final Map<Integer, Set<AuthParam>> mAuthParams;
+
+    @VisibleForTesting
+    public EAPMethod(int methodID, Map<Integer, Set<AuthParam>> authParams) {
+        mEAPMethodID = methodID;
+        mAuthParams = authParams;
+    }
+
+    /**
+     * Parse a EAPMethod from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link EAPMethod}
+     * @throws ProtocolException
+     * @throws BufferUnderflowException
+     */
+    public static EAPMethod parse(ByteBuffer payload) throws ProtocolException {
+        // Read and verify the length field.
+        int length = payload.get() & 0xFF;
+        if (length > payload.remaining()) {
+            throw new ProtocolException("Invalid data length: " + length);
+        }
+
+        int methodID = payload.get() & 0xFF;
+        int authCount = payload.get() & 0xFF;
+        Map<Integer, Set<AuthParam>> authParams = new HashMap<>();
+        while (authCount > 0) {
+            addAuthParam(authParams, parseAuthParam(payload));
+            authCount--;
+        }
+        return new EAPMethod(methodID, authParams);
+    }
+
+    /**
+     * Parse a AuthParam from the given buffer.
+     *
+     * Format:
+     * | Auth ID | Length | Value |
+     *      1         1    variable
+     *
+     * @param payload The byte buffer to read from
+     * @return {@link AuthParam}
+     * @throws BufferUnderflowException
+     * @throws ProtocolException
+     */
+    private static AuthParam parseAuthParam(ByteBuffer payload) throws ProtocolException {
+        int authID = payload.get() & 0xFF;
+        int length = payload.get() & 0xFF;
+        switch (authID) {
+            case AuthParam.PARAM_TYPE_EXPANDED_EAP_METHOD:
+                return ExpandedEAPMethod.parse(payload, length, false);
+            case AuthParam.PARAM_TYPE_NON_EAP_INNER_AUTH_TYPE:
+                return NonEAPInnerAuth.parse(payload, length);
+            case AuthParam.PARAM_TYPE_INNER_AUTH_EAP_METHOD_TYPE:
+                return InnerAuthEAP.parse(payload, length);
+            case AuthParam.PARAM_TYPE_EXPANDED_INNER_EAP_METHOD:
+                return ExpandedEAPMethod.parse(payload, length, true);
+            case AuthParam.PARAM_TYPE_CREDENTIAL_TYPE:
+                return CredentialType.parse(payload, length, false);
+            case AuthParam.PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE:
+                return CredentialType.parse(payload, length, true);
+            case AuthParam.PARAM_TYPE_VENDOR_SPECIFIC:
+                return VendorSpecificAuth.parse(payload, length);
+            default:
+                throw new ProtocolException("Unknow Auth Type ID: " + authID);
+        }
+    }
+
+    /**
+     * Add an AuthParam to a map of authentication parameters.  It is possible to have
+     * multiple authentication parameters for the same type.
+     *
+     * @param paramsMap The authentication parameter map to add the new parameter to
+     * @param authParam The authentication parameter to add
+     */
+    private static void addAuthParam(Map<Integer, Set<AuthParam>> paramsMap,
+            AuthParam authParam) {
+        Set<AuthParam> authParams = paramsMap.get(authParam.getAuthTypeID());
+        if (authParams == null) {
+            authParams = new HashSet<>();
+            paramsMap.put(authParam.getAuthTypeID(), authParams);
+        }
+        authParams.add(authParam);
+    }
+
+    public Map<Integer, Set<AuthParam>> getAuthParams() {
+        return Collections.unmodifiableMap(mAuthParams);
+    }
+
+    public int getEAPMethodID() {
+        return mEAPMethodID;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        }
+        if (!(thatObject instanceof EAPMethod)) {
+            return false;
+        }
+        EAPMethod that = (EAPMethod) thatObject;
+        return mEAPMethodID == that.mEAPMethodID && mAuthParams.equals(that.mAuthParams);
+    }
+
+    @Override
+    public int hashCode() {
+        return mEAPMethodID * 31 + mAuthParams.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "EAPMethod{mEAPMethodID=" + mEAPMethodID + " mAuthParams=" + mAuthParams + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/eap/ExpandedEAPMethod.java b/service/java/com/android/server/wifi/hotspot2/anqp/eap/ExpandedEAPMethod.java
new file mode 100644
index 0000000..a2a303f
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/eap/ExpandedEAPMethod.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.hotspot2.anqp.eap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ByteBufferReader;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * The Expanded EAP Method authentication parameter, IEEE802.11-2012, table 8-189.
+ * Used by both Expanded EAP Method and Expanded Inner EAP Method.
+ *
+ * Format:
+ * | Vendor ID | Vendor Type |
+ *       3            4
+ */
+public class ExpandedEAPMethod extends AuthParam {
+    public static final int EXPECTED_LENGTH_VALUE = 7;
+
+    private final int mVendorID;
+    private final long mVendorType;
+
+    @VisibleForTesting
+    public ExpandedEAPMethod(int authType, int vendorID, long vendorType) {
+        super(authType);
+        mVendorID = vendorID;
+        mVendorType = vendorType;
+    }
+
+    /**
+     * Parse a ExpandedEAPMethod from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @param length The length of the data
+     * @param inner Flag indicating if this is for an Inner EAP method
+     * @return {@link ExpandedEAPMethod}
+     * @throws ProtocolException
+     * @throws BufferUnderflowException
+     */
+    public static ExpandedEAPMethod parse(ByteBuffer payload, int length, boolean inner)
+            throws ProtocolException {
+        if (length != EXPECTED_LENGTH_VALUE) {
+            throw new ProtocolException("Invalid length value: " + length);
+        }
+
+        // Vendor ID and Vendor Type are expressed in big-endian byte order according to
+        // the spec.
+        int vendorID = (int) ByteBufferReader.readInteger(payload, ByteOrder.BIG_ENDIAN, 3)
+                & 0xFFFFFF;
+        long vendorType = ByteBufferReader.readInteger(payload, ByteOrder.BIG_ENDIAN, 4)
+                & 0xFFFFFFFF;
+
+        int authType = inner ? AuthParam.PARAM_TYPE_EXPANDED_INNER_EAP_METHOD
+                : AuthParam.PARAM_TYPE_EXPANDED_EAP_METHOD;
+        return new ExpandedEAPMethod(authType, vendorID, vendorType);
+    }
+
+    public int getVendorID() {
+        return mVendorID;
+    }
+
+    public long getVendorType() {
+        return mVendorType;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        }
+        if (!(thatObject instanceof ExpandedEAPMethod)) {
+            return false;
+        }
+        ExpandedEAPMethod that = (ExpandedEAPMethod) thatObject;
+        return mVendorID == that.mVendorID && mVendorType == that.mVendorType;
+    }
+
+    @Override
+    public int hashCode() {
+        return (mVendorID) * 31 + (int) mVendorType;
+    }
+
+    @Override
+    public String toString() {
+        return "ExpandedEAPMethod{mVendorID=" + mVendorID + " mVendorType=" + mVendorType + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/eap/InnerAuthEAP.java b/service/java/com/android/server/wifi/hotspot2/anqp/eap/InnerAuthEAP.java
new file mode 100644
index 0000000..a7dbdac
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/eap/InnerAuthEAP.java
@@ -0,0 +1,86 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * The Inner Authentication EAP Method Type authentication parameter, IEEE802.11-2012, table 8-188.
+ *
+ * Format:
+ * | EAP Method ID |
+ *         1
+ */
+public class InnerAuthEAP extends AuthParam {
+    @VisibleForTesting
+    public static final int EXPECTED_LENGTH_VALUE = 1;
+
+    private final int mEAPMethodID;
+
+    @VisibleForTesting
+    public InnerAuthEAP(int eapMethodID) {
+        super(AuthParam.PARAM_TYPE_INNER_AUTH_EAP_METHOD_TYPE);
+        mEAPMethodID = eapMethodID;
+    }
+
+    /**
+     * Parse a InnerAuthEAP from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @param length The length of the data
+     * @return {@link InnerAuthEAP}
+     * @throws ProtocolException
+     * @throws BufferUnderflowException
+     */
+    public static InnerAuthEAP parse(ByteBuffer payload, int length) throws ProtocolException {
+        if (length != EXPECTED_LENGTH_VALUE) {
+            throw new ProtocolException("Invalid length: " + length);
+        }
+        int eapMethodID = payload.get() & 0xFF;
+        return new InnerAuthEAP(eapMethodID);
+    }
+
+    public int getEAPMethodID() {
+        return mEAPMethodID;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        }
+        if (!(thatObject instanceof InnerAuthEAP)) {
+            return false;
+        }
+        InnerAuthEAP that = (InnerAuthEAP) thatObject;
+        return mEAPMethodID == that.mEAPMethodID;
+    }
+
+    @Override
+    public int hashCode() {
+        return mEAPMethodID;
+    }
+
+    @Override
+    public String toString() {
+        return "InnerAuthEAP{mEAPMethodID=" + mEAPMethodID + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/eap/NonEAPInnerAuth.java b/service/java/com/android/server/wifi/hotspot2/anqp/eap/NonEAPInnerAuth.java
new file mode 100644
index 0000000..b693393
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/eap/NonEAPInnerAuth.java
@@ -0,0 +1,109 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The Non-EAP Inner Authentication Type authentication parameter, IEEE802.11-2012, table 8-188.
+ *
+ * Format:
+ * | Type |
+ *    1
+ */
+public class NonEAPInnerAuth extends AuthParam {
+    public static final int AUTH_TYPE_UNKNOWN = 0;
+    public static final int AUTH_TYPE_PAP = 1;
+    public static final int AUTH_TYPE_CHAP = 2;
+    public static final int AUTH_TYPE_MSCHAP = 3;
+    public static final int AUTH_TYPE_MSCHAPV2 = 4;
+
+    private static final Map<String, Integer> AUTH_TYPE_MAP = new HashMap<>();
+    static {
+        AUTH_TYPE_MAP.put("PAP", AUTH_TYPE_PAP);
+        AUTH_TYPE_MAP.put("CHAP", AUTH_TYPE_CHAP);
+        AUTH_TYPE_MAP.put("MS-CHAP", AUTH_TYPE_MSCHAP);
+        AUTH_TYPE_MAP.put("MS-CHAP-V2", AUTH_TYPE_MSCHAPV2);
+    }
+
+    @VisibleForTesting
+    public static final int EXPECTED_LENGTH_VALUE = 1;
+
+    private final int mAuthType;
+
+    public NonEAPInnerAuth(int authType) {
+        super(AuthParam.PARAM_TYPE_NON_EAP_INNER_AUTH_TYPE);
+        mAuthType = authType;
+    }
+
+    /**
+     * Parse a NonEAPInnerAuth from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @param length The length of the data
+     * @return {@link NonEAPInnerAuth}
+     * @throws BufferUnderflowException
+     */
+    public static NonEAPInnerAuth parse(ByteBuffer payload, int length) throws ProtocolException {
+        if (length != EXPECTED_LENGTH_VALUE) {
+            throw new ProtocolException("Invalid length: " + length);
+        }
+        int authType = payload.get() & 0xFF;
+        return new NonEAPInnerAuth(authType);
+    }
+
+    /**
+     * Convert an authentication type string to an integer representation.
+     *
+     * @param typeStr The string of authentication type
+     * @return int
+     */
+    public static int getAuthTypeID(String typeStr) {
+        if (AUTH_TYPE_MAP.containsKey(typeStr)) {
+            return AUTH_TYPE_MAP.get(typeStr).intValue();
+        }
+        return AUTH_TYPE_UNKNOWN;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        }
+        if (!(thatObject instanceof NonEAPInnerAuth)) {
+            return false;
+        }
+        NonEAPInnerAuth that = (NonEAPInnerAuth) thatObject;
+        return mAuthType == that.mAuthType;
+    }
+
+    @Override
+    public int hashCode() {
+        return mAuthType;
+    }
+
+    @Override
+    public String toString() {
+        return "NonEAPInnerAuth{mAuthType=" + mAuthType + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/eap/VendorSpecificAuth.java b/service/java/com/android/server/wifi/hotspot2/anqp/eap/VendorSpecificAuth.java
new file mode 100644
index 0000000..048f0d6
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/anqp/eap/VendorSpecificAuth.java
@@ -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.
+ */
+
+package com.android.server.wifi.hotspot2.anqp.eap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * The Vendor Specific authentication parameter, IEEE802.11-2012, table 8-188.
+ *
+ * Format:
+ * | Data |
+ * variable
+ */
+public class VendorSpecificAuth extends AuthParam {
+    private final byte[] mData;
+
+    @VisibleForTesting
+    public VendorSpecificAuth(byte[] data) {
+        super(AuthParam.PARAM_TYPE_VENDOR_SPECIFIC);
+        mData = data;
+    }
+
+    /**
+     * Parse a VendorSpecificAuth from the given buffer.
+     *
+     * @param payload The byte buffer to read from
+     * @param length The length of the data
+     * @return {@link VendorSpecificAuth}
+     * @throws BufferUnderflowException
+     */
+    public static VendorSpecificAuth parse(ByteBuffer payload, int length) {
+        byte[] data = new byte[length];
+        payload.get(data);
+        return new VendorSpecificAuth(data);
+    }
+
+    public byte[] getData() {
+        return mData;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (thatObject == this) {
+            return true;
+        }
+        if (!(thatObject instanceof VendorSpecificAuth)) {
+            return false;
+        }
+        VendorSpecificAuth that = (VendorSpecificAuth) thatObject;
+        return Arrays.equals(mData, that.mData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mData);
+    }
+
+    @Override
+    public String toString() {
+        return "VendorSpecificAuth{mData=" + Arrays.toString(mData) + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java b/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java
deleted file mode 100644
index 63541ee..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/MOTree.java
+++ /dev/null
@@ -1,273 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import org.xml.sax.SAXException;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-
-public class MOTree {
-    public static final String MgmtTreeTag = "MgmtTree";
-
-    public static final String NodeTag = "Node";
-    public static final String NodeNameTag = "NodeName";
-    public static final String PathTag = "Path";
-    public static final String ValueTag = "Value";
-    public static final String RTPropTag = "RTProperties";
-    public static final String TypeTag = "Type";
-    public static final String DDFNameTag = "DDFName";
-
-    private final String mUrn;
-    private final String mDtdRev;
-    private final OMAConstructed mRoot;
-
-    public MOTree(XMLNode node, String urn) throws IOException, SAXException {
-        Iterator<XMLNode> children = node.getChildren().iterator();
-
-        String dtdRev = null;
-
-        while (children.hasNext()) {
-            XMLNode child = children.next();
-            if (child.getTag().equals(OMAConstants.SyncMLVersionTag)) {
-                dtdRev = child.getText();
-                children.remove();
-                break;
-            }
-        }
-
-        mUrn = urn;
-        mDtdRev = dtdRev;
-
-        mRoot = new ManagementTreeRoot(node, dtdRev);
-
-        for (XMLNode child : node.getChildren()) {
-            buildNode(mRoot, child);
-        }
-    }
-
-    public MOTree(String urn, String rev, OMAConstructed root) {
-        mUrn = urn;
-        mDtdRev = rev;
-        mRoot = root;
-    }
-
-    /**
-     * Build a Passpoint OMA-DM Management Object tree object.
-     * @param urn The URN for the tree.
-     * @param rev The DTD revision for the tree.
-     * @param root The OMA-DM tree root, in all practical cases the PerProviderSubscription
-     *             node.
-     * @return an MOTree object
-     */
-    public static MOTree buildMgmtTree(String urn, String rev, OMAConstructed root) {
-        OMAConstructed realRoot;
-        switch (urn) {
-            case OMAConstants.PPS_URN:
-            case OMAConstants.DevInfoURN:
-            case OMAConstants.DevDetailURN:
-            case OMAConstants.DevDetailXURN:
-                realRoot = new ManagementTreeRoot(OMAConstants.OMAVersion);
-                realRoot.addChild(root);
-                return new MOTree(urn, rev, realRoot);
-            default:
-                return new MOTree(urn, rev, root);
-        }
-    }
-
-    public static boolean hasMgmtTreeTag(String text) {
-        for (int n = 0; n < text.length(); n++) {
-            char ch = text.charAt(n);
-            if (ch > ' ') {
-                return text.regionMatches(true, n, '<' + MgmtTreeTag + '>',
-                        0, MgmtTreeTag.length() + 2);
-            }
-        }
-        return false;
-    }
-
-    private static class NodeData {
-        private final String mName;
-        private String mPath;
-        private String mValue;
-
-        private NodeData(String name) {
-            mName = name;
-        }
-
-        private void setPath(String path) {
-            mPath = path;
-        }
-
-        private void setValue(String value) {
-            mValue = value;
-        }
-
-        public String getName() {
-            return mName;
-        }
-
-        public String getPath() {
-            return mPath;
-        }
-
-        public String getValue() {
-            return mValue;
-        }
-    }
-
-    private static void buildNode(OMANode parent, XMLNode node) throws IOException {
-        if (!node.getTag().equals(NodeTag))
-            throw new IOException("Node is a '" + node.getTag() + "' instead of a 'Node'");
-
-        Map<String, XMLNode> checkMap = new HashMap<>(3);
-        String context = null;
-        List<NodeData> values = new ArrayList<>();
-        List<XMLNode> children = new ArrayList<>();
-
-        NodeData curValue = null;
-
-        for (XMLNode child : node.getChildren()) {
-            XMLNode old = checkMap.put(child.getTag(), child);
-
-            switch (child.getTag()) {
-                case NodeNameTag:
-                    if (curValue != null)
-                        throw new IOException(NodeNameTag + " not expected");
-                    curValue = new NodeData(child.getText());
-
-                    break;
-                case PathTag:
-                    if (curValue == null || curValue.getPath() != null)
-                        throw new IOException(PathTag + " not expected");
-                    curValue.setPath(child.getText());
-
-                    break;
-                case ValueTag:
-                    if (!children.isEmpty())
-                        throw new IOException(ValueTag + " in constructed node");
-                    if (curValue == null || curValue.getValue() != null)
-                        throw new IOException(ValueTag + " not expected");
-                    curValue.setValue(child.getText());
-                    values.add(curValue);
-                    curValue = null;
-
-                    break;
-                case RTPropTag:
-                    if (old != null)
-                        throw new IOException("Duplicate " + RTPropTag);
-                    XMLNode typeNode = getNextNode(child, TypeTag);
-                    XMLNode ddfName = getNextNode(typeNode, DDFNameTag);
-                    context = ddfName.getText();
-                    if (context == null)
-                        throw new IOException("No text in " + DDFNameTag);
-
-                    break;
-                case NodeTag:
-                    if (!values.isEmpty())
-                        throw new IOException("Scalar node " + node.getText() + " has Node child");
-                    children.add(child);
-
-                    break;
-            }
-        }
-
-        if (values.isEmpty()) {
-            if (curValue == null)
-                throw new IOException("Missing name");
-
-            OMANode subNode = parent.addChild(curValue.getName(),
-                    context, null, curValue.getPath());
-
-            for (XMLNode child : children) {
-                buildNode(subNode, child);
-            }
-        } else {
-            if (!children.isEmpty())
-                throw new IOException("Got both sub nodes and value(s)");
-
-            for (NodeData nodeData : values) {
-                parent.addChild(nodeData.getName(), context,
-                        nodeData.getValue(), nodeData.getPath());
-            }
-        }
-    }
-
-    private static XMLNode getNextNode(XMLNode node, String tag) throws IOException {
-        if (node == null)
-            throw new IOException("No node for " + tag);
-        if (node.getChildren().size() != 1)
-            throw new IOException("Expected " + node.getTag() + " to have exactly one child");
-        XMLNode child = node.getChildren().iterator().next();
-        if (!child.getTag().equals(tag))
-            throw new IOException("Expected " + node.getTag() + " to have child '" + tag +
-                    "' instead of '" + child.getTag() + "'");
-        return child;
-    }
-
-    public String getUrn() {
-        return mUrn;
-    }
-
-    public String getDtdRev() {
-        return mDtdRev;
-    }
-
-    public OMAConstructed getRoot() {
-        return mRoot;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("MO Tree v").append(mDtdRev).append(", urn ").append(mUrn).append(")\n");
-        sb.append(mRoot);
-
-        return sb.toString();
-    }
-
-    public void marshal(OutputStream out) throws IOException {
-        out.write("tree ".getBytes(StandardCharsets.UTF_8));
-        OMAConstants.serializeString(mDtdRev, out);
-        out.write(String.format("(%s)\n", mUrn).getBytes(StandardCharsets.UTF_8));
-        mRoot.marshal(out, 0);
-    }
-
-    public static MOTree unmarshal(InputStream in) throws IOException {
-        boolean strip = true;
-        StringBuilder tree = new StringBuilder();
-        for (; ; ) {
-            int octet = in.read();
-            if (octet < 0) {
-                throw new FileNotFoundException();
-            } else if (octet > ' ') {
-                tree.append((char) octet);
-                strip = false;
-            } else if (!strip) {
-                break;
-            }
-        }
-        if (!tree.toString().equals("tree")) {
-            throw new IOException("Not a tree: " + tree);
-        }
-
-        String version = OMAConstants.deserializeString(in);
-        int next = in.read();
-        if (next != '(') {
-            throw new IOException("Expected URN in tree definition");
-        }
-        String urn = OMAConstants.readURN(in);
-
-        OMAConstructed root = OMANode.unmarshal(in);
-
-        return new MOTree(urn, version, root);
-    }
-
-    public String toXml() {
-        StringBuilder sb = new StringBuilder();
-        mRoot.toXml(sb);
-        return sb.toString();
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/ManagementTreeRoot.java b/service/java/com/android/server/wifi/hotspot2/omadm/ManagementTreeRoot.java
deleted file mode 100644
index 83a8dc8..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/ManagementTreeRoot.java
+++ /dev/null
@@ -1,58 +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.hotspot2.omadm;
-
-import java.util.Map;
-
-/**
- * A specialized OMAConstructed OMA-DM node used as the MgmtTree root node in Passpoint
- * management trees.
- */
-public class ManagementTreeRoot extends OMAConstructed {
-    private final String mDtdRev;
-
-    public ManagementTreeRoot(XMLNode node, String dtdRev) {
-        super(null, MOTree.MgmtTreeTag, null, new MultiValueMap<OMANode>(),
-                node.getTextualAttributes());
-        mDtdRev = dtdRev;
-    }
-
-    public ManagementTreeRoot(String dtdRev) {
-        super(null, MOTree.MgmtTreeTag, null, "xmlns", OMAConstants.SyncML);
-        mDtdRev = dtdRev;
-    }
-
-    @Override
-    public void toXml(StringBuilder sb) {
-        sb.append('<').append(MOTree.MgmtTreeTag);
-        if (getAttributes() != null && !getAttributes().isEmpty()) {
-            for (Map.Entry<String, String> avp : getAttributes().entrySet()) {
-                sb.append(' ').append(avp.getKey()).append("=\"")
-                        .append(escape(avp.getValue())).append('"');
-            }
-        }
-        sb.append(">\n");
-
-        sb.append('<').append(OMAConstants.SyncMLVersionTag)
-                .append('>').append(mDtdRev)
-                .append("</").append(OMAConstants.SyncMLVersionTag).append(">\n");
-        for (OMANode child : getChildren()) {
-            child.toXml(sb);
-        }
-        sb.append("</").append(MOTree.MgmtTreeTag).append(">\n");
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/MultiValueMap.java b/service/java/com/android/server/wifi/hotspot2/omadm/MultiValueMap.java
deleted file mode 100644
index 21e2831..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/MultiValueMap.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-public class MultiValueMap<T> {
-    private final Map<String, ArrayList<T>> mMap = new LinkedHashMap<>();
-
-    public void put(String key, T value) {
-        key = key.toLowerCase();
-        ArrayList<T> values = mMap.get(key);
-        if (values == null) {
-            values = new ArrayList<>();
-            mMap.put(key, values);
-        }
-        values.add(value);
-    }
-
-    public T get(String key) {
-        key = key.toLowerCase();
-        List<T> values = mMap.get(key);
-        if (values == null) {
-            return null;
-        }
-        else if (values.size() == 1) {
-            return values.get(0);
-        }
-        else {
-            throw new IllegalArgumentException("Cannot do get on multi-value");
-        }
-    }
-
-    public T replace(String key, T oldValue, T newValue) {
-        key = key.toLowerCase();
-        List<T> values = mMap.get(key);
-        if (values == null) {
-            return null;
-        }
-
-        for (int n = 0; n < values.size(); n++) {
-            T value = values.get(n);
-            if (value == oldValue) {
-                values.set(n, newValue);
-                return value;
-            }
-        }
-        return null;
-    }
-
-    public T remove(String key, T value) {
-        key = key.toLowerCase();
-        List<T> values = mMap.get(key);
-        if (values == null) {
-            return null;
-        }
-
-        T result = null;
-        Iterator<T> valueIterator = values.iterator();
-        while (valueIterator.hasNext()) {
-            if (valueIterator.next() == value) {
-                valueIterator.remove();
-                result = value;
-                break;
-            }
-        }
-        if (values.isEmpty()) {
-            mMap.remove(key);
-        }
-        return result;
-    }
-
-    public T remove(T value) {
-        T result = null;
-        Iterator<Map.Entry<String, ArrayList<T>>> iterator = mMap.entrySet().iterator();
-        while (iterator.hasNext()) {
-            ArrayList<T> values = iterator.next().getValue();
-            Iterator<T> valueIterator = values.iterator();
-            while (valueIterator.hasNext()) {
-                if (valueIterator.next() == value) {
-                    valueIterator.remove();
-                    result = value;
-                    break;
-                }
-            }
-            if (result != null) {
-                if (values.isEmpty()) {
-                    iterator.remove();
-                }
-                break;
-            }
-        }
-        return result;
-    }
-
-    public Collection<T> values() {
-        List<T> allValues = new ArrayList<>(mMap.size());
-        for (List<T> values : mMap.values()) {
-            for (T value : values) {
-                allValues.add(value);
-            }
-        }
-        return allValues;
-    }
-
-    public T getSingletonValue() {
-        if (mMap.size() != 1) {
-            throw new IllegalArgumentException("Map is not a single entry map");
-        }
-        List<T> values = mMap.values().iterator().next();
-        if (values.size() != 1) {
-            throw new IllegalArgumentException("Map is not a single entry map");
-        }
-        return values.iterator().next();
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java b/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java
deleted file mode 100644
index 87ded86..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/NodeAttribute.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-public class NodeAttribute {
-    private final String mName;
-    private final String mType;
-    private final String mValue;
-
-    public NodeAttribute(String name, String type, String value) {
-        mName = name;
-        mType = type;
-        mValue = value;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public String getValue() {
-        return mValue;
-    }
-
-    public String getType() {
-        return mType;
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (this == thatObject) {
-            return true;
-        }
-        if (thatObject == null || getClass() != thatObject.getClass()) {
-            return false;
-        }
-
-        NodeAttribute that = (NodeAttribute) thatObject;
-        return mName.equals(that.mName) && mType.equals(that.mType) && mValue.equals(that.mValue);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s (%s) = '%s'", mName, mType, mValue);
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java
deleted file mode 100644
index 6d00edd..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstants.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-public class OMAConstants {
-    private OMAConstants() {
-    }
-
-    public static final String MOVersion = "1.0";
-    public static final String PPS_URN = "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
-    public static final String DevInfoURN = "urn:oma:mo:oma-dm-devinfo:1.0";
-    public static final String DevDetailURN = "urn:oma:mo:oma-dm-devdetail:1.0";
-    public static final String DevDetailXURN = "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext:1.0";
-
-    public static final String[] SupportedMO_URNs = {
-            PPS_URN, DevInfoURN, DevDetailURN, DevDetailXURN
-    };
-
-    public static final String SppMOAttribute = "spp:moURN";
-    public static final String TAG_PostDevData = "spp:sppPostDevData";
-    public static final String TAG_SupportedVersions = "spp:supportedSPPVersions";
-    public static final String TAG_SupportedMOs = "spp:supportedMOList";
-    public static final String TAG_UpdateResponse = "spp:sppUpdateResponse";
-    public static final String TAG_MOContainer = "spp:moContainer";
-    public static final String TAG_Version = "spp:sppVersion";
-
-    public static final String TAG_SessionID = "spp:sessionID";
-    public static final String TAG_Status = "spp:sppStatus";
-    public static final String TAG_Error = "spp:sppError";
-
-    public static final String SyncMLVersionTag = "VerDTD";
-    public static final String OMAVersion = "1.2";
-    public static final String SyncML = "syncml:dmddf1.2";
-
-    private static final byte[] INDENT = new byte[1024];
-
-    public static void serializeString(String s, OutputStream out) throws IOException {
-        byte[] octets = s.getBytes(StandardCharsets.UTF_8);
-        byte[] prefix = String.format("%x:", octets.length).getBytes(StandardCharsets.UTF_8);
-        out.write(prefix);
-        out.write(octets);
-    }
-
-    public static void indent(int level, OutputStream out) throws IOException {
-        out.write(INDENT, 0, level);
-    }
-
-    public static String deserializeString(InputStream in) throws IOException {
-        StringBuilder prefix = new StringBuilder();
-        for (; ; ) {
-            byte b = (byte) in.read();
-            if (b == '.')
-                return null;
-            else if (b == ':')
-                break;
-            else if (b > ' ')
-                prefix.append((char) b);
-        }
-        int length = Integer.parseInt(prefix.toString(), 16);
-        byte[] octets = new byte[length];
-        int offset = 0;
-        while (offset < octets.length) {
-            int amount = in.read(octets, offset, octets.length - offset);
-            if (amount <= 0)
-                throw new EOFException();
-            offset += amount;
-        }
-        return new String(octets, StandardCharsets.UTF_8);
-    }
-
-    public static String readURN(InputStream in) throws IOException {
-        StringBuilder urn = new StringBuilder();
-
-        for (; ; ) {
-            byte b = (byte) in.read();
-            if (b == ')')
-                break;
-            urn.append((char) b);
-        }
-        return urn.toString();
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java
deleted file mode 100644
index c0e4c66..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/OMAConstructed.java
+++ /dev/null
@@ -1,172 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.Map;
-
-public class OMAConstructed extends OMANode {
-    private final MultiValueMap<OMANode> mChildren;
-
-    public OMAConstructed(OMAConstructed parent, String name, String context, String ... avps) {
-        this(parent, name, context, new MultiValueMap<OMANode>(), buildAttributes(avps));
-    }
-
-    protected OMAConstructed(OMAConstructed parent, String name, String context,
-                           MultiValueMap<OMANode> children, Map<String, String> avps) {
-        super(parent, name, context, avps);
-        mChildren = children;
-    }
-
-    @Override
-    public OMANode addChild(String name, String context, String value, String pathString)
-            throws IOException {
-        if (pathString == null) {
-            OMANode child = value != null ?
-                    new OMAScalar(this, name, context, value) :
-                    new OMAConstructed(this, name, context);
-            mChildren.put(name, child);
-            return child;
-        } else {
-            OMANode target = this;
-            while (target.getParent() != null)
-                target = target.getParent();
-
-            for (String element : pathString.split("/")) {
-                target = target.getChild(element);
-                if (target == null)
-                    throw new IOException("No child node '" + element + "' in " + getPathString());
-                else if (target.isLeaf())
-                    throw new IOException("Cannot add child to leaf node: " + getPathString());
-            }
-            return target.addChild(name, context, value, null);
-        }
-    }
-
-    @Override
-    public OMAConstructed reparent(OMAConstructed parent) {
-        return new OMAConstructed(parent, getName(), getContext(), mChildren, getAttributes());
-    }
-
-    public void addChild(OMANode child) {
-        mChildren.put(child.getName(), child.reparent(this));
-    }
-
-    public String getScalarValue(Iterator<String> path) throws OMAException {
-        if (!path.hasNext()) {
-            throw new OMAException("Path too short for " + getPathString());
-        }
-        String tag = path.next();
-        OMANode child = mChildren.get(tag);
-        if (child != null) {
-            return child.getScalarValue(path);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public OMANode getListValue(Iterator<String> path) throws OMAException {
-        if (!path.hasNext()) {
-            return null;
-        }
-        String tag = path.next();
-        OMANode child;
-        if (tag.equals("?")) {
-            child = mChildren.getSingletonValue();
-        }
-        else {
-            child = mChildren.get(tag);
-        }
-
-        if (child == null) {
-            return null;
-        }
-        else if (path.hasNext()) {
-            return child.getListValue(path);
-        } else {
-            return child;
-        }
-    }
-
-    @Override
-    public boolean isLeaf() {
-        return false;
-    }
-
-    @Override
-    public Collection<OMANode> getChildren() {
-        return Collections.unmodifiableCollection(mChildren.values());
-    }
-
-    public OMANode getChild(String name) {
-        return mChildren.get(name);
-    }
-
-    public OMANode replaceNode(OMANode oldNode, OMANode newNode) {
-        return mChildren.replace(oldNode.getName(), oldNode, newNode);
-    }
-
-    public OMANode removeNode(String key, OMANode node) {
-        if (key.equals("?")) {
-            return mChildren.remove(node);
-        }
-        else {
-            return mChildren.remove(key, node);
-        }
-    }
-
-    @Override
-    public String getValue() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void toString(StringBuilder sb, int level) {
-        sb.append(getPathString());
-        if (getContext() != null) {
-            sb.append(" (").append(getContext()).append(')');
-        }
-        sb.append('\n');
-
-        for (OMANode node : mChildren.values()) {
-            node.toString(sb, level + 1);
-        }
-    }
-
-    @Override
-    public void marshal(OutputStream out, int level) throws IOException {
-        OMAConstants.indent(level, out);
-        OMAConstants.serializeString(getName(), out);
-        if (getContext() != null) {
-            out.write(String.format("(%s)", getContext()).getBytes(StandardCharsets.UTF_8));
-        }
-        out.write(new byte[] { '+', '\n' });
-
-        for (OMANode child : mChildren.values()) {
-            child.marshal(out, level + 1);
-        }
-        OMAConstants.indent(level, out);
-        out.write(".\n".getBytes(StandardCharsets.UTF_8));
-    }
-
-    @Override
-    public void fillPayload(StringBuilder sb) {
-        if (getContext() != null) {
-            sb.append('<').append(MOTree.RTPropTag).append(">\n");
-            sb.append('<').append(MOTree.TypeTag).append(">\n");
-            sb.append('<').append(MOTree.DDFNameTag).append(">");
-            sb.append(getContext());
-            sb.append("</").append(MOTree.DDFNameTag).append(">\n");
-            sb.append("</").append(MOTree.TypeTag).append(">\n");
-            sb.append("</").append(MOTree.RTPropTag).append(">\n");
-        }
-
-        for (OMANode child : getChildren()) {
-            child.toXml(sb);
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java
deleted file mode 100644
index 5f8cd11..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/OMAException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import java.io.IOException;
-
-public class OMAException extends IOException {
-    public OMAException(String message) {
-        super(message);
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java
deleted file mode 100644
index fbb6075..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/OMANode.java
+++ /dev/null
@@ -1,195 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-public abstract class OMANode {
-    private final OMAConstructed mParent;
-    private final String mName;
-    private final String mContext;
-    private final Map<String, String> mAttributes;
-
-    private static final Map<Character, String> sEscapes = new HashMap<>();
-
-    static {
-        sEscapes.put('"', "&quot;");
-        sEscapes.put('\'', "&apos;");
-        sEscapes.put('<', "&lt;");
-        sEscapes.put('>', "&gt;");
-        sEscapes.put('&', "&amp;");
-    }
-
-    protected OMANode(OMAConstructed parent, String name, String context, Map<String, String> avps) {
-        mParent = parent;
-        mName = name;
-        mContext = context;
-        mAttributes = avps;
-    }
-
-    protected static Map<String, String> buildAttributes(String[] avps) {
-        if (avps == null) {
-            return null;
-        }
-        Map<String, String> attributes = new HashMap<>();
-        for (int n = 0; n < avps.length; n += 2) {
-            attributes.put(avps[n], avps[n+1]);
-        }
-        return attributes;
-    }
-
-    protected Map<String, String> getAttributes() {
-        return mAttributes;
-    }
-
-    public OMAConstructed getParent() {
-        return mParent;
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public String getContext() {
-        return mContext;
-    }
-
-    public List<String> getPath() {
-        LinkedList<String> path = new LinkedList<>();
-        for (OMANode node = this; node != null; node = node.getParent()) {
-            path.addFirst(node.getName());
-        }
-        return path;
-    }
-
-    public String getPathString() {
-        StringBuilder sb = new StringBuilder();
-        for (String element : getPath()) {
-            sb.append('/').append(element);
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Perform escaping of special XML characters
-     * @param s the raw string
-     * @return a "XML clean" representation
-     */
-    public static String escape(String s) {
-        StringBuilder sb = new StringBuilder(s.length());
-        for (int n = 0; n < s.length(); n++) {
-            char ch = s.charAt(n);
-            String escape = sEscapes.get(ch);
-            if (escape != null) {
-                sb.append(escape);
-            } else {
-                sb.append(ch);
-            }
-        }
-        return sb.toString();
-    }
-
-    public abstract OMANode reparent(OMAConstructed parent);
-
-    public abstract String getScalarValue(Iterator<String> path) throws OMAException;
-
-    public abstract OMANode getListValue(Iterator<String> path) throws OMAException;
-
-    public abstract boolean isLeaf();
-
-    public abstract Collection<OMANode> getChildren();
-
-    public abstract OMANode getChild(String name) throws OMAException;
-
-    public abstract String getValue();
-
-    public abstract OMANode addChild(String name, String context, String value, String path)
-            throws IOException;
-
-    public abstract void marshal(OutputStream out, int level) throws IOException;
-
-    public abstract void toString(StringBuilder sb, int level);
-
-    public abstract void fillPayload(StringBuilder sb);
-
-    public void toXml(StringBuilder sb) {
-        sb.append('<').append(MOTree.NodeTag);
-        if (mAttributes != null && !mAttributes.isEmpty()) {
-            for (Map.Entry<String, String> avp : mAttributes.entrySet()) {
-                sb.append(' ').append(avp.getKey()).append("=\"")
-                        .append(escape(avp.getValue())).append('"');
-            }
-        }
-        sb.append(">\n");
-
-        sb.append('<').append(MOTree.NodeNameTag).append('>');
-        sb.append(getName());
-        sb.append("</").append(MOTree.NodeNameTag).append(">\n");
-
-        fillPayload(sb);
-
-        sb.append("</").append(MOTree.NodeTag).append(">\n");
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        toString(sb, 0);
-        return sb.toString();
-    }
-
-    public static OMAConstructed unmarshal(InputStream in) throws IOException {
-        OMANode node = buildNode(in, null);
-        if (node == null || node.isLeaf()) {
-            throw new IOException("Bad OMA tree");
-        }
-        unmarshal(in, (OMAConstructed) node);
-        return (OMAConstructed) node;
-    }
-
-    private static void unmarshal(InputStream in, OMAConstructed parent) throws IOException {
-        for (; ; ) {
-            OMANode node = buildNode(in, parent);
-            if (node == null) {
-                return;
-            }
-            else if (!node.isLeaf()) {
-                unmarshal(in, (OMAConstructed) node);
-            }
-        }
-    }
-
-    private static OMANode buildNode(InputStream in, OMAConstructed parent) throws IOException {
-        String name = OMAConstants.deserializeString(in);
-        if (name == null) {
-            return null;
-        }
-
-        String urn = null;
-        int next = in.read();
-        if (next == '(') {
-            urn = OMAConstants.readURN(in);
-            next = in.read();
-        }
-
-        if (next == '=') {
-            String value = OMAConstants.deserializeString(in);
-            return parent.addChild(name, urn, value, null);
-        } else if (next == '+') {
-            if (parent != null) {
-                return parent.addChild(name, urn, null, null);
-            } else {
-                return new OMAConstructed(null, name, urn);
-            }
-        }
-        else {
-            throw new IOException("Parse error: expected = or + after node name");
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java
deleted file mode 100644
index d39fa33..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/OMAParser.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.IOException;
-import java.io.StringReader;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Parses an OMA-DM XML tree.
- * OMA-DM = Open Mobile Association Device Management
- */
-public class OMAParser extends DefaultHandler {
-    private XMLNode mRoot;
-    private XMLNode mCurrent;
-
-    public OMAParser() {
-        mRoot = null;
-        mCurrent = null;
-    }
-
-    public MOTree parse(String text, String urn) throws IOException, SAXException {
-        if (text == null) {
-          throw new IOException("Missing text string");
-        }
-        try {
-            SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
-            parser.parse(new InputSource(new StringReader(text)), this);
-            return new MOTree(mRoot, urn);
-        } catch (ParserConfigurationException pce) {
-            throw new SAXException(pce);
-        }
-    }
-
-    public XMLNode getRoot() {
-        return mRoot;
-    }
-
-    @Override
-    public void startElement(String uri, String localName, String qName, Attributes attributes)
-            throws SAXException {
-        XMLNode parent = mCurrent;
-
-        mCurrent = new XMLNode(mCurrent, qName, attributes);
-
-        if (mRoot == null)
-            mRoot = mCurrent;
-        else
-            parent.addChild(mCurrent);
-    }
-
-    @Override
-    public void endElement(String uri, String localName, String qName) throws SAXException {
-        if (!qName.equals(mCurrent.getTag()))
-            throw new SAXException("End tag '" + qName + "' doesn't match current node: " +
-                    mCurrent);
-
-        try {
-            mCurrent.close();
-        } catch (IOException ioe) {
-            throw new SAXException("Failed to close element", ioe);
-        }
-
-        mCurrent = mCurrent.getParent();
-    }
-
-    @Override
-    public void characters(char[] ch, int start, int length) throws SAXException {
-        mCurrent.addText(ch, start, length);
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java b/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java
deleted file mode 100644
index 86740ac..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/OMAScalar.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Map;
-
-public class OMAScalar extends OMANode {
-    private final String mValue;
-
-    public OMAScalar(OMAConstructed parent, String name, String context, String value,
-                     String ... avps) {
-        this(parent, name, context, value, buildAttributes(avps));
-    }
-
-    public OMAScalar(OMAConstructed parent, String name, String context, String value,
-                     Map<String, String> avps) {
-        super(parent, name, context, avps);
-        mValue = value;
-    }
-
-    @Override
-    public OMAScalar reparent(OMAConstructed parent) {
-        return new OMAScalar(parent, getName(), getContext(), mValue, getAttributes());
-    }
-
-    public String getScalarValue(Iterator<String> path) throws OMAException {
-        return mValue;
-    }
-
-    @Override
-    public OMANode getListValue(Iterator<String> path) throws OMAException {
-        throw new OMAException("Scalar encountered in list path: " + getPathString());
-    }
-
-    @Override
-    public boolean isLeaf() {
-        return true;
-    }
-
-    @Override
-    public Collection<OMANode> getChildren() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getValue() {
-        return mValue;
-    }
-
-    @Override
-    public OMANode getChild(String name) throws OMAException {
-        throw new OMAException("'" + getName() + "' is a scalar node");
-    }
-
-    @Override
-    public OMANode addChild(String name, String context, String value, String path)
-            throws IOException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void toString(StringBuilder sb, int level) {
-        sb.append(getPathString()).append('=').append(mValue);
-        if (getContext() != null) {
-            sb.append(" (").append(getContext()).append(')');
-        }
-        sb.append('\n');
-    }
-
-    @Override
-    public void marshal(OutputStream out, int level) throws IOException {
-        OMAConstants.indent(level, out);
-        OMAConstants.serializeString(getName(), out);
-        out.write((byte) '=');
-        OMAConstants.serializeString(getValue(), out);
-        out.write((byte) '\n');
-    }
-
-    @Override
-    public void fillPayload(StringBuilder sb) {
-        sb.append('<').append(MOTree.ValueTag).append('>');
-        sb.append(escape(mValue));
-        sb.append("</").append(MOTree.ValueTag).append(">\n");
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/PasspointManagementObjectManager.java b/service/java/com/android/server/wifi/hotspot2/omadm/PasspointManagementObjectManager.java
deleted file mode 100644
index e967212..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/PasspointManagementObjectManager.java
+++ /dev/null
@@ -1,951 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import android.net.wifi.PasspointManagementObjectDefinition;
-import android.util.Base64;
-import android.util.Log;
-
-import com.android.server.wifi.IMSIParameter;
-import com.android.server.wifi.anqp.eap.EAP;
-import com.android.server.wifi.anqp.eap.EAPMethod;
-import com.android.server.wifi.anqp.eap.ExpandedEAPMethod;
-import com.android.server.wifi.anqp.eap.InnerAuthEAP;
-import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.pps.Credential;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
-import com.android.server.wifi.hotspot2.pps.Policy;
-import com.android.server.wifi.hotspot2.pps.SubscriptionParameters;
-import com.android.server.wifi.hotspot2.pps.UpdateInfo;
-
-import org.xml.sax.SAXException;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TimeZone;
-
-/**
- * Handles provisioning of PerProviderSubscription data.
- */
-public class PasspointManagementObjectManager {
-
-    public static final String TAG_AAAServerTrustRoot = "AAAServerTrustRoot";
-    public static final String TAG_AbleToShare = "AbleToShare";
-    public static final String TAG_CertificateType = "CertificateType";
-    public static final String TAG_CertSHA256Fingerprint = "CertSHA256Fingerprint";
-    public static final String TAG_CertURL = "CertURL";
-    public static final String TAG_CheckAAAServerCertStatus = "CheckAAAServerCertStatus";
-    public static final String TAG_Country = "Country";
-    public static final String TAG_CreationDate = "CreationDate";
-    public static final String TAG_Credential = "Credential";
-    public static final String TAG_CredentialPriority = "CredentialPriority";
-    public static final String TAG_DataLimit = "DataLimit";
-    public static final String TAG_DigitalCertificate = "DigitalCertificate";
-    public static final String TAG_DLBandwidth = "DLBandwidth";
-    public static final String TAG_EAPMethod = "EAPMethod";
-    public static final String TAG_EAPType = "EAPType";
-    public static final String TAG_ExpirationDate = "ExpirationDate";
-    public static final String TAG_Extension = "Extension";
-    public static final String TAG_FQDN = "FQDN";
-    public static final String TAG_FQDN_Match = "FQDN_Match";
-    public static final String TAG_FriendlyName = "FriendlyName";
-    public static final String TAG_HESSID = "HESSID";
-    public static final String TAG_HomeOI = "HomeOI";
-    public static final String TAG_HomeOIList = "HomeOIList";
-    public static final String TAG_HomeOIRequired = "HomeOIRequired";
-    public static final String TAG_HomeSP = "HomeSP";
-    public static final String TAG_IconURL = "IconURL";
-    public static final String TAG_IMSI = "IMSI";
-    public static final String TAG_InnerEAPType = "InnerEAPType";
-    public static final String TAG_InnerMethod = "InnerMethod";
-    public static final String TAG_InnerVendorID = "InnerVendorID";
-    public static final String TAG_InnerVendorType = "InnerVendorType";
-    public static final String TAG_IPProtocol = "IPProtocol";
-    public static final String TAG_MachineManaged = "MachineManaged";
-    public static final String TAG_MaximumBSSLoadValue = "MaximumBSSLoadValue";
-    public static final String TAG_MinBackhaulThreshold = "MinBackhaulThreshold";
-    public static final String TAG_NetworkID = "NetworkID";
-    public static final String TAG_NetworkType = "NetworkType";
-    public static final String TAG_Other = "Other";
-    public static final String TAG_OtherHomePartners = "OtherHomePartners";
-    public static final String TAG_Password = "Password";
-    public static final String TAG_PerProviderSubscription = "PerProviderSubscription";
-    public static final String TAG_Policy = "Policy";
-    public static final String TAG_PolicyUpdate = "PolicyUpdate";
-    public static final String TAG_PortNumber = "PortNumber";
-    public static final String TAG_PreferredRoamingPartnerList = "PreferredRoamingPartnerList";
-    public static final String TAG_Priority = "Priority";
-    public static final String TAG_Realm = "Realm";
-    public static final String TAG_RequiredProtoPortTuple = "RequiredProtoPortTuple";
-    public static final String TAG_Restriction = "Restriction";
-    public static final String TAG_RoamingConsortiumOI = "RoamingConsortiumOI";
-    public static final String TAG_SIM = "SIM";
-    public static final String TAG_SoftTokenApp = "SoftTokenApp";
-    public static final String TAG_SPExclusionList = "SPExclusionList";
-    public static final String TAG_SSID = "SSID";
-    public static final String TAG_StartDate = "StartDate";
-    public static final String TAG_SubscriptionParameters = "SubscriptionParameters";
-    public static final String TAG_SubscriptionUpdate = "SubscriptionUpdate";
-    public static final String TAG_TimeLimit = "TimeLimit";
-    public static final String TAG_TrustRoot = "TrustRoot";
-    public static final String TAG_TypeOfSubscription = "TypeOfSubscription";
-    public static final String TAG_ULBandwidth = "ULBandwidth";
-    public static final String TAG_UpdateIdentifier = "UpdateIdentifier";
-    public static final String TAG_UpdateInterval = "UpdateInterval";
-    public static final String TAG_UpdateMethod = "UpdateMethod";
-    public static final String TAG_URI = "URI";
-    public static final String TAG_UsageLimits = "UsageLimits";
-    public static final String TAG_UsageTimePeriod = "UsageTimePeriod";
-    public static final String TAG_Username = "Username";
-    public static final String TAG_UsernamePassword = "UsernamePassword";
-    public static final String TAG_VendorId = "VendorId";
-    public static final String TAG_VendorType = "VendorType";
-
-    public static final long IntervalFactor = 60000L;  // All MO intervals are in minutes
-
-    private static final DateFormat DTFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
-
-    private static final Map<String, Map<String, Object>> sSelectionMap;
-
-    static {
-        DTFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-
-        sSelectionMap = new HashMap<>();
-
-        setSelections(TAG_FQDN_Match,
-                "exactmatch", Boolean.FALSE,
-                "includesubdomains", Boolean.TRUE);
-        setSelections(TAG_UpdateMethod,
-                "oma-dm-clientinitiated", Boolean.FALSE,
-                "spp-clientinitiated", Boolean.TRUE);
-        setSelections(TAG_Restriction,
-                "homesp", UpdateInfo.UpdateRestriction.HomeSP,
-                "roamingpartner", UpdateInfo.UpdateRestriction.RoamingPartner,
-                "unrestricted", UpdateInfo.UpdateRestriction.Unrestricted);
-    }
-
-    private static void setSelections(String key, Object... pairs) {
-        Map<String, Object> kvp = new HashMap<>();
-        sSelectionMap.put(key, kvp);
-        for (int n = 0; n < pairs.length; n += 2) {
-            kvp.put(pairs[n].toString(), pairs[n + 1]);
-        }
-    }
-
-    private final File mPpsFile;
-    private final boolean mEnabled;
-    private final Map<String, HomeSP> mSPs;
-
-    public PasspointManagementObjectManager(File ppsFile, boolean hs2enabled) {
-        mPpsFile = ppsFile;
-        mEnabled = hs2enabled;
-        mSPs = new HashMap<>();
-    }
-
-    public boolean isEnabled() {
-        return mEnabled;
-    }
-
-    public boolean isConfigured() {
-        return mEnabled && !mSPs.isEmpty();
-    }
-
-    public Map<String, HomeSP> getLoadedSPs() {
-        return Collections.unmodifiableMap(mSPs);
-    }
-
-    public List<HomeSP> loadAllSPs() throws IOException {
-
-        if (!mEnabled || !mPpsFile.exists()) {
-            return Collections.emptyList();
-        }
-
-        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
-            mSPs.clear();
-            MOTree moTree;
-            try {
-                moTree = MOTree.unmarshal(in);
-            } catch (FileNotFoundException fnfe) {
-                return Collections.emptyList();     // Empty file
-            }
-
-            List<HomeSP> sps = buildSPs(moTree);
-            if (sps != null) {
-                for (HomeSP sp : sps) {
-                    if (mSPs.put(sp.getFQDN(), sp) != null) {
-                        throw new OMAException("Multiple SPs for FQDN '" + sp.getFQDN() + "'");
-                    } else {
-                        Log.d(Utils.hs2LogTag(getClass()),
-                                "retrieved " + sp.getFQDN() + " from PPS");
-                    }
-                }
-                return sps;
-
-            } else {
-                throw new OMAException("Failed to build HomeSP");
-            }
-        }
-    }
-
-    public static HomeSP buildSP(String xml) throws IOException, SAXException {
-        OMAParser omaParser = new OMAParser();
-        MOTree tree = omaParser.parse(xml, OMAConstants.PPS_URN);
-        List<HomeSP> spList = buildSPs(tree);
-        if (spList.size() != 1) {
-            throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
-        }
-        return spList.iterator().next();
-    }
-
-    public HomeSP addSP(String xml) throws IOException, SAXException {
-        OMAParser omaParser = new OMAParser();
-        return addSP(omaParser.parse(xml, OMAConstants.PPS_URN));
-    }
-
-    private static final List<String> FQDNPath = Arrays.asList(TAG_HomeSP, TAG_FQDN);
-
-    /**
-     * R1 *only* addSP method.
-     *
-     * @param homeSP
-     * @throws IOException
-     */
-    public void addSP(HomeSP homeSP) throws IOException {
-        if (!mEnabled) {
-            throw new IOException("HS2.0 not enabled on this device");
-        }
-        if (mSPs.containsKey(homeSP.getFQDN())) {
-            Log.d(Utils.hs2LogTag(getClass()), "HS20 profile for "
-                    + homeSP.getFQDN() + " already exists");
-            return;
-        }
-        Log.d(Utils.hs2LogTag(getClass()), "Adding new HS20 profile for " + homeSP.getFQDN());
-
-        OMAConstructed dummyRoot = new OMAConstructed(null, TAG_PerProviderSubscription, null);
-        buildHomeSPTree(homeSP, dummyRoot, mSPs.size() + 1);
-        try {
-            addSP(dummyRoot);
-        } catch (FileNotFoundException fnfe) {
-            MOTree tree = MOTree.buildMgmtTree(OMAConstants.PPS_URN,
-                    OMAConstants.OMAVersion, dummyRoot);
-            writeMO(tree, mPpsFile);
-        }
-        mSPs.put(homeSP.getFQDN(), homeSP);
-    }
-
-    public HomeSP addSP(MOTree instanceTree) throws IOException {
-        List<HomeSP> spList = buildSPs(instanceTree);
-        if (spList.size() != 1) {
-            throw new OMAException("Expected exactly one HomeSP, got " + spList.size());
-        }
-
-        HomeSP sp = spList.iterator().next();
-        String fqdn = sp.getFQDN();
-        if (mSPs.put(fqdn, sp) != null) {
-            throw new OMAException("SP " + fqdn + " already exists");
-        }
-
-        OMAConstructed pps = (OMAConstructed) instanceTree.getRoot()
-                .getChild(TAG_PerProviderSubscription);
-
-        try {
-            addSP(pps);
-        } catch (FileNotFoundException fnfe) {
-            MOTree tree = new MOTree(instanceTree.getUrn(), instanceTree.getDtdRev(),
-                    instanceTree.getRoot());
-            writeMO(tree, mPpsFile);
-        }
-
-        return sp;
-    }
-
-    /**
-     * Add an SP sub-tree. mo must be PPS with an immediate instance child (e.g. Cred01) and an
-     * optional UpdateIdentifier,
-     *
-     * @param mo The new MO
-     * @throws IOException
-     */
-    private void addSP(OMANode mo) throws IOException {
-        MOTree moTree;
-        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
-            moTree = MOTree.unmarshal(in);
-            moTree.getRoot().addChild(mo);
-        }
-        writeMO(moTree, mPpsFile);
-    }
-
-    private static OMAConstructed findTargetTree(MOTree moTree, String fqdn) throws OMAException {
-        OMANode pps = moTree.getRoot();
-        for (OMANode node : pps.getChildren()) {
-            OMANode instance = null;
-            if (node.getName().equals(TAG_PerProviderSubscription)) {
-                instance = getInstanceNode((OMAConstructed) node);
-            } else if (!node.isLeaf()) {
-                instance = node;
-            }
-            if (instance != null) {
-                String nodeFqdn = getString(instance.getListValue(FQDNPath.iterator()));
-                if (fqdn.equalsIgnoreCase(nodeFqdn)) {
-                    return (OMAConstructed) node;
-                    // targetTree is rooted at the PPS
-                }
-            }
-        }
-        return null;
-    }
-
-    private static OMAConstructed getInstanceNode(OMAConstructed root) throws OMAException {
-        for (OMANode child : root.getChildren()) {
-            if (!child.isLeaf()) {
-                return (OMAConstructed) child;
-            }
-        }
-        throw new OMAException("Cannot find instance node");
-    }
-
-    public int modifySP(String fqdn, Collection<PasspointManagementObjectDefinition> mods)
-            throws IOException, SAXException {
-
-        Log.d(Utils.hs2LogTag(getClass()), "modifying SP: " + mods);
-        MOTree moTree;
-        int ppsMods = 0;
-        int updateIdentifier;
-        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
-            moTree = MOTree.unmarshal(in);
-            // moTree is PPS/?/provider-data
-
-            OMAConstructed targetTree = findTargetTree(moTree, fqdn);
-            if (targetTree == null) {
-                throw new IOException("Failed to find PPS tree for " + fqdn);
-            }
-            OMAConstructed instance = getInstanceNode(targetTree);
-
-            for (PasspointManagementObjectDefinition mod : mods) {
-                LinkedList<String> tailPath = getTailPath(mod.getBaseUri(),
-                        TAG_PerProviderSubscription);
-                OMAConstructed modRoot = buildMoTree(mod).getRoot();
-                // modRoot is the MgmtTree with the actual object as a
-                // direct child (e.g. Credential)
-
-                if (tailPath.getFirst().equals(TAG_UpdateIdentifier)) {
-                    updateIdentifier = getInteger(modRoot.getChildren().iterator().next());
-                    OMANode oldUdi = targetTree.getChild(TAG_UpdateIdentifier);
-                    if (getInteger(oldUdi) != updateIdentifier) {
-                        ppsMods++;
-                    }
-                    if (oldUdi != null) {
-                        targetTree.replaceNode(oldUdi, modRoot.getChild(TAG_UpdateIdentifier));
-                    } else {
-                        targetTree.addChild(modRoot.getChild(TAG_UpdateIdentifier));
-                    }
-                } else {
-                    tailPath.removeFirst();     // Drop the instance
-                    OMANode current = instance.getListValue(tailPath.iterator());
-                    if (current == null) {
-                        throw new IOException("No previous node for " + tailPath + " in " + fqdn);
-                    }
-                    for (OMANode newNode : modRoot.getChildren()) {
-                        // newNode is something like Credential
-                        // current is the same existing node
-                        current.getParent().replaceNode(current, newNode);
-                        ppsMods++;
-                    }
-                }
-            }
-        }
-        writeMO(moTree, mPpsFile);
-
-        return ppsMods;
-    }
-
-    private static MOTree buildMoTree(PasspointManagementObjectDefinition
-                                              managementObjectDefinition)
-            throws IOException, SAXException {
-
-        OMAParser omaParser = new OMAParser();
-        return omaParser.parse(managementObjectDefinition.getMoTree(), OMAConstants.PPS_URN);
-    }
-
-    private static LinkedList<String> getTailPath(String pathString, String rootName)
-            throws IOException {
-        String[] path = pathString.split("/");
-        int pathIndex;
-        for (pathIndex = 0; pathIndex < path.length; pathIndex++) {
-            if (path[pathIndex].equalsIgnoreCase(rootName)) {
-                pathIndex++;
-                break;
-            }
-        }
-        if (pathIndex >= path.length) {
-            throw new IOException("Bad node-path: " + pathString);
-        }
-        LinkedList<String> tailPath = new LinkedList<>();
-        while (pathIndex < path.length) {
-            tailPath.add(path[pathIndex]);
-            pathIndex++;
-        }
-        return tailPath;
-    }
-
-    public HomeSP getHomeSP(String fqdn) {
-        return mSPs.get(fqdn);
-    }
-
-    public void removeSP(String fqdn) throws IOException {
-        if (mSPs.remove(fqdn) == null) {
-            Log.d(Utils.hs2LogTag(getClass()), "No HS20 profile to delete for " + fqdn);
-            return;
-        }
-
-        Log.d(Utils.hs2LogTag(getClass()), "Deleting HS20 profile for " + fqdn);
-
-        MOTree moTree;
-        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
-            moTree = MOTree.unmarshal(in);
-            OMAConstructed tbd = findTargetTree(moTree, fqdn);
-            if (tbd == null) {
-                throw new IOException("Node " + fqdn + " doesn't exist in MO tree");
-            }
-            OMAConstructed pps = moTree.getRoot();
-            OMANode removed = pps.removeNode("?", tbd);
-            if (removed == null) {
-                throw new IOException("Failed to remove " + fqdn + " out of MO tree");
-            }
-        }
-        writeMO(moTree, mPpsFile);
-    }
-
-    public String getMOTree(String fqdn) throws IOException {
-        if (fqdn == null) {
-            return null;
-        }
-        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(mPpsFile))) {
-            MOTree moTree = MOTree.unmarshal(in);
-            OMAConstructed target = findTargetTree(moTree, fqdn);
-            if (target == null) {
-                return null;
-            }
-            return MOTree.buildMgmtTree(OMAConstants.PPS_URN,
-                    OMAConstants.OMAVersion, target).toXml();
-        } catch (FileNotFoundException fnfe) {
-            return null;
-        }
-    }
-
-    private static void writeMO(MOTree moTree, File f) throws IOException {
-        try (BufferedOutputStream out =
-                     new BufferedOutputStream(new FileOutputStream(f, false))) {
-            moTree.marshal(out);
-            out.flush();
-        }
-    }
-
-    private static OMANode buildHomeSPTree(HomeSP homeSP, OMAConstructed root, int instanceID)
-            throws IOException {
-        OMANode providerSubNode = root.addChild(getInstanceString(instanceID),
-                null, null, null);
-
-        // The HomeSP:
-        OMANode homeSpNode = providerSubNode.addChild(TAG_HomeSP, null, null, null);
-        if (!homeSP.getSSIDs().isEmpty()) {
-            OMAConstructed nwkIDNode =
-                    (OMAConstructed) homeSpNode.addChild(TAG_NetworkID, null, null, null);
-            int instance = 0;
-            for (Map.Entry<String, Long> entry : homeSP.getSSIDs().entrySet()) {
-                OMAConstructed inode =
-                        (OMAConstructed) nwkIDNode.addChild(getInstanceString(instance++),
-                                null, null, null);
-                inode.addChild(TAG_SSID, null, entry.getKey(), null);
-                if (entry.getValue() != null) {
-                    inode.addChild(TAG_HESSID, null,
-                            String.format("%012x", entry.getValue()), null);
-                }
-            }
-        }
-
-        homeSpNode.addChild(TAG_FriendlyName, null, homeSP.getFriendlyName(), null);
-
-        if (homeSP.getIconURL() != null) {
-            homeSpNode.addChild(TAG_IconURL, null, homeSP.getIconURL(), null);
-        }
-
-        homeSpNode.addChild(TAG_FQDN, null, homeSP.getFQDN(), null);
-
-        if (!homeSP.getMatchAllOIs().isEmpty() || !homeSP.getMatchAnyOIs().isEmpty()) {
-            OMAConstructed homeOIList =
-                    (OMAConstructed) homeSpNode.addChild(TAG_HomeOIList, null, null, null);
-
-            int instance = 0;
-            for (Long oi : homeSP.getMatchAllOIs()) {
-                OMAConstructed inode =
-                        (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
-                                null, null, null);
-                inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
-                inode.addChild(TAG_HomeOIRequired, null, "TRUE", null);
-            }
-            for (Long oi : homeSP.getMatchAnyOIs()) {
-                OMAConstructed inode =
-                        (OMAConstructed) homeOIList.addChild(getInstanceString(instance++),
-                                null, null, null);
-                inode.addChild(TAG_HomeOI, null, String.format("%x", oi), null);
-                inode.addChild(TAG_HomeOIRequired, null, "FALSE", null);
-            }
-        }
-
-        if (!homeSP.getOtherHomePartners().isEmpty()) {
-            OMAConstructed otherPartners =
-                    (OMAConstructed) homeSpNode.addChild(TAG_OtherHomePartners, null, null, null);
-            int instance = 0;
-            for (String fqdn : homeSP.getOtherHomePartners()) {
-                OMAConstructed inode =
-                        (OMAConstructed) otherPartners.addChild(getInstanceString(instance++),
-                                null, null, null);
-                inode.addChild(TAG_FQDN, null, fqdn, null);
-            }
-        }
-
-        if (!homeSP.getRoamingConsortiums().isEmpty()) {
-            homeSpNode.addChild(TAG_RoamingConsortiumOI, null,
-                    getRCList(homeSP.getRoamingConsortiums()), null);
-        }
-
-        // The Credential:
-        OMANode credentialNode = providerSubNode.addChild(TAG_Credential, null, null, null);
-        Credential cred = homeSP.getCredential();
-        EAPMethod method = cred.getEAPMethod();
-
-        if (cred.getCtime() > 0) {
-            credentialNode.addChild(TAG_CreationDate,
-                    null, DTFormat.format(new Date(cred.getCtime())), null);
-        }
-        if (cred.getExpTime() > 0) {
-            credentialNode.addChild(TAG_ExpirationDate,
-                    null, DTFormat.format(new Date(cred.getExpTime())), null);
-        }
-
-        if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_SIM
-                || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKA
-                || method.getEAPMethodID() == EAP.EAPMethodID.EAP_AKAPrim) {
-
-            OMANode simNode = credentialNode.addChild(TAG_SIM, null, null, null);
-            simNode.addChild(TAG_IMSI, null, cred.getImsi().toString(), null);
-            simNode.addChild(TAG_EAPType, null,
-                    Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
-
-        } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TTLS) {
-
-            OMANode unpNode = credentialNode.addChild(TAG_UsernamePassword, null, null, null);
-            unpNode.addChild(TAG_Username, null, cred.getUserName(), null);
-            unpNode.addChild(TAG_Password, null,
-                    Base64.encodeToString(cred.getPassword().getBytes(StandardCharsets.UTF_8),
-                            Base64.DEFAULT), null);
-            OMANode eapNode = unpNode.addChild(TAG_EAPMethod, null, null, null);
-            eapNode.addChild(TAG_EAPType, null,
-                    Integer.toString(EAP.mapEAPMethod(method.getEAPMethodID())), null);
-            eapNode.addChild(TAG_InnerMethod, null,
-                    ((NonEAPInnerAuth) method.getAuthParam()).getOMAtype(), null);
-
-        } else if (method.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS) {
-
-            OMANode certNode = credentialNode.addChild(TAG_DigitalCertificate, null, null, null);
-            certNode.addChild(TAG_CertificateType, null, Credential.CertTypeX509, null);
-            certNode.addChild(TAG_CertSHA256Fingerprint, null,
-                    Utils.toHex(cred.getFingerPrint()), null);
-
-        } else {
-            throw new OMAException("Invalid credential on " + homeSP.getFQDN());
-        }
-
-        credentialNode.addChild(TAG_Realm, null, cred.getRealm(), null);
-
-        // !!! Note: This node defines CRL checking through OSCP, I suspect we won't be able
-        // to do that so it is commented out:
-        //credentialNode.addChild(TAG_CheckAAAServerCertStatus, null, "TRUE", null);
-        return providerSubNode;
-    }
-
-    private static String getInstanceString(int instance) {
-        return "r1i" + instance;
-    }
-
-    private static String getRCList(Collection<Long> rcs) {
-        StringBuilder builder = new StringBuilder();
-        boolean first = true;
-        for (Long roamingConsortium : rcs) {
-            if (first) {
-                first = false;
-            } else {
-                builder.append(',');
-            }
-            builder.append(String.format("%x", roamingConsortium));
-        }
-        return builder.toString();
-    }
-
-    private static List<HomeSP> buildSPs(MOTree moTree) throws OMAException {
-        OMAConstructed spList;
-        List<HomeSP> homeSPs = new ArrayList<>();
-        if (moTree.getRoot().getName().equals(TAG_PerProviderSubscription)) {
-            // The old PPS file was rooted at PPS instead of MgmtTree to conserve space
-            spList = moTree.getRoot();
-
-            if (spList == null) {
-                return homeSPs;
-            }
-
-            for (OMANode node : spList.getChildren()) {
-                if (!node.isLeaf()) {
-                    homeSPs.add(buildHomeSP(node, 0));
-                }
-            }
-        } else {
-            for (OMANode ppsRoot : moTree.getRoot().getChildren()) {
-                if (ppsRoot.getName().equals(TAG_PerProviderSubscription)) {
-                    Integer updateIdentifier = null;
-                    OMANode instance = null;
-                    for (OMANode child : ppsRoot.getChildren()) {
-                        if (child.getName().equals(TAG_UpdateIdentifier)) {
-                            updateIdentifier = getInteger(child);
-                        } else if (!child.isLeaf()) {
-                            instance = child;
-                        }
-                    }
-                    if (instance == null) {
-                        throw new OMAException("PPS node missing instance node");
-                    }
-                    homeSPs.add(buildHomeSP(instance,
-                            updateIdentifier != null ? updateIdentifier : 0));
-                }
-            }
-        }
-
-        return homeSPs;
-    }
-
-    private static HomeSP buildHomeSP(OMANode ppsRoot, int updateIdentifier) throws OMAException {
-        OMANode spRoot = ppsRoot.getChild(TAG_HomeSP);
-
-        String fqdn = spRoot.getScalarValue(Arrays.asList(TAG_FQDN).iterator());
-        String friendlyName = spRoot.getScalarValue(Arrays.asList(TAG_FriendlyName).iterator());
-        String iconURL = spRoot.getScalarValue(Arrays.asList(TAG_IconURL).iterator());
-
-        HashSet<Long> roamingConsortiums = new HashSet<>();
-        String oiString = spRoot.getScalarValue(Arrays.asList(TAG_RoamingConsortiumOI).iterator());
-        if (oiString != null) {
-            for (String oi : oiString.split(",")) {
-                roamingConsortiums.add(Long.parseLong(oi.trim(), 16));
-            }
-        }
-
-        Map<String, Long> ssids = new HashMap<>();
-
-        OMANode ssidListNode = spRoot.getListValue(Arrays.asList(TAG_NetworkID).iterator());
-        if (ssidListNode != null) {
-            for (OMANode ssidRoot : ssidListNode.getChildren()) {
-                OMANode hessidNode = ssidRoot.getChild(TAG_HESSID);
-                ssids.put(ssidRoot.getChild(TAG_SSID).getValue(), getMac(hessidNode));
-            }
-        }
-
-        Set<Long> matchAnyOIs = new HashSet<>();
-        List<Long> matchAllOIs = new ArrayList<>();
-        OMANode homeOIListNode = spRoot.getListValue(Arrays.asList(TAG_HomeOIList).iterator());
-        if (homeOIListNode != null) {
-            for (OMANode homeOIRoot : homeOIListNode.getChildren()) {
-                String homeOI = homeOIRoot.getChild(TAG_HomeOI).getValue();
-                if (Boolean.parseBoolean(homeOIRoot.getChild(TAG_HomeOIRequired).getValue())) {
-                    matchAllOIs.add(Long.parseLong(homeOI, 16));
-                } else {
-                    matchAnyOIs.add(Long.parseLong(homeOI, 16));
-                }
-            }
-        }
-
-        Set<String> otherHomePartners = new HashSet<>();
-        OMANode otherListNode =
-                spRoot.getListValue(Arrays.asList(TAG_OtherHomePartners).iterator());
-        if (otherListNode != null) {
-            for (OMANode fqdnNode : otherListNode.getChildren()) {
-                otherHomePartners.add(fqdnNode.getChild(TAG_FQDN).getValue());
-            }
-        }
-
-        Credential credential = buildCredential(ppsRoot.getChild(TAG_Credential));
-
-        OMANode policyNode = ppsRoot.getChild(TAG_Policy);
-        Policy policy = policyNode != null ? new Policy(policyNode) : null;
-
-        Map<String, String> aaaTrustRoots;
-        OMANode aaaRootNode = ppsRoot.getChild(TAG_AAAServerTrustRoot);
-        if (aaaRootNode == null) {
-            aaaTrustRoots = null;
-        } else {
-            aaaTrustRoots = new HashMap<>(aaaRootNode.getChildren().size());
-            for (OMANode child : aaaRootNode.getChildren()) {
-                aaaTrustRoots.put(getString(child, TAG_CertURL),
-                        getString(child, TAG_CertSHA256Fingerprint));
-            }
-        }
-
-        OMANode updateNode = ppsRoot.getChild(TAG_SubscriptionUpdate);
-        UpdateInfo subscriptionUpdate = updateNode != null ? new UpdateInfo(updateNode) : null;
-        OMANode subNode = ppsRoot.getChild(TAG_SubscriptionParameters);
-        SubscriptionParameters subscriptionParameters = subNode != null
-                ? new SubscriptionParameters(subNode) : null;
-
-        return new HomeSP(ssids, fqdn, roamingConsortiums, otherHomePartners,
-                matchAnyOIs, matchAllOIs, friendlyName, iconURL, credential,
-                policy, getInteger(ppsRoot.getChild(TAG_CredentialPriority), 0),
-                aaaTrustRoots, subscriptionUpdate, subscriptionParameters, updateIdentifier);
-    }
-
-    private static Credential buildCredential(OMANode credNode) throws OMAException {
-        long ctime = getTime(credNode.getChild(TAG_CreationDate));
-        long expTime = getTime(credNode.getChild(TAG_ExpirationDate));
-        String realm = getString(credNode.getChild(TAG_Realm));
-        boolean checkAAACert = getBoolean(credNode.getChild(TAG_CheckAAAServerCertStatus));
-
-        OMANode unNode = credNode.getChild(TAG_UsernamePassword);
-        OMANode certNode = credNode.getChild(TAG_DigitalCertificate);
-        OMANode simNode = credNode.getChild(TAG_SIM);
-
-        int alternatives = 0;
-        alternatives += unNode != null ? 1 : 0;
-        alternatives += certNode != null ? 1 : 0;
-        alternatives += simNode != null ? 1 : 0;
-        if (alternatives != 1) {
-            throw new OMAException("Expected exactly one credential type, got " + alternatives);
-        }
-
-        if (unNode != null) {
-            String userName = getString(unNode.getChild(TAG_Username));
-            String password = getString(unNode.getChild(TAG_Password));
-            boolean machineManaged = getBoolean(unNode.getChild(TAG_MachineManaged));
-            String softTokenApp = getString(unNode.getChild(TAG_SoftTokenApp));
-            boolean ableToShare = getBoolean(unNode.getChild(TAG_AbleToShare));
-
-            OMANode eapMethodNode = unNode.getChild(TAG_EAPMethod);
-            int eapID = getInteger(eapMethodNode.getChild(TAG_EAPType));
-
-            EAP.EAPMethodID eapMethodID = EAP.mapEAPMethod(eapID);
-            if (eapMethodID == null) {
-                throw new OMAException("Unknown EAP method: " + eapID);
-            }
-
-            Long vid = getOptionalInteger(eapMethodNode.getChild(TAG_VendorId));
-            Long vtype = getOptionalInteger(eapMethodNode.getChild(TAG_VendorType));
-            Long innerEAPType = getOptionalInteger(eapMethodNode.getChild(TAG_InnerEAPType));
-            EAP.EAPMethodID innerEAPMethod = null;
-            if (innerEAPType != null) {
-                innerEAPMethod = EAP.mapEAPMethod(innerEAPType.intValue());
-                if (innerEAPMethod == null) {
-                    throw new OMAException("Bad inner EAP method: " + innerEAPType);
-                }
-            }
-
-            Long innerVid = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorID));
-            Long innerVtype = getOptionalInteger(eapMethodNode.getChild(TAG_InnerVendorType));
-            String innerNonEAPMethod = getString(eapMethodNode.getChild(TAG_InnerMethod));
-
-            EAPMethod eapMethod;
-            if (innerEAPMethod != null) {
-                eapMethod = new EAPMethod(eapMethodID, new InnerAuthEAP(innerEAPMethod));
-            } else if (vid != null) {
-                eapMethod = new EAPMethod(eapMethodID,
-                        new ExpandedEAPMethod(EAP.AuthInfoID.ExpandedEAPMethod,
-                                vid.intValue(), vtype));
-            } else if (innerVid != null) {
-                eapMethod =
-                        new EAPMethod(eapMethodID, new ExpandedEAPMethod(EAP.AuthInfoID
-                                .ExpandedInnerEAPMethod, innerVid.intValue(), innerVtype));
-            } else if (innerNonEAPMethod != null) {
-                eapMethod = new EAPMethod(eapMethodID, new NonEAPInnerAuth(innerNonEAPMethod));
-            } else {
-                throw new OMAException("Incomplete set of EAP parameters");
-            }
-
-            return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, userName,
-                    password, machineManaged, softTokenApp, ableToShare);
-        }
-        if (certNode != null) {
-            try {
-                String certTypeString = getString(certNode.getChild(TAG_CertificateType));
-                byte[] fingerPrint = getOctets(certNode.getChild(TAG_CertSHA256Fingerprint));
-
-                EAPMethod eapMethod = new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
-
-                return new Credential(ctime, expTime, realm, checkAAACert, eapMethod,
-                        Credential.mapCertType(certTypeString), fingerPrint);
-            } catch (NumberFormatException nfe) {
-                throw new OMAException("Bad hex string: " + nfe.toString());
-            }
-        }
-        if (simNode != null) {
-            try {
-                IMSIParameter imsi = new IMSIParameter(getString(simNode.getChild(TAG_IMSI)));
-
-                EAPMethod eapMethod =
-                        new EAPMethod(EAP.mapEAPMethod(getInteger(simNode.getChild(TAG_EAPType))),
-                                null);
-
-                return new Credential(ctime, expTime, realm, checkAAACert, eapMethod, imsi);
-            } catch (IOException ioe) {
-                throw new OMAException("Failed to parse IMSI: " + ioe);
-            }
-        }
-        throw new OMAException("Missing credential parameters");
-    }
-
-    public static OMANode getChild(OMANode node, String key) throws OMAException {
-        OMANode child = node.getChild(key);
-        if (child == null) {
-            throw new OMAException("No such node: " + key);
-        }
-        return child;
-    }
-
-    public static String getString(OMANode node, String key) throws OMAException {
-        OMANode child = node.getChild(key);
-        if (child == null) {
-            throw new OMAException("Missing value for " + key);
-        } else if (!child.isLeaf()) {
-            throw new OMAException(key + " is not a leaf node");
-        }
-        return child.getValue();
-    }
-
-    public static long getLong(OMANode node, String key, Long dflt) throws OMAException {
-        OMANode child = node.getChild(key);
-        if (child == null) {
-            if (dflt != null) {
-                return dflt;
-            } else {
-                throw new OMAException("Missing value for " + key);
-            }
-        } else {
-            if (!child.isLeaf()) {
-                throw new OMAException(key + " is not a leaf node");
-            }
-            String value = child.getValue();
-            try {
-                long result = Long.parseLong(value);
-                if (result < 0) {
-                    throw new OMAException("Negative value for " + key);
-                }
-                return result;
-            } catch (NumberFormatException nfe) {
-                throw new OMAException("Value for " + key + " is non-numeric: " + value);
-            }
-        }
-    }
-
-    public static <T> T getSelection(OMANode node, String key) throws OMAException {
-        OMANode child = node.getChild(key);
-        if (child == null) {
-            throw new OMAException("Missing value for " + key);
-        } else if (!child.isLeaf()) {
-            throw new OMAException(key + " is not a leaf node");
-        }
-        return getSelection(key, child.getValue());
-    }
-
-    public static <T> T getSelection(String key, String value) throws OMAException {
-        if (value == null) {
-            throw new OMAException("No value for " + key);
-        }
-        Map<String, Object> kvp = sSelectionMap.get(key);
-        T result = (T) kvp.get(value.toLowerCase());
-        if (result == null) {
-            throw new OMAException("Invalid value '" + value + "' for " + key);
-        }
-        return result;
-    }
-
-    private static boolean getBoolean(OMANode boolNode) {
-        return boolNode != null && Boolean.parseBoolean(boolNode.getValue());
-    }
-
-    public static String getString(OMANode stringNode) {
-        return stringNode != null ? stringNode.getValue() : null;
-    }
-
-    private static int getInteger(OMANode intNode, int dflt) throws OMAException {
-        if (intNode == null) {
-            return dflt;
-        }
-        return getInteger(intNode);
-    }
-
-    private static int getInteger(OMANode intNode) throws OMAException {
-        if (intNode == null) {
-            throw new OMAException("Missing integer value");
-        }
-        try {
-            return Integer.parseInt(intNode.getValue());
-        } catch (NumberFormatException nfe) {
-            throw new OMAException("Invalid integer: " + intNode.getValue());
-        }
-    }
-
-    private static Long getMac(OMANode macNode) throws OMAException {
-        if (macNode == null) {
-            return null;
-        }
-        try {
-            return Long.parseLong(macNode.getValue(), 16);
-        } catch (NumberFormatException nfe) {
-            throw new OMAException("Invalid MAC: " + macNode.getValue());
-        }
-    }
-
-    private static Long getOptionalInteger(OMANode intNode) throws OMAException {
-        if (intNode == null) {
-            return null;
-        }
-        try {
-            return Long.parseLong(intNode.getValue());
-        } catch (NumberFormatException nfe) {
-            throw new OMAException("Invalid integer: " + intNode.getValue());
-        }
-    }
-
-    public static long getTime(OMANode timeNode) throws OMAException {
-        if (timeNode == null) {
-            return Utils.UNSET_TIME;
-        }
-        String timeText = timeNode.getValue();
-        try {
-            Date date = DTFormat.parse(timeText);
-            return date.getTime();
-        } catch (ParseException pe) {
-            throw new OMAException("Badly formatted time: " + timeText);
-        }
-    }
-
-    private static byte[] getOctets(OMANode octetNode) throws OMAException {
-        if (octetNode == null) {
-            throw new OMAException("Missing byte value");
-        }
-        return Utils.hexToBytes(octetNode.getValue());
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java b/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java
deleted file mode 100644
index 76b7076..0000000
--- a/service/java/com/android/server/wifi/hotspot2/omadm/XMLNode.java
+++ /dev/null
@@ -1,287 +0,0 @@
-package com.android.server.wifi.hotspot2.omadm;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class XMLNode {
-    private final String mTag;
-    private final Map<String, NodeAttribute> mAttributes;
-    private final List<XMLNode> mChildren;
-    private final XMLNode mParent;
-    private MOTree mMO;
-    private StringBuilder mTextBuilder;
-    private String mText;
-
-    private static final String XML_SPECIAL_CHARS = "\"'<>&";
-    private static final Set<Character> XML_SPECIAL = new HashSet<>();
-    private static final String CDATA_OPEN = "<![CDATA[";
-    private static final String CDATA_CLOSE = "]]>";
-
-    static {
-        for (int n = 0; n < XML_SPECIAL_CHARS.length(); n++) {
-            XML_SPECIAL.add(XML_SPECIAL_CHARS.charAt(n));
-        }
-    }
-
-    public XMLNode(XMLNode parent, String tag, Attributes attributes) throws SAXException {
-        mTag = tag;
-
-        mAttributes = new HashMap<>();
-
-        if (attributes.getLength() > 0) {
-            for (int n = 0; n < attributes.getLength(); n++)
-                mAttributes.put(attributes.getQName(n), new NodeAttribute(attributes.getQName(n),
-                        attributes.getType(n), attributes.getValue(n)));
-        }
-
-        mParent = parent;
-        mChildren = new ArrayList<>();
-
-        mTextBuilder = new StringBuilder();
-    }
-
-    public XMLNode(XMLNode parent, String tag, Map<String, String> attributes) {
-        mTag = tag;
-
-        mAttributes = new HashMap<>(attributes == null ? 0 : attributes.size());
-
-        if (attributes != null) {
-            for (Map.Entry<String, String> entry : attributes.entrySet()) {
-                mAttributes.put(entry.getKey(), new NodeAttribute(entry.getKey(), "", entry.getValue()));
-            }
-        }
-
-        mParent = parent;
-        mChildren = new ArrayList<>();
-
-        mTextBuilder = new StringBuilder();
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (thatObject == this) {
-            return true;
-        } else if (thatObject.getClass() != XMLNode.class) {
-            return false;
-        }
-
-        XMLNode that = (XMLNode) thatObject;
-        if (!getTag().equals(that.getTag())
-                || mAttributes.size() != that.mAttributes.size()
-                || mChildren.size() != that.mChildren.size()) {
-            return false;
-        }
-
-        for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
-            if (!entry.getValue().equals(that.mAttributes.get(entry.getKey()))) {
-                return false;
-            }
-        }
-
-        List<XMLNode> cloneOfThat = new ArrayList<>(that.mChildren);
-        for (XMLNode child : mChildren) {
-            Iterator<XMLNode> thatChildren = cloneOfThat.iterator();
-            boolean found = false;
-            while (thatChildren.hasNext()) {
-                XMLNode thatChild = thatChildren.next();
-                if (child.equals(thatChild)) {
-                    found = true;
-                    thatChildren.remove();
-                    break;
-                }
-            }
-            if (!found) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public void setText(String text) {
-        mText = text;
-        mTextBuilder = null;
-    }
-
-    public void addText(char[] chs, int start, int length) {
-        String s = new String(chs, start, length);
-        String trimmed = s.trim();
-        if (trimmed.isEmpty())
-            return;
-
-        if (s.charAt(0) != trimmed.charAt(0))
-            mTextBuilder.append(' ');
-        mTextBuilder.append(trimmed);
-        if (s.charAt(s.length() - 1) != trimmed.charAt(trimmed.length() - 1))
-            mTextBuilder.append(' ');
-    }
-
-    public void addChild(XMLNode child) {
-        mChildren.add(child);
-    }
-
-    public void close() throws IOException, SAXException {
-        String text = mTextBuilder.toString().trim();
-        StringBuilder filtered = new StringBuilder(text.length());
-        for (int n = 0; n < text.length(); n++) {
-            char ch = text.charAt(n);
-            if (ch >= ' ')
-                filtered.append(ch);
-        }
-
-        mText = filtered.toString();
-        mTextBuilder = null;
-
-        if (MOTree.hasMgmtTreeTag(mText)) {
-            try {
-                NodeAttribute urn = mAttributes.get(OMAConstants.SppMOAttribute);
-                OMAParser omaParser = new OMAParser();
-                mMO = omaParser.parse(mText, urn != null ? urn.getValue() : null);
-            }
-            catch (SAXException | IOException e) {
-                mMO = null;
-            }
-        }
-    }
-
-    public String getTag() {
-        return mTag;
-    }
-
-    public String getNameSpace() throws OMAException {
-        String[] nsn = mTag.split(":");
-        if (nsn.length != 2) {
-            throw new OMAException("Non-namespaced tag: '" + mTag + "'");
-        }
-        return nsn[0];
-    }
-
-    public String getStrippedTag() throws OMAException {
-        String[] nsn = mTag.split(":");
-        if (nsn.length != 2) {
-            throw new OMAException("Non-namespaced tag: '" + mTag + "'");
-        }
-        return nsn[1].toLowerCase();
-    }
-
-    public XMLNode getSoleChild() throws OMAException{
-        if (mChildren.size() != 1) {
-            throw new OMAException("Expected exactly one child to " + mTag);
-        }
-        return mChildren.get(0);
-    }
-
-    public XMLNode getParent() {
-        return mParent;
-    }
-
-    public String getText() {
-        return mText;
-    }
-
-    public Map<String, NodeAttribute> getAttributes() {
-        return Collections.unmodifiableMap(mAttributes);
-    }
-
-    /**
-     * Get the attributes of this node as a map of attribute name to attribute value.
-     * @return The attribute mapping.
-     */
-    public Map<String, String> getTextualAttributes() {
-        Map<String, String> map = new HashMap<>(mAttributes.size());
-        for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
-            map.put(entry.getKey(), entry.getValue().getValue());
-        }
-        return map;
-    }
-
-    public String getAttributeValue(String name) {
-        NodeAttribute nodeAttribute = mAttributes.get(name);
-        return nodeAttribute != null ? nodeAttribute.getValue() : null;
-    }
-
-    public List<XMLNode> getChildren() {
-        return mChildren;
-    }
-
-    public MOTree getMOTree() {
-        return mMO;
-    }
-
-    private void toString(char[] indent, StringBuilder sb) {
-        Arrays.fill(indent, ' ');
-
-        sb.append(indent).append('<').append(mTag);
-        for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
-            sb.append(' ').append(entry.getKey()).append("='").append(entry.getValue().getValue()).append('\'');
-        }
-
-        if (mText != null && !mText.isEmpty()) {
-            sb.append('>').append(escapeCdata(mText)).append("</").append(mTag).append(">\n");
-        }
-        else if (mChildren.isEmpty()) {
-            sb.append("/>\n");
-        }
-        else {
-            sb.append(">\n");
-            char[] subIndent = Arrays.copyOf(indent, indent.length + 2);
-            for (XMLNode child : mChildren) {
-                child.toString(subIndent, sb);
-            }
-            sb.append(indent).append("</").append(mTag).append(">\n");
-        }
-    }
-
-    private static String escapeCdata(String text) {
-        if (!escapable(text)) {
-            return text;
-        }
-
-        // Any appearance of ]]> in the text must be split into "]]" | "]]>" | <![CDATA[ | ">"
-        // i.e. "split the sequence by putting a close CDATA and a new open CDATA before the '>'
-        StringBuilder sb = new StringBuilder();
-        sb.append(CDATA_OPEN);
-        int start = 0;
-        for (;;) {
-            int etoken = text.indexOf(CDATA_CLOSE);
-            if (etoken >= 0) {
-                sb.append(text.substring(start, etoken + 2)).append(CDATA_CLOSE).append(CDATA_OPEN);
-                start = etoken + 2;
-            }
-            else {
-                if (start < text.length() - 1) {
-                    sb.append(text.substring(start));
-                }
-                break;
-            }
-        }
-        sb.append(CDATA_CLOSE);
-        return sb.toString();
-    }
-
-    private static boolean escapable(String s) {
-        for (int n = 0; n < s.length(); n++) {
-            if (XML_SPECIAL.contains(s.charAt(n))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        toString(new char[0], sb);
-        return sb.toString();
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/Credential.java b/service/java/com/android/server/wifi/hotspot2/pps/Credential.java
deleted file mode 100644
index 4a06021..0000000
--- a/service/java/com/android/server/wifi/hotspot2/pps/Credential.java
+++ /dev/null
@@ -1,357 +0,0 @@
-package com.android.server.wifi.hotspot2.pps;
-
-import android.net.wifi.WifiEnterpriseConfig;
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.Log;
-
-import com.android.server.wifi.IMSIParameter;
-import com.android.server.wifi.anqp.eap.EAP;
-import com.android.server.wifi.anqp.eap.EAPMethod;
-import com.android.server.wifi.anqp.eap.NonEAPInnerAuth;
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.omadm.OMAException;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.util.Arrays;
-
-public class Credential {
-    public enum CertType {IEEE, x509v3}
-
-    public static final String CertTypeX509 = "x509v3";
-    public static final String CertTypeIEEE = "802.1ar";
-
-    private final long mCtime;
-    private final long mExpTime;
-    private final String mRealm;
-    private final boolean mCheckAAACert;
-
-    private final String mUserName;
-    private final String mPassword;
-    private final boolean mDisregardPassword;
-    private final boolean mMachineManaged;
-    private final String mSTokenApp;
-    private final boolean mShare;
-    private final EAPMethod mEAPMethod;
-
-    private final CertType mCertType;
-    private final byte[] mFingerPrint;
-
-    private final IMSIParameter mImsi;
-
-    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
-                      EAPMethod eapMethod, String userName, String password,
-                      boolean machineManaged, String stApp, boolean share) {
-        mCtime = ctime;
-        mExpTime = expTime;
-        mRealm = realm;
-        mCheckAAACert = checkAAACert;
-        mEAPMethod = eapMethod;
-        mUserName = userName;
-
-        if (!TextUtils.isEmpty(password)) {
-            byte[] pwOctets = Base64.decode(password, Base64.DEFAULT);
-            mPassword = new String(pwOctets, StandardCharsets.UTF_8);
-        } else {
-            mPassword = null;
-        }
-        mDisregardPassword = false;
-
-        mMachineManaged = machineManaged;
-        mSTokenApp = stApp;
-        mShare = share;
-
-        mCertType = null;
-        mFingerPrint = null;
-
-        mImsi = null;
-    }
-
-    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
-                      EAPMethod eapMethod, Credential.CertType certType, byte[] fingerPrint) {
-        mCtime = ctime;
-        mExpTime = expTime;
-        mRealm = realm;
-        mCheckAAACert = checkAAACert;
-        mEAPMethod = eapMethod;
-        mCertType = certType;
-        mFingerPrint = fingerPrint;
-
-        mUserName = null;
-        mPassword = null;
-        mDisregardPassword = false;
-        mMachineManaged = false;
-        mSTokenApp = null;
-        mShare = false;
-
-        mImsi = null;
-    }
-
-    public Credential(long ctime, long expTime, String realm, boolean checkAAACert,
-                      EAPMethod eapMethod, IMSIParameter imsi) {
-        mCtime = ctime;
-        mExpTime = expTime;
-        mRealm = realm;
-        mCheckAAACert = checkAAACert;
-        mEAPMethod = eapMethod;
-        mImsi = imsi;
-
-        mCertType = null;
-        mFingerPrint = null;
-
-        mUserName = null;
-        mPassword = null;
-        mDisregardPassword = false;
-        mMachineManaged = false;
-        mSTokenApp = null;
-        mShare = false;
-    }
-
-    public Credential(Credential other, String password) {
-        mCtime = other.mCtime;
-        mExpTime = other.mExpTime;
-        mRealm = other.mRealm;
-        mCheckAAACert = other.mCheckAAACert;
-        mUserName = other.mUserName;
-        mPassword = password;
-        mDisregardPassword = other.mDisregardPassword;
-        mMachineManaged = other.mMachineManaged;
-        mSTokenApp = other.mSTokenApp;
-        mShare = other.mShare;
-        mEAPMethod = other.mEAPMethod;
-        mCertType = other.mCertType;
-        mFingerPrint = other.mFingerPrint;
-        mImsi = other.mImsi;
-    }
-
-    public Credential(WifiEnterpriseConfig enterpriseConfig, KeyStore keyStore, boolean update)
-            throws IOException {
-        mCtime = Utils.UNSET_TIME;
-        mExpTime = Utils.UNSET_TIME;
-        mRealm = enterpriseConfig.getRealm();
-        mCheckAAACert = false;
-        mEAPMethod = mapEapMethod(enterpriseConfig.getEapMethod(),
-                enterpriseConfig.getPhase2Method());
-        mCertType = mEAPMethod.getEAPMethodID() == EAP.EAPMethodID.EAP_TLS ? CertType.x509v3 : null;
-        byte[] fingerPrint;
-
-        if (enterpriseConfig.getClientCertificate() != null) {
-            // !!! Not sure this will be true in any practical instances:
-            try {
-                MessageDigest digester = MessageDigest.getInstance("SHA-256");
-                fingerPrint = digester.digest(enterpriseConfig.getClientCertificate().getEncoded());
-            } catch (GeneralSecurityException gse) {
-                Log.e(Utils.hs2LogTag(getClass()),
-                        "Failed to generate certificate fingerprint: " + gse);
-                fingerPrint = null;
-            }
-        } else if (enterpriseConfig.getClientCertificateAlias() != null) {
-            String alias = enterpriseConfig.getClientCertificateAlias();
-            byte[] octets = keyStore.get(Credentials.USER_CERTIFICATE + alias);
-            if (octets != null) {
-                try {
-                    MessageDigest digester = MessageDigest.getInstance("SHA-256");
-                    fingerPrint = digester.digest(octets);
-                } catch (GeneralSecurityException gse) {
-                    Log.e(Utils.hs2LogTag(getClass()), "Failed to construct digest: " + gse);
-                    fingerPrint = null;
-                }
-            } else // !!! The current alias is *not* derived from the fingerprint...
-            {
-                try {
-                    fingerPrint = Base64.decode(enterpriseConfig.getClientCertificateAlias(),
-                            Base64.DEFAULT);
-                } catch (IllegalArgumentException ie) {
-                    Log.e(Utils.hs2LogTag(getClass()), "Bad base 64 alias");
-                    fingerPrint = null;
-                }
-            }
-        } else {
-            fingerPrint = null;
-        }
-        mFingerPrint = fingerPrint;
-        String imsi = enterpriseConfig.getPlmn();
-        mImsi = imsi == null || imsi.length() == 0 ? null : new IMSIParameter(imsi);
-        mUserName = enterpriseConfig.getIdentity();
-        mPassword = enterpriseConfig.getPassword();
-        mDisregardPassword = update && mPassword.length() < 2;
-        mMachineManaged = false;
-        mSTokenApp = null;
-        mShare = false;
-    }
-
-    public static CertType mapCertType(String certType) throws OMAException {
-        if (certType.equalsIgnoreCase(CertTypeX509)) {
-            return CertType.x509v3;
-        } else if (certType.equalsIgnoreCase(CertTypeIEEE)) {
-            return CertType.IEEE;
-        } else {
-            throw new OMAException("Invalid cert type: '" + certType + "'");
-        }
-    }
-
-    private static EAPMethod mapEapMethod(int eapMethod, int phase2Method) throws IOException {
-        switch (eapMethod) {
-            case WifiEnterpriseConfig.Eap.TLS:
-                return new EAPMethod(EAP.EAPMethodID.EAP_TLS, null);
-            case WifiEnterpriseConfig.Eap.TTLS:
-            /* keep this table in sync with WifiEnterpriseConfig.Phase2 enum */
-                NonEAPInnerAuth inner;
-                switch (phase2Method) {
-                    case WifiEnterpriseConfig.Phase2.PAP:
-                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.PAP);
-                        break;
-                    case WifiEnterpriseConfig.Phase2.MSCHAP:
-                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.MSCHAP);
-                        break;
-                    case WifiEnterpriseConfig.Phase2.MSCHAPV2:
-                        inner = new NonEAPInnerAuth(NonEAPInnerAuth.NonEAPType.MSCHAPv2);
-                        break;
-                    default:
-                        throw new IOException("TTLS phase2 method " +
-                                phase2Method + " not valid for Passpoint");
-                }
-                return new EAPMethod(EAP.EAPMethodID.EAP_TTLS, inner);
-            case WifiEnterpriseConfig.Eap.SIM:
-                return new EAPMethod(EAP.EAPMethodID.EAP_SIM, null);
-            case WifiEnterpriseConfig.Eap.AKA:
-                return new EAPMethod(EAP.EAPMethodID.EAP_AKA, null);
-            case WifiEnterpriseConfig.Eap.AKA_PRIME:
-                return new EAPMethod(EAP.EAPMethodID.EAP_AKAPrim, null);
-            default:
-                String methodName;
-                if (eapMethod >= 0 && eapMethod < WifiEnterpriseConfig.Eap.strings.length) {
-                    methodName = WifiEnterpriseConfig.Eap.strings[eapMethod];
-                } else {
-                    methodName = Integer.toString(eapMethod);
-                }
-                throw new IOException("EAP method id " + methodName + " is not valid for Passpoint");
-        }
-    }
-
-    public EAPMethod getEAPMethod() {
-        return mEAPMethod;
-    }
-
-    public String getRealm() {
-        return mRealm;
-    }
-
-    public IMSIParameter getImsi() {
-        return mImsi;
-    }
-
-    public String getUserName() {
-        return mUserName;
-    }
-
-    public String getPassword() {
-        return mPassword;
-    }
-
-    public boolean hasDisregardPassword() {
-        return mDisregardPassword;
-    }
-
-    public CertType getCertType() {
-        return mCertType;
-    }
-
-    public byte[] getFingerPrint() {
-        return mFingerPrint;
-    }
-
-    public long getCtime() {
-        return mCtime;
-    }
-
-    public long getExpTime() {
-        return mExpTime;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        Credential that = (Credential) o;
-
-        if (mCheckAAACert != that.mCheckAAACert) return false;
-        if (mCtime != that.mCtime) return false;
-        if (mExpTime != that.mExpTime) return false;
-        if (mMachineManaged != that.mMachineManaged) return false;
-        if (mShare != that.mShare) return false;
-        if (mCertType != that.mCertType) return false;
-        if (!mEAPMethod.equals(that.mEAPMethod)) return false;
-        if (!Arrays.equals(mFingerPrint, that.mFingerPrint)) return false;
-        if (!safeEquals(mImsi, that.mImsi)) {
-            return false;
-        }
-
-        if (!mDisregardPassword && !safeEquals(mPassword, that.mPassword)) {
-            return false;
-        }
-
-        if (!mRealm.equals(that.mRealm)) return false;
-        if (!safeEquals(mSTokenApp, that.mSTokenApp)) {
-            return false;
-        }
-        if (!safeEquals(mUserName, that.mUserName)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private static boolean safeEquals(Object s1, Object s2) {
-        if (s1 == null) {
-            return s2 == null;
-        }
-        else {
-            return s2 != null && s1.equals(s2);
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        int result = (int) (mCtime ^ (mCtime >>> 32));
-        result = 31 * result + (int) (mExpTime ^ (mExpTime >>> 32));
-        result = 31 * result + mRealm.hashCode();
-        result = 31 * result + (mCheckAAACert ? 1 : 0);
-        result = 31 * result + (mUserName != null ? mUserName.hashCode() : 0);
-        result = 31 * result + (mPassword != null ? mPassword.hashCode() : 0);
-        result = 31 * result + (mMachineManaged ? 1 : 0);
-        result = 31 * result + (mSTokenApp != null ? mSTokenApp.hashCode() : 0);
-        result = 31 * result + (mShare ? 1 : 0);
-        result = 31 * result + mEAPMethod.hashCode();
-        result = 31 * result + (mCertType != null ? mCertType.hashCode() : 0);
-        result = 31 * result + (mFingerPrint != null ? Arrays.hashCode(mFingerPrint) : 0);
-        result = 31 * result + (mImsi != null ? mImsi.hashCode() : 0);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "Credential{" +
-                "mCtime=" + Utils.toUTCString(mCtime) +
-                ", mExpTime=" + Utils.toUTCString(mExpTime) +
-                ", mRealm='" + mRealm + '\'' +
-                ", mCheckAAACert=" + mCheckAAACert +
-                ", mUserName='" + mUserName + '\'' +
-                ", mPassword='" + mPassword + '\'' +
-                ", mDisregardPassword=" + mDisregardPassword +
-                ", mMachineManaged=" + mMachineManaged +
-                ", mSTokenApp='" + mSTokenApp + '\'' +
-                ", mShare=" + mShare +
-                ", mEAPMethod=" + mEAPMethod +
-                ", mCertType=" + mCertType +
-                ", mFingerPrint=" + Utils.toHexString(mFingerPrint) +
-                ", mImsi='" + mImsi + '\'' +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/DomainMatcher.java b/service/java/com/android/server/wifi/hotspot2/pps/DomainMatcher.java
deleted file mode 100644
index 3f6e22a..0000000
--- a/service/java/com/android/server/wifi/hotspot2/pps/DomainMatcher.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package com.android.server.wifi.hotspot2.pps;
-
-import android.util.Log;
-
-import com.android.server.wifi.hotspot2.Utils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-
-public class DomainMatcher {
-
-    public enum Match {None, Primary, Secondary}
-
-    private final Label mRoot;
-
-    private static class Label {
-        private final Map<String, Label> mSubDomains;
-        private final Match mMatch;
-
-        private Label(Match match) {
-            mMatch = match;
-            mSubDomains = match == Match.None ? new HashMap<String, Label>() : null;
-        }
-
-        private void addDomain(Iterator<String> labels, Match match) {
-            String labelName = labels.next();
-            if (labels.hasNext()) {
-                Label subLabel = new Label(Match.None);
-                mSubDomains.put(labelName, subLabel);
-                subLabel.addDomain(labels, match);
-            } else {
-                mSubDomains.put(labelName, new Label(match));
-            }
-        }
-
-        private Label getSubLabel(String labelString) {
-            return mSubDomains.get(labelString);
-        }
-
-        public Match getMatch() {
-            return mMatch;
-        }
-
-        private void toString(StringBuilder sb) {
-            if (mSubDomains != null) {
-                sb.append(".{");
-                for (Map.Entry<String, Label> entry : mSubDomains.entrySet()) {
-                    sb.append(entry.getKey());
-                    entry.getValue().toString(sb);
-                }
-                sb.append('}');
-            } else {
-                sb.append('=').append(mMatch);
-            }
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder sb = new StringBuilder();
-            toString(sb);
-            return sb.toString();
-        }
-    }
-
-    public DomainMatcher(List<String> primary, List<List<String>> secondary) {
-        mRoot = new Label(Match.None);
-        for (List<String> secondaryLabel : secondary) {
-            mRoot.addDomain(secondaryLabel.iterator(), Match.Secondary);
-        }
-        // Primary overwrites secondary.
-        mRoot.addDomain(primary.iterator(), Match.Primary);
-    }
-
-    /**
-     * Check if domain is either a the same or a sub-domain of any of the domains in the domain tree
-     * in this matcher, i.e. all or or a sub-set of the labels in domain matches a path in the tree.
-     * @param domain Domain to be checked.
-     * @return None if domain is not a sub-domain, Primary if it matched one of the primary domains
-     * or Secondary if it matched on of the secondary domains.
-     */
-    public Match isSubDomain(List<String> domain) {
-
-        Label label = mRoot;
-        for (String labelString : domain) {
-            label = label.getSubLabel(labelString);
-            if (label == null) {
-                return Match.None;
-            } else if (label.getMatch() != Match.None) {
-                return label.getMatch();
-            }
-        }
-        return Match.None;  // Domain is a super domain
-    }
-
-    public static boolean arg2SubdomainOfArg1(List<String> arg1, List<String> arg2) {
-        if (arg2.size() < arg1.size()) {
-            return false;
-        }
-
-        Iterator<String> l1 = arg1.iterator();
-        Iterator<String> l2 = arg2.iterator();
-
-        while(l1.hasNext()) {
-            if (!l1.next().equals(l2.next())) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public String toString() {
-        return "Domain matcher " + mRoot;
-    }
-
-    private static final String[] TestDomains = {
-            "garbage.apple.com",
-            "apple.com",
-            "com",
-            "jan.android.google.com.",
-            "jan.android.google.com",
-            "android.google.com",
-            "google.com",
-            "jan.android.google.net.",
-            "jan.android.google.net",
-            "android.google.net",
-            "google.net",
-            "net.",
-            "."
-    };
-
-    public static void main(String[] args) {
-        DomainMatcher dm1 = new DomainMatcher(Utils.splitDomain("android.google.com"),
-                Collections.<List<String>>emptyList());
-        for (String domain : TestDomains) {
-            System.out.println(domain + ": " + dm1.isSubDomain(Utils.splitDomain(domain)));
-        }
-        List<List<String>> secondaries = new ArrayList<List<String>>();
-        secondaries.add(Utils.splitDomain("apple.com"));
-        secondaries.add(Utils.splitDomain("net"));
-        DomainMatcher dm2 = new DomainMatcher(Utils.splitDomain("android.google.com"), secondaries);
-        for (String domain : TestDomains) {
-            System.out.println(domain + ": " + dm2.isSubDomain(Utils.splitDomain(domain)));
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/HomeSP.java b/service/java/com/android/server/wifi/hotspot2/pps/HomeSP.java
deleted file mode 100644
index 160a056..0000000
--- a/service/java/com/android/server/wifi/hotspot2/pps/HomeSP.java
+++ /dev/null
@@ -1,374 +0,0 @@
-package com.android.server.wifi.hotspot2.pps;
-
-import android.util.Log;
-
-import com.android.server.wifi.SIMAccessor;
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.CellularNetwork;
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.anqp.DomainNameElement;
-import com.android.server.wifi.anqp.NAIRealmElement;
-import com.android.server.wifi.anqp.RoamingConsortiumElement;
-import com.android.server.wifi.anqp.ThreeGPPNetworkElement;
-import com.android.server.wifi.hotspot2.AuthMatch;
-import com.android.server.wifi.hotspot2.NetworkDetail;
-import com.android.server.wifi.hotspot2.PasspointMatch;
-import com.android.server.wifi.hotspot2.Utils;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static com.android.server.wifi.anqp.Constants.ANQPElementType;
-
-/**
- * This object describes the Home SP sub tree in the "PerProviderSubscription MO" described in
- * the Hotspot 2.0 specification, section 9.1.
- * As a convenience, the object also refers to the other sub-parts of the full
- * PerProviderSubscription tree.
- */
-public class HomeSP {
-    private final Map<String, Long> mSSIDs;        // SSID, HESSID, [0,N]
-    private final String mFQDN;
-    private final DomainMatcher mDomainMatcher;
-    private final Set<String> mOtherHomePartners;
-    private final HashSet<Long> mRoamingConsortiums;    // [0,N]
-    private final Set<Long> mMatchAnyOIs;           // [0,N]
-    private final List<Long> mMatchAllOIs;          // [0,N]
-
-    private final Credential mCredential;
-
-    // Informational:
-    private final String mFriendlyName;             // [1]
-    private final String mIconURL;                  // [0,1]
-
-    private final Policy mPolicy;
-    private final int mCredentialPriority;
-    private final Map<String, String> mAAATrustRoots;
-    private final UpdateInfo mSubscriptionUpdate;
-    private final SubscriptionParameters mSubscriptionParameters;
-    private final int mUpdateIdentifier;
-
-    @Deprecated
-    public HomeSP(Map<String, Long> ssidMap,
-                   /*@NotNull*/ String fqdn,
-                   /*@NotNull*/ HashSet<Long> roamingConsortiums,
-                   /*@NotNull*/ Set<String> otherHomePartners,
-                   /*@NotNull*/ Set<Long> matchAnyOIs,
-                   /*@NotNull*/ List<Long> matchAllOIs,
-                   String friendlyName,
-                   String iconURL,
-                   Credential credential) {
-
-        mSSIDs = ssidMap;
-        List<List<String>> otherPartners = new ArrayList<>(otherHomePartners.size());
-        for (String otherPartner : otherHomePartners) {
-            otherPartners.add(Utils.splitDomain(otherPartner));
-        }
-        mOtherHomePartners = otherHomePartners;
-        mFQDN = fqdn;
-        mDomainMatcher = new DomainMatcher(Utils.splitDomain(fqdn), otherPartners);
-        mRoamingConsortiums = roamingConsortiums;
-        mMatchAnyOIs = matchAnyOIs;
-        mMatchAllOIs = matchAllOIs;
-        mFriendlyName = friendlyName;
-        mIconURL = iconURL;
-        mCredential = credential;
-
-        mPolicy = null;
-        mCredentialPriority = -1;
-        mAAATrustRoots = null;
-        mSubscriptionUpdate = null;
-        mSubscriptionParameters = null;
-        mUpdateIdentifier = -1;
-    }
-
-    public HomeSP(Map<String, Long> ssidMap,
-                   /*@NotNull*/ String fqdn,
-                   /*@NotNull*/ HashSet<Long> roamingConsortiums,
-                   /*@NotNull*/ Set<String> otherHomePartners,
-                   /*@NotNull*/ Set<Long> matchAnyOIs,
-                   /*@NotNull*/ List<Long> matchAllOIs,
-                   String friendlyName,
-                   String iconURL,
-                   Credential credential,
-
-                  Policy policy,
-                  int credentialPriority,
-                  Map<String, String> AAATrustRoots,
-                  UpdateInfo subscriptionUpdate,
-                  SubscriptionParameters subscriptionParameters,
-                  int updateIdentifier) {
-
-        mSSIDs = ssidMap;
-        List<List<String>> otherPartners = new ArrayList<>(otherHomePartners.size());
-        for (String otherPartner : otherHomePartners) {
-            otherPartners.add(Utils.splitDomain(otherPartner));
-        }
-        mOtherHomePartners = otherHomePartners;
-        mFQDN = fqdn;
-        mDomainMatcher = new DomainMatcher(Utils.splitDomain(fqdn), otherPartners);
-        mRoamingConsortiums = roamingConsortiums;
-        mMatchAnyOIs = matchAnyOIs;
-        mMatchAllOIs = matchAllOIs;
-        mFriendlyName = friendlyName;
-        mIconURL = iconURL;
-        mCredential = credential;
-
-        mPolicy = policy;
-        mCredentialPriority = credentialPriority;
-        mAAATrustRoots = AAATrustRoots;
-        mSubscriptionUpdate = subscriptionUpdate;
-        mSubscriptionParameters = subscriptionParameters;
-        mUpdateIdentifier = updateIdentifier;
-    }
-
-    public int getUpdateIdentifier() {
-        return mUpdateIdentifier;
-    }
-
-    public UpdateInfo getSubscriptionUpdate() {
-        return mSubscriptionUpdate;
-    }
-
-    public Policy getPolicy() {
-        return mPolicy;
-    }
-
-    public PasspointMatch match(NetworkDetail networkDetail,
-                                Map<ANQPElementType, ANQPElement> anqpElementMap,
-                                SIMAccessor simAccessor) {
-
-        List<String> imsis = simAccessor.getMatchingImsis(mCredential.getImsi());
-
-        PasspointMatch spMatch = matchSP(networkDetail, anqpElementMap, imsis);
-
-        if (spMatch == PasspointMatch.Incomplete || spMatch == PasspointMatch.Declined) {
-            return spMatch;
-        }
-
-        if (imsiMatch(imsis, (ThreeGPPNetworkElement)
-                anqpElementMap.get(ANQPElementType.ANQP3GPPNetwork)) != null) {
-            // PLMN match, promote sp match to roaming if necessary.
-            return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch;
-        }
-
-        NAIRealmElement naiRealmElement =
-                (NAIRealmElement) anqpElementMap.get(ANQPElementType.ANQPNAIRealm);
-
-        int authMatch = naiRealmElement != null ?
-                naiRealmElement.match(mCredential) :
-                AuthMatch.Indeterminate;
-        
-        Log.d(Utils.hs2LogTag(getClass()), networkDetail.toKeyString() + " match on " + mFQDN +
-                ": " + spMatch + ", auth " + AuthMatch.toString(authMatch));
-
-        if (authMatch == AuthMatch.None) {
-            // Distinct auth mismatch, demote authentication.
-            return PasspointMatch.None;
-        }
-        else if ((authMatch & AuthMatch.Realm) == 0) {
-            // No realm match, return sp match as is.
-            return spMatch;
-        }
-        else {
-            // Realm match, promote sp match to roaming if necessary.
-            return spMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider : spMatch;
-        }
-    }
-
-    public PasspointMatch matchSP(NetworkDetail networkDetail,
-                                Map<ANQPElementType, ANQPElement> anqpElementMap,
-                                List<String> imsis) {
-
-        if (mSSIDs.containsKey(networkDetail.getSSID())) {
-            Long hessid = mSSIDs.get(networkDetail.getSSID());
-            if (hessid == null || networkDetail.getHESSID() == hessid) {
-                Log.d(Utils.hs2LogTag(getClass()), "match SSID");
-                return PasspointMatch.HomeProvider;
-            }
-        }
-
-        Set<Long> anOIs = new HashSet<>();
-
-        if (networkDetail.getRoamingConsortiums() != null) {
-            for (long oi : networkDetail.getRoamingConsortiums()) {
-                anOIs.add(oi);
-            }
-        }
-
-        boolean validANQP = anqpElementMap != null &&
-                Constants.hasBaseANQPElements(anqpElementMap.keySet());
-
-        RoamingConsortiumElement rcElement = validANQP ?
-                (RoamingConsortiumElement) anqpElementMap.get(ANQPElementType.ANQPRoamingConsortium)
-                : null;
-        if (rcElement != null) {
-            anOIs.addAll(rcElement.getOIs());
-        }
-
-        // It may seem reasonable to check for home provider match prior to checking for roaming
-        // relationship, but it is possible to avoid an ANQP query if it turns out that the
-        // "match all" rule fails based only on beacon info only.
-        boolean roamingMatch = false;
-
-        if (!mMatchAllOIs.isEmpty()) {
-            boolean matchesAll = true;
-
-            for (long spOI : mMatchAllOIs) {
-                if (!anOIs.contains(spOI)) {
-                    matchesAll = false;
-                    break;
-                }
-            }
-            if (matchesAll) {
-                roamingMatch = true;
-            }
-            else {
-                if (validANQP || networkDetail.getAnqpOICount() == 0) {
-                    return PasspointMatch.Declined;
-                }
-                else {
-                    return PasspointMatch.Incomplete;
-                }
-            }
-        }
-
-        if (!roamingMatch &&
-                (!Collections.disjoint(mMatchAnyOIs, anOIs) ||
-                        !Collections.disjoint(mRoamingConsortiums, anOIs))) {
-            roamingMatch = true;
-        }
-
-        if (!validANQP) {
-            return PasspointMatch.Incomplete;
-        }
-
-        DomainNameElement domainNameElement =
-                (DomainNameElement) anqpElementMap.get(ANQPElementType.ANQPDomName);
-
-        if (domainNameElement != null) {
-            for (String domain : domainNameElement.getDomains()) {
-                List<String> anLabels = Utils.splitDomain(domain);
-                DomainMatcher.Match match = mDomainMatcher.isSubDomain(anLabels);
-                if (match != DomainMatcher.Match.None) {
-                    return PasspointMatch.HomeProvider;
-                }
-
-                if (imsiMatch(imsis, anLabels) != null) {
-                    return PasspointMatch.HomeProvider;
-                }
-            }
-        }
-
-        return roamingMatch ? PasspointMatch.RoamingProvider : PasspointMatch.None;
-    }
-
-    private String imsiMatch(List<String> imsis, ThreeGPPNetworkElement plmnElement) {
-        if (imsis == null || plmnElement == null || plmnElement.getPlmns().isEmpty()) {
-            return null;
-        }
-        for (CellularNetwork network : plmnElement.getPlmns()) {
-            for (String mccMnc : network) {
-                String imsi = imsiMatch(imsis, mccMnc);
-                if (imsi != null) {
-                    return imsi;
-                }
-            }
-        }
-        return null;
-    }
-
-    private String imsiMatch(List<String> imsis, List<String> fqdn) {
-        if (imsis == null) {
-            return null;
-        }
-        String mccMnc = Utils.getMccMnc(fqdn);
-        return mccMnc != null ? imsiMatch(imsis, mccMnc) : null;
-    }
-
-    private String imsiMatch(List<String> imsis, String mccMnc) {
-        if (mCredential.getImsi().matchesMccMnc(mccMnc)) {
-            for (String imsi : imsis) {
-                if (imsi.startsWith(mccMnc)) {
-                    return imsi;
-                }
-            }
-        }
-        return null;
-    }
-
-    public String getFQDN() { return mFQDN; }
-    public String getFriendlyName() { return mFriendlyName; }
-    public HashSet<Long> getRoamingConsortiums() { return mRoamingConsortiums; }
-    public Credential getCredential() { return mCredential; }
-
-    public Map<String, Long> getSSIDs() {
-        return mSSIDs;
-    }
-
-    public Collection<String> getOtherHomePartners() {
-        return mOtherHomePartners;
-    }
-
-    public Set<Long> getMatchAnyOIs() {
-        return mMatchAnyOIs;
-    }
-
-    public List<Long> getMatchAllOIs() {
-        return mMatchAllOIs;
-    }
-
-    public String getIconURL() {
-        return mIconURL;
-    }
-
-    public boolean deepEquals(HomeSP other) {
-        return mFQDN.equals(other.mFQDN) &&
-                mSSIDs.equals(other.mSSIDs) &&
-                mOtherHomePartners.equals(other.mOtherHomePartners) &&
-                mRoamingConsortiums.equals(other.mRoamingConsortiums) &&
-                mMatchAnyOIs.equals(other.mMatchAnyOIs) &&
-                mMatchAllOIs.equals(other.mMatchAllOIs) &&
-                mFriendlyName.equals(other.mFriendlyName) &&
-                Utils.compare(mIconURL, other.mIconURL) == 0 &&
-                mCredential.equals(other.mCredential);
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (this == thatObject) {
-            return true;
-        } else if (thatObject == null || getClass() != thatObject.getClass()) {
-            return false;
-        }
-
-        HomeSP that = (HomeSP) thatObject;
-        return mFQDN.equals(that.mFQDN);
-    }
-
-    @Override
-    public int hashCode() {
-        return mFQDN.hashCode();
-    }
-
-    @Override
-    public String toString() {
-        return "HomeSP{" +
-                "SSIDs=" + mSSIDs +
-                ", FQDN='" + mFQDN + '\'' +
-                ", DomainMatcher=" + mDomainMatcher +
-                ", RoamingConsortiums={" + Utils.roamingConsortiumsToString(mRoamingConsortiums) +
-                '}' +
-                ", MatchAnyOIs={" + Utils.roamingConsortiumsToString(mMatchAnyOIs) + '}' +
-                ", MatchAllOIs={" + Utils.roamingConsortiumsToString(mMatchAllOIs) + '}' +
-                ", Credential=" + mCredential +
-                ", FriendlyName='" + mFriendlyName + '\'' +
-                ", IconURL='" + mIconURL + '\'' +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/Policy.java b/service/java/com/android/server/wifi/hotspot2/pps/Policy.java
deleted file mode 100644
index 186afd1..0000000
--- a/service/java/com/android/server/wifi/hotspot2/pps/Policy.java
+++ /dev/null
@@ -1,208 +0,0 @@
-package com.android.server.wifi.hotspot2.pps;
-
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.omadm.OMAException;
-import com.android.server.wifi.hotspot2.omadm.OMANode;
-import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_Country;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_DLBandwidth;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_FQDN_Match;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_IPProtocol;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_MaximumBSSLoadValue;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_MinBackhaulThreshold;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_NetworkType;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_PolicyUpdate;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_PortNumber;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_PreferredRoamingPartnerList;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_Priority;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_RequiredProtoPortTuple;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_SPExclusionList;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_SSID;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_ULBandwidth;
-
-public class Policy {
-    private final List<PreferredRoamingPartner> mPreferredRoamingPartners;
-    private final List<MinBackhaul> mMinBackhaulThresholds;
-    private final UpdateInfo mPolicyUpdate;
-    private final List<String> mSPExclusionList;
-    private final Map<Integer, List<Integer>> mRequiredProtos;
-    private final int mMaxBSSLoad;
-
-    public Policy(OMANode node) throws OMAException {
-
-        OMANode rpNode = node.getChild(TAG_PreferredRoamingPartnerList);
-        if (rpNode == null) {
-            mPreferredRoamingPartners = null;
-        }
-        else {
-            mPreferredRoamingPartners = new ArrayList<>(rpNode.getChildren().size());
-            for (OMANode instance : rpNode.getChildren()) {
-                if (instance.isLeaf()) {
-                    throw new OMAException("Not expecting leaf node in " +
-                            TAG_PreferredRoamingPartnerList);
-                }
-                mPreferredRoamingPartners.add(new PreferredRoamingPartner(instance));
-            }
-        }
-
-        OMANode bhtNode = node.getChild(TAG_MinBackhaulThreshold);
-        if (bhtNode == null) {
-            mMinBackhaulThresholds = null;
-        }
-        else {
-            mMinBackhaulThresholds = new ArrayList<>(bhtNode.getChildren().size());
-            for (OMANode instance : bhtNode.getChildren()) {
-                if (instance.isLeaf()) {
-                    throw new OMAException("Not expecting leaf node in " +
-                            TAG_MinBackhaulThreshold);
-                }
-                mMinBackhaulThresholds.add(new MinBackhaul(instance));
-            }
-        }
-
-        mPolicyUpdate = new UpdateInfo(node.getChild(TAG_PolicyUpdate));
-
-        OMANode sxNode = node.getChild(TAG_SPExclusionList);
-        if (sxNode == null) {
-            mSPExclusionList = null;
-        }
-        else {
-            mSPExclusionList = new ArrayList<>(sxNode.getChildren().size());
-            for (OMANode instance : sxNode.getChildren()) {
-                if (instance.isLeaf()) {
-                    throw new OMAException("Not expecting leaf node in " + TAG_SPExclusionList);
-                }
-                mSPExclusionList
-                        .add(PasspointManagementObjectManager.getString(instance, TAG_SSID));
-            }
-        }
-
-        OMANode rptNode = node.getChild(TAG_RequiredProtoPortTuple);
-        if (rptNode == null) {
-            mRequiredProtos = null;
-        }
-        else {
-            mRequiredProtos = new HashMap<>(rptNode.getChildren().size());
-            for (OMANode instance : rptNode.getChildren()) {
-                if (instance.isLeaf()) {
-                    throw new OMAException("Not expecting leaf node in " +
-                            TAG_RequiredProtoPortTuple);
-                }
-                int protocol =
-                        (int) PasspointManagementObjectManager
-                                .getLong(instance, TAG_IPProtocol, null);
-                String[] portSegments =
-                        PasspointManagementObjectManager
-                                .getString(instance, TAG_PortNumber).split(",");
-                List<Integer> ports = new ArrayList<>(portSegments.length);
-                for (String portSegment : portSegments) {
-                    try {
-                        ports.add(Integer.parseInt(portSegment));
-                    }
-                    catch (NumberFormatException nfe) {
-                        throw new OMAException("Port is not a number: " + portSegment);
-                    }
-                }
-                mRequiredProtos.put(protocol, ports);
-            }
-        }
-
-        mMaxBSSLoad = (int) PasspointManagementObjectManager.getLong(node,
-                TAG_MaximumBSSLoadValue, Long.MAX_VALUE);
-    }
-
-    public List<PreferredRoamingPartner> getPreferredRoamingPartners() {
-        return mPreferredRoamingPartners;
-    }
-
-    public List<MinBackhaul> getMinBackhaulThresholds() {
-        return mMinBackhaulThresholds;
-    }
-
-    public UpdateInfo getPolicyUpdate() {
-        return mPolicyUpdate;
-    }
-
-    public List<String> getSPExclusionList() {
-        return mSPExclusionList;
-    }
-
-    public Map<Integer, List<Integer>> getRequiredProtos() {
-        return mRequiredProtos;
-    }
-
-    public int getMaxBSSLoad() {
-        return mMaxBSSLoad;
-    }
-
-    private static class PreferredRoamingPartner {
-        private final List<String> mDomain;
-        private final Boolean mIncludeSubDomains;
-        private final int mPriority;
-        private final String mCountry;
-
-        private PreferredRoamingPartner(OMANode node)
-                throws OMAException {
-
-            String[] segments =
-                    PasspointManagementObjectManager.getString(node, TAG_FQDN_Match).split(",");
-            if (segments.length != 2) {
-                throw new OMAException("Bad FQDN match string: " + TAG_FQDN_Match);
-            }
-            mDomain = Utils.splitDomain(segments[0]);
-            mIncludeSubDomains =
-                    PasspointManagementObjectManager.getSelection(TAG_FQDN_Match, segments[1]);
-            mPriority = (int) PasspointManagementObjectManager.getLong(node, TAG_Priority, null);
-            mCountry = PasspointManagementObjectManager.getString(node, TAG_Country);
-        }
-
-        @Override
-        public String toString() {
-            return "PreferredRoamingPartner{" +
-                    "domain=" + mDomain +
-                    ", includeSubDomains=" + mIncludeSubDomains +
-                    ", priority=" + mPriority +
-                    ", country='" + mCountry + '\'' +
-                    '}';
-        }
-    }
-
-    private static class MinBackhaul {
-        private final Boolean mHome;
-        private final long mDL;
-        private final long mUL;
-
-        private MinBackhaul(OMANode node) throws OMAException {
-            mHome = PasspointManagementObjectManager.getSelection(node, TAG_NetworkType);
-            mDL = PasspointManagementObjectManager.getLong(node, TAG_DLBandwidth, Long.MAX_VALUE);
-            mUL = PasspointManagementObjectManager.getLong(node, TAG_ULBandwidth, Long.MAX_VALUE);
-        }
-
-        @Override
-        public String toString() {
-            return "MinBackhaul{" +
-                    "home=" + mHome +
-                    ", DL=" + mDL +
-                    ", UL=" + mUL +
-                    '}';
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "Policy{" +
-                "preferredRoamingPartners=" + mPreferredRoamingPartners +
-                ", minBackhaulThresholds=" + mMinBackhaulThresholds +
-                ", policyUpdate=" + mPolicyUpdate +
-                ", SPExclusionList=" + mSPExclusionList +
-                ", requiredProtos=" + mRequiredProtos +
-                ", maxBSSLoad=" + mMaxBSSLoad +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/SubscriptionParameters.java b/service/java/com/android/server/wifi/hotspot2/pps/SubscriptionParameters.java
deleted file mode 100644
index 2e44d5d..0000000
--- a/service/java/com/android/server/wifi/hotspot2/pps/SubscriptionParameters.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.android.server.wifi.hotspot2.pps;
-
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.omadm.OMAException;
-import com.android.server.wifi.hotspot2.omadm.OMANode;
-import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_CreationDate;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_DataLimit;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_ExpirationDate;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_StartDate;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_TimeLimit;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_TypeOfSubscription;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_UsageLimits;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_UsageTimePeriod;
-
-public class SubscriptionParameters {
-    private final long mCDate;
-    private final long mXDate;
-    private final String mType;
-    private final List<Limit> mLimits;
-
-    public SubscriptionParameters(OMANode node) throws OMAException {
-        mCDate = PasspointManagementObjectManager.getTime(node.getChild(TAG_CreationDate));
-        mXDate = PasspointManagementObjectManager.getTime(node.getChild(TAG_ExpirationDate));
-        mType = PasspointManagementObjectManager.getString(node.getChild(TAG_TypeOfSubscription));
-
-        OMANode ulNode = node.getChild(TAG_UsageLimits);
-        if (ulNode == null) {
-            mLimits = null;
-        }
-        else {
-            mLimits = new ArrayList<>(ulNode.getChildren().size());
-            for (OMANode instance : ulNode.getChildren()) {
-                if (instance.isLeaf()) {
-                    throw new OMAException("Not expecting leaf node in " +
-                            TAG_UsageLimits);
-                }
-                mLimits.add(new Limit(instance));
-            }
-        }
-
-    }
-
-    private static class Limit {
-        private final long mDataLimit;
-        private final long mStartDate;
-        private final long mTimeLimit;
-        private final long mUsageTimePeriod;
-
-        private Limit(OMANode node) throws OMAException {
-            mDataLimit = PasspointManagementObjectManager
-                    .getLong(node, TAG_DataLimit, Long.MAX_VALUE);
-            mStartDate = PasspointManagementObjectManager
-                    .getTime(node.getChild(TAG_StartDate));
-            mTimeLimit = PasspointManagementObjectManager
-                    .getLong(node, TAG_TimeLimit, Long.MAX_VALUE)
-                    * PasspointManagementObjectManager.IntervalFactor;
-            mUsageTimePeriod = PasspointManagementObjectManager
-                    .getLong(node, TAG_UsageTimePeriod, null);
-        }
-
-        @Override
-        public String toString() {
-            return "Limit{" +
-                    "dataLimit=" + mDataLimit +
-                    ", startDate=" + Utils.toUTCString(mStartDate) +
-                    ", timeLimit=" + mTimeLimit +
-                    ", usageTimePeriod=" + mUsageTimePeriod +
-                    '}';
-        }
-    }
-
-    @Override
-    public String toString() {
-        return "SubscriptionParameters{" +
-                "cDate=" + Utils.toUTCString(mCDate) +
-                ", xDate=" + Utils.toUTCString(mXDate) +
-                ", type='" + mType + '\'' +
-                ", limits=" + mLimits +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/hotspot2/pps/UpdateInfo.java b/service/java/com/android/server/wifi/hotspot2/pps/UpdateInfo.java
deleted file mode 100644
index 99861dd..0000000
--- a/service/java/com/android/server/wifi/hotspot2/pps/UpdateInfo.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.android.server.wifi.hotspot2.pps;
-
-import android.util.Base64;
-
-import com.android.server.wifi.hotspot2.Utils;
-import com.android.server.wifi.hotspot2.omadm.OMAException;
-import com.android.server.wifi.hotspot2.omadm.OMANode;
-import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
-
-import java.nio.charset.StandardCharsets;
-
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_CertSHA256Fingerprint;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_CertURL;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_Password;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_Restriction;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_TrustRoot;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_URI;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_UpdateInterval;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_UpdateMethod;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_Username;
-import static com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager.TAG_UsernamePassword;
-
-public class UpdateInfo {
-    public enum UpdateRestriction {HomeSP, RoamingPartner, Unrestricted}
-
-    private final long mInterval;
-    private final boolean mSPPClientInitiated;
-    private final UpdateRestriction mUpdateRestriction;
-    private final String mURI;
-    private final String mUsername;
-    private final String mPassword;
-    private final String mCertURL;
-    private final String mCertFP;
-
-    public UpdateInfo(OMANode policyUpdate) throws OMAException {
-        mInterval = PasspointManagementObjectManager.getLong(policyUpdate, TAG_UpdateInterval, null)
-                * PasspointManagementObjectManager.IntervalFactor;
-        mSPPClientInitiated = PasspointManagementObjectManager.getSelection(policyUpdate,
-                TAG_UpdateMethod);
-        mUpdateRestriction =
-                PasspointManagementObjectManager.getSelection(policyUpdate, TAG_Restriction);
-        mURI = PasspointManagementObjectManager.getString(policyUpdate, TAG_URI);
-
-        OMANode unp = policyUpdate.getChild(TAG_UsernamePassword);
-        if (unp != null) {
-            mUsername = PasspointManagementObjectManager.getString(unp.getChild(TAG_Username));
-            String pw = PasspointManagementObjectManager.getString(unp.getChild(TAG_Password));
-            mPassword = new String(Base64.decode(pw.getBytes(StandardCharsets.US_ASCII),
-                    Base64.DEFAULT), StandardCharsets.UTF_8);
-        }
-        else {
-            mUsername = null;
-            mPassword = null;
-        }
-
-        OMANode trustRoot = PasspointManagementObjectManager.getChild(policyUpdate, TAG_TrustRoot);
-        mCertURL = PasspointManagementObjectManager.getString(trustRoot, TAG_CertURL);
-        mCertFP = PasspointManagementObjectManager.getString(trustRoot, TAG_CertSHA256Fingerprint);
-    }
-
-    public long getInterval() {
-        return mInterval;
-    }
-
-    public boolean isSPPClientInitiated() {
-        return mSPPClientInitiated;
-    }
-
-    public UpdateRestriction getUpdateRestriction() {
-        return mUpdateRestriction;
-    }
-
-    public String getURI() {
-        return mURI;
-    }
-
-    public String getUsername() {
-        return mUsername;
-    }
-
-    public String getPassword() {
-        return mPassword;
-    }
-
-    public String getCertURL() {
-        return mCertURL;
-    }
-
-    public String getCertFP() {
-        return mCertFP;
-    }
-
-    @Override
-    public String toString() {
-        return "UpdateInfo{" +
-                "interval=" + Utils.toHMS(mInterval) +
-                ", SPPClientInitiated=" + mSPPClientInitiated +
-                ", updateRestriction=" + mUpdateRestriction +
-                ", URI='" + mURI + '\'' +
-                ", username='" + mUsername + '\'' +
-                ", password=" + mPassword +
-                ", certURL='" + mCertURL + '\'' +
-                ", certFP='" + mCertFP + '\'' +
-                '}';
-    }
-}
diff --git a/service/java/com/android/server/wifi/nan/WifiNanClientState.java b/service/java/com/android/server/wifi/nan/WifiNanClientState.java
deleted file mode 100644
index 0707270..0000000
--- a/service/java/com/android/server/wifi/nan/WifiNanClientState.java
+++ /dev/null
@@ -1,180 +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.nan;
-
-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.os.RemoteException;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-public class WifiNanClientState {
-    private static final String TAG = "WifiNanClientState";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    /* 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 final SparseArray<WifiNanSessionState> mSessions = new SparseArray<>();
-
-    private int mUid;
-    private ConfigRequest mConfigRequest;
-
-    public WifiNanClientState(int uid, IWifiNanEventListener listener, int events) {
-        mUid = uid;
-        mListener = listener;
-        mEvents = events;
-    }
-
-    public void destroy() {
-        mListener = null;
-        for (int i = 0; i < mSessions.size(); ++i) {
-            mSessions.valueAt(i).destroy();
-        }
-        mSessions.clear();
-        mConfigRequest = null;
-    }
-
-    public void setConfigRequest(ConfigRequest configRequest) {
-        mConfigRequest = configRequest;
-    }
-
-    public ConfigRequest getConfigRequest() {
-        return mConfigRequest;
-    }
-
-    public int getUid() {
-        return mUid;
-    }
-
-    public WifiNanSessionState getNanSessionStateForPubSubId(int pubSubId) {
-        for (int i = 0; i < mSessions.size(); ++i) {
-            WifiNanSessionState session = mSessions.valueAt(i);
-            if (session.isPubSubIdSession(pubSubId)) {
-                return session;
-            }
-        }
-
-        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);
-        }
-
-        mSessions.put(sessionId, new WifiNanSessionState(sessionId, listener, events));
-    }
-
-    public void destroySession(int sessionId) {
-        WifiNanSessionState session = mSessions.get(sessionId);
-        if (session == null) {
-            Log.e(TAG, "destroySession: sessionId doesn't exist - " + sessionId);
-            return;
-        }
-
-        mSessions.delete(sessionId);
-        session.destroy();
-    }
-
-    public WifiNanSessionState getSession(int sessionId) {
-        return mSessions.get(sessionId);
-    }
-
-    public void onConfigCompleted(ConfigRequest completedConfig) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_CONFIG_COMPLETED) != 0) {
-            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();
-            } catch (RemoteException e) {
-                Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
-            }
-
-            return 1;
-        }
-
-        return 0;
-    }
-
-    public int onClusterChange(int flag, byte[] mac) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_IDENTITY_CHANGED) != 0) {
-            try {
-                mListener.onIdentityChanged();
-            } catch (RemoteException e) {
-                Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
-            }
-
-            return 1;
-        }
-
-        return 0;
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("NanClientState:");
-        pw.println("  mUid: " + mUid);
-        pw.println("  mConfigRequest: " + mConfigRequest);
-        pw.println("  mListener: " + mListener);
-        pw.println("  mEvents: " + mEvents);
-        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/WifiNanNative.java b/service/java/com/android/server/wifi/nan/WifiNanNative.java
deleted file mode 100644
index 8715719..0000000
--- a/service/java/com/android/server/wifi/nan/WifiNanNative.java
+++ /dev/null
@@ -1,627 +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.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.util.Log;
-
-import com.android.server.wifi.WifiNative;
-
-import libcore.util.HexEncoding;
-
-/**
- * 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";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    private static final int WIFI_SUCCESS = 0;
-
-    private static boolean sNanNativeInit = false;
-
-    private static WifiNanNative sWifiNanNativeSingleton;
-
-    private static native int registerNanNatives();
-
-    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
-        // initialization.
-        WifiNative dummy = WifiNative.getWlanNativeInterface();
-        if (dummy == null) {
-            Log.w(TAG, "can't get access to WifiNative");
-            return null;
-        }
-
-        if (sWifiNanNativeSingleton == null) {
-            sWifiNanNativeSingleton = new WifiNanNative();
-            registerNanNatives();
-        }
-
-        return sWifiNanNativeSingleton;
-    }
-
-    public static class Capabilities {
-        public int maxConcurrentNanClusters;
-        public int maxPublishes;
-        public int maxSubscribes;
-        public int maxServiceNameLen;
-        public int maxMatchFilterLen;
-        public int maxTotalMatchFilterLen;
-        public int maxServiceSpecificInfoLen;
-        public int maxVsaDataLen;
-        public int maxMeshDataLen;
-        public int maxNdiInterfaces;
-        public int maxNdpSessions;
-        public int maxAppInfoLen;
-
-        @Override
-        public String toString() {
-            return "Capabilities [maxConcurrentNanClusters=" + maxConcurrentNanClusters
-                    + ", maxPublishes=" + maxPublishes + ", maxSubscribes=" + maxSubscribes
-                    + ", maxServiceNameLen=" + maxServiceNameLen + ", maxMatchFilterLen="
-                    + maxMatchFilterLen + ", maxTotalMatchFilterLen=" + maxTotalMatchFilterLen
-                    + ", maxServiceSpecificInfoLen=" + maxServiceSpecificInfoLen
-                    + ", maxVsaDataLen=" + maxVsaDataLen + ", maxMeshDataLen=" + maxMeshDataLen
-                    + ", maxNdiInterfaces=" + maxNdiInterfaces + ", maxNdpSessions="
-                    + maxNdpSessions + ", maxAppInfoLen=" + maxAppInfoLen + "]";
-        }
-    }
-
-    /* package */ static native int initNanHandlersNative(Object 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");
-        synchronized (WifiNative.sLock) {
-            boolean halStarted = WifiNative.getWlanNativeInterface().isHalStarted();
-            if (!halStarted) {
-                halStarted = WifiNative.getWlanNativeInterface().startHal();
-            }
-            if (halStarted) {
-                int ret = initNanHandlersNative(WifiNative.class, WifiNative.sWlan0Index);
-                if (DBG) Log.d(TAG, "initNanHandlersNative: res=" + ret);
-                sNanNativeInit = ret == WIFI_SUCCESS;
-
-                if (sNanNativeInit) {
-                    ret = getCapabilitiesNative(
-                            WifiNanStateManager.getInstance().createNextTransactionId(),
-                            WifiNative.class,
-                            WifiNative.sWlan0Index);
-                    if (DBG) Log.d(TAG, "getCapabilitiesNative: res=" + ret);
-                }
-
-                return sNanNativeInit;
-            } else {
-                Log.w(TAG, "isNanInit: HAL not initialized");
-                return false;
-            }
-        }
-    }
-
-    private WifiNanNative() {
-        // do nothing
-    }
-
-    private static native int enableAndConfigureNative(short transactionId, Object cls, int iface,
-            ConfigRequest configRequest);
-
-    public void enableAndConfigure(short transactionId, ConfigRequest configRequest) {
-        boolean success;
-
-        if (VDBG) Log.d(TAG, "enableAndConfigure: configRequest=" + configRequest);
-        if (isNanInit(true)) {
-            int ret;
-            synchronized (WifiNative.sLock) {
-                ret = enableAndConfigureNative(transactionId, WifiNative.class,
-                        WifiNative.sWlan0Index, configRequest);
-            }
-            if (DBG) Log.d(TAG, "enableAndConfigureNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
-        } else {
-            Log.w(TAG, "enableAndConfigure: NanInit fails");
-            success = false;
-        }
-
-
-        // TODO: do something on !success - send failure message back
-    }
-
-    private static native int disableNative(short transactionId, Object cls, int iface);
-
-    public void disable(short transactionId) {
-        boolean success;
-
-        if (VDBG) Log.d(TAG, "disableNan");
-        if (isNanInit(true)) {
-            int ret;
-            synchronized (WifiNative.sLock) {
-                ret = disableNative(transactionId, WifiNative.class, WifiNative.sWlan0Index);
-            }
-            if (DBG) Log.d(TAG, "disableNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
-        } else {
-            Log.w(TAG, "disable: cannot initialize NAN");
-            success = 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);
-
-    public void publish(short transactionId, int publishId, PublishData publishData,
-            PublishSettings publishSettings) {
-        boolean success;
-
-        if (VDBG) {
-            Log.d(TAG, "publish: transactionId=" + transactionId + ",data='" + publishData
-                    + "', settings=" + publishSettings);
-        }
-
-        if (isNanInit(true)) {
-            int ret;
-            synchronized (WifiNative.sLock) {
-                ret = publishNative(transactionId, publishId, WifiNative.class,
-                        WifiNative.sWlan0Index, publishData, publishSettings);
-            }
-            if (DBG) Log.d(TAG, "publishNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
-        } else {
-            Log.w(TAG, "publish: cannot initialize NAN");
-            success = 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);
-
-    public void subscribe(short transactionId, int subscribeId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        boolean success;
-
-        if (VDBG) {
-            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", data='" + subscribeData
-                    + "', settings=" + subscribeSettings);
-        }
-
-        if (isNanInit(true)) {
-            int ret;
-            synchronized (WifiNative.sLock) {
-                ret = subscribeNative(transactionId, subscribeId, WifiNative.class,
-                        WifiNative.sWlan0Index, subscribeData, subscribeSettings);
-            }
-            if (DBG) Log.d(TAG, "subscribeNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
-        } else {
-            Log.w(TAG, "subscribe: cannot initialize NAN");
-            success = 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);
-
-    public void sendMessage(short transactionId, int pubSubId, int requestorInstanceId, byte[] dest,
-            byte[] message, int messageLength) {
-        boolean success;
-
-        if (VDBG) {
-            Log.d(TAG,
-                    "sendMessage: transactionId=" + transactionId + ", pubSubId=" + pubSubId
-                            + ", requestorInstanceId=" + requestorInstanceId + ", dest="
-                            + String.valueOf(HexEncoding.encode(dest)) + ", messageLength="
-                            + messageLength);
-        }
-
-        if (isNanInit(true)) {
-            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;
-        } else {
-            Log.w(TAG, "sendMessage: cannot initialize NAN");
-            success = false;
-        }
-
-        // TODO: do something on !success - send failure message back
-    }
-
-    private static native int stopPublishNative(short transactionId, Object cls, int iface,
-            int pubSubId);
-
-    public void stopPublish(short transactionId, int pubSubId) {
-        boolean success;
-
-        if (VDBG) {
-            Log.d(TAG, "stopPublish: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
-        }
-
-        if (isNanInit(true)) {
-            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;
-        } else {
-            Log.w(TAG, "stopPublish: cannot initialize NAN");
-            success = false;
-        }
-
-        // TODO: do something on !success - send failure message back
-    }
-
-    private static native int stopSubscribeNative(short transactionId, Object cls, int iface,
-            int pubSubId);
-
-    public void stopSubscribe(short transactionId, int pubSubId) {
-        boolean success;
-
-        if (VDBG) {
-            Log.d(TAG, "stopSubscribe: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
-        }
-
-        if (isNanInit(true)) {
-            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;
-        } else {
-            Log.w(TAG, "stopSubscribe: cannot initialize NAN");
-            success = false;
-        }
-
-        // TODO: do something on !success - send failure message back
-    }
-
-    // EVENTS
-
-    // NanResponseType for API responses: will add values as needed
-    public static final int NAN_RESPONSE_ENABLED = 0;
-    public static final int NAN_RESPONSE_PUBLISH = 2;
-    public static final int NAN_RESPONSE_PUBLISH_CANCEL = 3;
-    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_GET_CAPABILITIES = 12;
-
-    // direct copy from wifi_nan.h: need to keep in sync
-    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;
-    public static final int NAN_STATUS_INVALID_MSG_VERSION = 3;
-    public static final int NAN_STATUS_INVALID_MSG_LEN = 4;
-    public static final int NAN_STATUS_INVALID_MSG_ID = 5;
-    public static final int NAN_STATUS_INVALID_HANDLE = 6;
-    public static final int NAN_STATUS_NO_SPACE_AVAILABLE = 7;
-    public static final int NAN_STATUS_INVALID_PUBLISH_TYPE = 8;
-    public static final int NAN_STATUS_INVALID_TX_TYPE = 9;
-    public static final int NAN_STATUS_INVALID_MATCH_ALGORITHM = 10;
-    public static final int NAN_STATUS_DISABLE_IN_PROGRESS = 11;
-    public static final int NAN_STATUS_INVALID_TLV_LEN = 12;
-    public static final int NAN_STATUS_INVALID_TLV_TYPE = 13;
-    public static final int NAN_STATUS_MISSING_TLV_TYPE = 14;
-    public static final int NAN_STATUS_INVALID_TOTAL_TLVS_LEN = 15;
-    public static final int NAN_STATUS_INVALID_MATCH_HANDLE = 16;
-    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;
-
-    // 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;
-    public static final int NAN_STATUS_INVALID_MASTER_PREFERENCE_VALUE = 4099;
-    public static final int NAN_STATUS_INVALID_LOW_CLUSTER_ID_VALUE = 4100;
-    public static final int NAN_STATUS_INVALID_HIGH_CLUSTER_ID_VALUE = 4101;
-    public static final int NAN_STATUS_INVALID_BACKGROUND_SCAN_PERIOD = 4102;
-    public static final int NAN_STATUS_INVALID_RSSI_PROXIMITY_VALUE = 4103;
-    public static final int NAN_STATUS_INVALID_SCAN_CHANNEL = 4104;
-    public static final int NAN_STATUS_INVALID_POST_NAN_CONNECTIVITY_CAPABILITIES_BITMAP = 4105;
-    public static final int NAN_STATUS_INVALID_FA_MAP_NUMCHAN_VALUE = 4106;
-    public static final int NAN_STATUS_INVALID_FA_MAP_DURATION_VALUE = 4107;
-    public static final int NAN_STATUS_INVALID_FA_MAP_CLASS_VALUE = 4108;
-    public static final int NAN_STATUS_INVALID_FA_MAP_CHANNEL_VALUE = 4109;
-    public static final int NAN_STATUS_INVALID_FA_MAP_AVAILABILITY_INTERVAL_BITMAP_VALUE = 4110;
-    public static final int NAN_STATUS_INVALID_FA_MAP_MAP_ID = 4111;
-    public static final int NAN_STATUS_INVALID_POST_NAN_DISCOVERY_CONN_TYPE_VALUE = 4112;
-    public static final int NAN_STATUS_INVALID_POST_NAN_DISCOVERY_DEVICE_ROLE_VALUE = 4113;
-    public static final int NAN_STATUS_INVALID_POST_NAN_DISCOVERY_DURATION_VALUE = 4114;
-    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;
-
-    // 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;
-    public static final int NAN_TERMINATED_REASON_FAILURE = 8195;
-    public static final int NAN_TERMINATED_REASON_COUNT_REACHED = 8196;
-    public static final int NAN_TERMINATED_REASON_DE_SHUTDOWN = 8197;
-    public static final int NAN_TERMINATED_REASON_DISABLE_IN_PROGRESS = 8198;
-    public static final int NAN_TERMINATED_REASON_POST_DISC_ATTR_EXPIRED = 8199;
-    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) {
-        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_INVALID_RSSI_CLOSE_VALUE:
-            case NAN_STATUS_INVALID_RSSI_MIDDLE_VALUE:
-            case NAN_STATUS_INVALID_HOP_COUNT_LIMIT:
-            case NAN_STATUS_INVALID_MASTER_PREFERENCE_VALUE:
-            case NAN_STATUS_INVALID_LOW_CLUSTER_ID_VALUE:
-            case NAN_STATUS_INVALID_HIGH_CLUSTER_ID_VALUE:
-            case NAN_STATUS_INVALID_BACKGROUND_SCAN_PERIOD:
-            case NAN_STATUS_INVALID_RSSI_PROXIMITY_VALUE:
-            case NAN_STATUS_INVALID_SCAN_CHANNEL:
-            case NAN_STATUS_INVALID_POST_NAN_CONNECTIVITY_CAPABILITIES_BITMAP:
-            case NAN_STATUS_INVALID_FA_MAP_NUMCHAN_VALUE:
-            case NAN_STATUS_INVALID_FA_MAP_DURATION_VALUE:
-            case NAN_STATUS_INVALID_FA_MAP_CLASS_VALUE:
-            case NAN_STATUS_INVALID_FA_MAP_CHANNEL_VALUE:
-            case NAN_STATUS_INVALID_FA_MAP_AVAILABILITY_INTERVAL_BITMAP_VALUE:
-            case NAN_STATUS_INVALID_FA_MAP_MAP_ID:
-            case NAN_STATUS_INVALID_POST_NAN_DISCOVERY_CONN_TYPE_VALUE:
-            case NAN_STATUS_INVALID_POST_NAN_DISCOVERY_DEVICE_ROLE_VALUE:
-            case NAN_STATUS_INVALID_POST_NAN_DISCOVERY_DURATION_VALUE:
-            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;
-
-                // publish/subscribe termination reasons
-            case NAN_TERMINATED_REASON_TIMEOUT:
-            case NAN_TERMINATED_REASON_USER_REQUEST:
-            case NAN_TERMINATED_REASON_COUNT_REACHED:
-                return WifiNanSessionListener.TERMINATE_REASON_DONE;
-
-            case NAN_TERMINATED_REASON_INVALID:
-            case NAN_TERMINATED_REASON_FAILURE:
-            case NAN_TERMINATED_REASON_DE_SHUTDOWN:
-            case NAN_TERMINATED_REASON_DISABLE_IN_PROGRESS:
-            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 WifiNanSessionListener.FAIL_REASON_OTHER;
-    }
-
-    // callback from native
-    private static void onNanNotifyResponse(short transactionId, int responseType, int status,
-            int value) {
-        if (VDBG) {
-            Log.v(TAG,
-                    "onNanNotifyResponse: transactionId=" + transactionId + ", responseType="
-                    + responseType + ", status=" + status + ", value=" + value);
-        }
-
-        switch (responseType) {
-            case NAN_RESPONSE_ENABLED:
-                if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onConfigCompleted(transactionId);
-                } else {
-                    WifiNanStateManager.getInstance().onConfigFailed(transactionId,
-                            translateHalStatusToPublicStatus(status));
-                }
-                break;
-            case NAN_RESPONSE_PUBLISH_CANCEL:
-                if (status != NAN_STATUS_SUCCESS) {
-                    Log.e(TAG, "onNanNotifyResponse: NAN_RESPONSE_PUBLISH_CANCEL error - status="
-                            + status + ", value=" + value);
-                }
-                break;
-            case NAN_RESPONSE_TRANSMIT_FOLLOWUP:
-                if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onMessageSendSuccess(transactionId);
-                } else {
-                    WifiNanStateManager.getInstance().onMessageSendFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
-                }
-                break;
-            case NAN_RESPONSE_SUBSCRIBE_CANCEL:
-                if (status != NAN_STATUS_SUCCESS) {
-                    Log.e(TAG, "onNanNotifyResponse: NAN_RESPONSE_PUBLISH_CANCEL error - status="
-                            + status + ", value=" + value);
-                }
-                break;
-            default:
-                WifiNanStateManager.getInstance().onUnknownTransaction(responseType, transactionId,
-                        translateHalStatusToPublicStatus(status));
-                break;
-        }
-    }
-
-    private static void onNanNotifyResponsePublishSubscribe(short transactionId, int responseType,
-            int status, int value, int pubSubId) {
-        if (VDBG) {
-            Log.v(TAG,
-                    "onNanNotifyResponsePublishSubscribe: transactionId=" + transactionId
-                            + ", responseType=" + responseType + ", status=" + status + ", value="
-                            + value + ", pubSubId=" + pubSubId);
-        }
-
-        switch (responseType) {
-            case NAN_RESPONSE_PUBLISH:
-                if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onPublishSuccess(transactionId, pubSubId);
-                } else {
-                    WifiNanStateManager.getInstance().onPublishFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
-                }
-                break;
-            case NAN_RESPONSE_SUBSCRIBE:
-                if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onSubscribeSuccess(transactionId, pubSubId);
-                } else {
-                    WifiNanStateManager.getInstance().onSubscribeFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
-                }
-                break;
-            default:
-                WifiNanStateManager.getInstance().onUnknownTransaction(responseType, transactionId,
-                        translateHalStatusToPublicStatus(status));
-                break;
-        }
-    }
-
-    private static void onNanNotifyResponseCapabilities(short transactionId, int status, int value,
-            Capabilities capabilities) {
-        if (VDBG) {
-            Log.v(TAG, "onNanNotifyResponsePublishSubscribe: transactionId=" + transactionId
-                    + ", status=" + status + ", value=" + value + ", capabilities=" + capabilities);
-        }
-
-        if (status == NAN_STATUS_SUCCESS) {
-            WifiNanStateManager.getInstance().onCapabilitiesUpdate(transactionId, capabilities);
-        } else {
-            Log.e(TAG,
-                    "onNanNotifyResponseCapabilities: error status=" + status + ", value=" + value);
-        }
-    }
-
-    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;
-
-    // callback from native
-    private static void onDiscoveryEngineEvent(int eventType, byte[] mac) {
-        if (VDBG) {
-            Log.v(TAG, "onDiscoveryEngineEvent: eventType=" + eventType + ", mac="
-                    + String.valueOf(HexEncoding.encode(mac)));
-        }
-
-        if (eventType == NAN_EVENT_ID_DISC_MAC_ADDR) {
-            WifiNanStateManager.getInstance().onInterfaceAddressChange(mac);
-        } else if (eventType == NAN_EVENT_ID_STARTED_CLUSTER) {
-            WifiNanStateManager.getInstance()
-                    .onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, mac);
-        } else if (eventType == NAN_EVENT_ID_JOINED_CLUSTER) {
-            WifiNanStateManager.getInstance()
-                    .onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, mac);
-        } else {
-            Log.w(TAG, "onDiscoveryEngineEvent: invalid eventType=" + eventType);
-        }
-    }
-
-    // callback from native
-    private static void onMatchEvent(int pubSubId, int requestorInstanceId, byte[] mac,
-            byte[] serviceSpecificInfo, int serviceSpecificInfoLength, byte[] matchFilter,
-            int matchFilterLength) {
-        if (VDBG) {
-            Log.v(TAG, "onMatchEvent: pubSubId=" + pubSubId + ", requestorInstanceId="
-                    + requestorInstanceId + ", mac=" + String.valueOf(HexEncoding.encode(mac))
-                    + ", serviceSpecificInfo=" + serviceSpecificInfo + ", matchFilterLength="
-                    + matchFilterLength + ", matchFilter=" + matchFilter);
-        }
-
-        WifiNanStateManager.getInstance().onMatch(pubSubId, requestorInstanceId, mac,
-                serviceSpecificInfo, serviceSpecificInfoLength, matchFilter, matchFilterLength);
-    }
-
-    // callback from native
-    private static void onPublishTerminated(int publishId, int status) {
-        if (VDBG) Log.v(TAG, "onPublishTerminated: publishId=" + publishId + ", status=" + status);
-
-        WifiNanStateManager.getInstance().onPublishTerminated(publishId,
-                translateHalStatusToPublicStatus(status));
-    }
-
-    // callback from native
-    private static void onSubscribeTerminated(int subscribeId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onSubscribeTerminated: subscribeId=" + subscribeId + ", status=" + status);
-        }
-
-        WifiNanStateManager.getInstance().onSubscribeTerminated(subscribeId,
-                translateHalStatusToPublicStatus(status));
-    }
-
-    // callback from native
-    private static void onFollowupEvent(int pubSubId, int requestorInstanceId, byte[] mac,
-            byte[] message, int messageLength) {
-        if (VDBG) {
-            Log.v(TAG, "onFollowupEvent: pubSubId=" + pubSubId + ", requestorInstanceId="
-                    + requestorInstanceId + ", mac=" + String.valueOf(HexEncoding.encode(mac))
-                    + ", messageLength=" + messageLength);
-        }
-
-        WifiNanStateManager.getInstance().onMessageReceived(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));
-    }
-}
diff --git a/service/java/com/android/server/wifi/nan/WifiNanService.java b/service/java/com/android/server/wifi/nan/WifiNanService.java
deleted file mode 100644
index b5920f2..0000000
--- a/service/java/com/android/server/wifi/nan/WifiNanService.java
+++ /dev/null
@@ -1,46 +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.nan;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.android.server.SystemService;
-
-public final class WifiNanService extends SystemService {
-    private static final String TAG = "WifiNanService";
-    final WifiNanServiceImpl mImpl;
-
-    public WifiNanService(Context context) {
-        super(context);
-        mImpl = new WifiNanServiceImpl(context);
-    }
-
-    @Override
-    public void onStart() {
-        Log.i(TAG, "Registering " + Context.WIFI_NAN_SERVICE);
-        publishBinderService(Context.WIFI_NAN_SERVICE, mImpl);
-    }
-
-    @Override
-    public void onBootPhase(int phase) {
-        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
-            mImpl.start();
-        }
-    }
-}
-
diff --git a/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java b/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
deleted file mode 100644
index deefe94..0000000
--- a/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
+++ /dev/null
@@ -1,245 +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.nan;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-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.os.Binder;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-public class WifiNanServiceImpl extends IWifiNanManager.Stub {
-    private static final String TAG = "WifiNanService";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    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;
-
-    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();
-    }
-
-    public void start() {
-        Log.i(TAG, "Starting Wi-Fi NAN service");
-
-        // TODO: share worker thread with other Wi-Fi handlers
-        HandlerThread wifiNanThread = new HandlerThread("wifiNanService");
-        wifiNanThread.start();
-
-        mStateManager.start(wifiNanThread.getLooper());
-    }
-
-    @Override
-    public void connect(final IBinder binder, IWifiNanEventListener listener, int events) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        final int uid = getCallingUid();
-
-        if (VDBG) Log.v(TAG, "connect: uid=" + uid);
-
-
-        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
-            @Override
-            public void binderDied() {
-                if (DBG) Log.d(TAG, "binderDied: uid=" + uid);
-                binder.unlinkToDeath(this, 0);
-
-                synchronized (mLock) {
-                    mDeathRecipientsByUid.delete(uid);
-                }
-
-                mStateManager.disconnect(uid);
-            }
-        };
-        synchronized (mLock) {
-            mDeathRecipientsByUid.put(uid, dr);
-        }
-        try {
-            binder.linkToDeath(dr, 0);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error on linkToDeath - " + e);
-        }
-
-        mStateManager.connect(uid, listener, events);
-    }
-
-    @Override
-    public void disconnect(IBinder binder) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        int uid = getCallingUid();
-
-        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid);
-
-        synchronized (mLock) {
-            IBinder.DeathRecipient dr = mDeathRecipientsByUid.get(uid);
-            if (dr != null) {
-                binder.unlinkToDeath(dr, 0);
-                mDeathRecipientsByUid.delete(uid);
-            }
-        }
-
-        mStateManager.disconnect(uid);
-    }
-
-    @Override
-    public void requestConfig(ConfigRequest configRequest) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) {
-            Log.v(TAG,
-                    "requestConfig: uid=" + getCallingUid() + ", configRequest=" + configRequest);
-        }
-
-        mStateManager.requestConfig(getCallingUid(), configRequest);
-    }
-
-    @Override
-    public void stopSession(int sessionId) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) Log.v(TAG, "stopSession: sessionId=" + sessionId + ", uid=" + getCallingUid());
-
-        mStateManager.stopSession(getCallingUid(), sessionId);
-    }
-
-    @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;
-        synchronized (mLock) {
-            sessionId = mNextSessionId++;
-        }
-
-        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);
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
-            pw.println("Permission Denial: can't dump WifiNanService from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
-            return;
-        }
-        pw.println("Wi-Fi NAN Service");
-        pw.println("  mNanSupported: " + mNanSupported);
-        pw.println("  mNextNetworkRequestToken: " + mNextNetworkRequestToken);
-        pw.println("  mNextSessionId: " + mNextSessionId);
-        pw.println("  mDeathRecipientsByUid: " + mDeathRecipientsByUid);
-        mStateManager.dump(fd, pw, args);
-    }
-
-    private void enforceAccessPermission() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
-    }
-
-    private void enforceChangePermission() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
-    }
-}
diff --git a/service/java/com/android/server/wifi/nan/WifiNanSessionState.java b/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
deleted file mode 100644
index ea64403..0000000
--- a/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
+++ /dev/null
@@ -1,255 +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.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.os.RemoteException;
-import android.util.Log;
-import android.util.SparseArray;
-
-import libcore.util.HexEncoding;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-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 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) {
-        mSessionId = sessionId;
-        mListener = listener;
-        mEvents = events;
-    }
-
-    public void destroy() {
-        stop(WifiNanStateManager.getInstance().createNextTransactionId());
-        if (mPubSubIdValid) {
-            mMacByRequestorInstanceId.clear();
-            mListener = null;
-            mPubSubIdValid = false;
-        }
-    }
-
-    public int getSessionId() {
-        return mSessionId;
-    }
-
-    public boolean isPubSubIdSession(int pubSubId) {
-        return mPubSubIdValid && 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");
-        }
-        mSessionType = SESSION_TYPE_PUBLISH;
-
-        WifiNanNative.getInstance().publish(transactionId, mPubSubIdValid ? mPubSubId : 0, data,
-                settings);
-    }
-
-    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");
-        }
-        mSessionType = SESSION_TYPE_SUBSCRIBE;
-
-        WifiNanNative.getInstance().subscribe(transactionId, mPubSubIdValid ? mPubSubId : 0, data,
-                settings);
-    }
-
-    public void 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;
-        }
-        byte[] peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
-
-        WifiNanNative.getInstance().sendMessage(transactionId, mPubSubId, peerId, peerMac, message,
-                messageLength);
-    }
-
-    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);
-        }
-    }
-
-    public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
-            int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
-        String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
-        mMacByRequestorInstanceId.put(requestorInstanceId, new String(HexEncoding.encode(peerMac)));
-
-        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);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onMessageReceived(int requestorInstanceId, byte[] peerMac, byte[] message,
-            int messageLength) {
-        String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
-        mMacByRequestorInstanceId.put(requestorInstanceId, new String(HexEncoding.encode(peerMac)));
-
-        if (DBG) {
-            Log.d(TAG, "onMessageReceived: previous peer MAC replaced - " + prevMac);
-        }
-
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED) != 0) {
-                mListener.onMessageReceived(requestorInstanceId, message, messageLength);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onMessageReceived: RemoteException (FYI): " + e);
-        }
-    }
-
-    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("  mMacByRequestorInstanceId: [" + mMacByRequestorInstanceId + "]");
-    }
-}
diff --git a/service/java/com/android/server/wifi/nan/WifiNanStateManager.java b/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
deleted file mode 100644
index f7bfa55..0000000
--- a/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
+++ /dev/null
@@ -1,1172 +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.nan;
-
-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.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.util.SparseArray;
-
-import libcore.util.HexEncoding;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-
-public class WifiNanStateManager {
-    private static final String TAG = "WifiNanStateManager";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    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;
-
-    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_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";
-    private static final String MESSAGE_BUNDLE_KEY_FILTER_DATA = "filter_data";
-    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 WifiNanNative.Capabilities mCapabilities;
-
-    private WifiNanStateHandler mHandler;
-
-    // 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 WifiNanStateManager() {
-        // EMPTY: singleton pattern
-    }
-
-    public static WifiNanStateManager getInstance() {
-        if (sNanStateManagerSingleton == null) {
-            sNanStateManagerSingleton = new WifiNanStateManager();
-        }
-
-        return sNanStateManagerSingleton;
-    }
-
-    public void start(Looper looper) {
-        Log.i(TAG, "start()");
-
-        mHandler = new WifiNanStateHandler(looper);
-    }
-
-    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);
-    }
-
-    public void disconnect(int uid) {
-        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT);
-        msg.arg1 = uid;
-        mHandler.sendMessage(msg);
-    }
-
-    public void requestConfig(int uid, ConfigRequest configRequest) {
-        Message msg = mHandler.obtainMessage(MESSAGE_REQUEST_CONFIG);
-        msg.arg1 = uid;
-        msg.obj = configRequest;
-        mHandler.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);
-    }
-
-    public void destroySession(int uid, int sessionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_DESTROY_SESSION);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.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);
-    }
-
-    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);
-    }
-
-    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);
-    }
-
-    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);
-    }
-
-    public void onCapabilitiesUpdate(short transactionId, WifiNanNative.Capabilities capabilities) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CAPABILITIES_UPDATED);
-        msg.arg1 = transactionId;
-        msg.obj = capabilities;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onConfigCompleted(short transactionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CONFIG_COMPLETED);
-        msg.arg1 = transactionId;
-        mHandler.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);
-    }
-
-    public void onPublishSuccess(short transactionId, int publishId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_SUCCESS);
-        msg.arg1 = transactionId;
-        msg.arg2 = publishId;
-        mHandler.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);
-    }
-
-    public void onMessageSendSuccess(short transactionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MESSAGE_SEND_SUCCESS);
-        msg.arg1 = transactionId;
-        mHandler.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);
-    }
-
-    public void onSubscribeSuccess(short transactionId, int subscribeId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_SUBSCRIBE_SUCCESS);
-        msg.arg1 = transactionId;
-        msg.arg2 = subscribeId;
-        mHandler.sendMessage(msg);
-    }
-
-    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);
-        msg.obj = mac;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onClusterChange(int flag, byte[] clusterId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CLUSTER_CHANGE);
-        msg.arg1 = flag;
-        msg.obj = clusterId;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onMatch(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);
-    }
-
-    public void onPublishTerminated(int publishId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_TERMINATED);
-        msg.arg1 = publishId;
-        msg.arg2 = status;
-        mHandler.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,
-            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);
-    }
-
-    public void onNanDown(int reason) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_NAN_DOWN);
-        msg.arg1 = reason;
-        mHandler.sendMessage(msg);
-    }
-
-    private class WifiNanStateHandler extends Handler {
-        WifiNanStateHandler(android.os.Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (DBG) {
-                Log.d(TAG, "Message: " + msg.what);
-            }
-            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;
-                }
-                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;
-                }
-                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);
-                    }
-
-                    sendFollowonMessageLocal(msg.arg1, sessionId, peerId, message, msg.arg2,
-                            messageId);
-                    break;
-                }
-                case MESSAGE_STOP_SESSION: {
-                    if (VDBG) {
-                        Log.d(TAG, "Stop session");
-                    }
-                    stopSessionLocal(msg.arg1, msg.arg2);
-                    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;
-                    byte[] peerMac = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS);
-                    byte[] serviceSpecificInfo = msg.getData()
-                            .getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA);
-                    int serviceSpecificInfoLength = msg.getData()
-                            .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;
-                    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;
-                }
-                default:
-                    Log.e(TAG, "Unknown message code: " + msg.what);
-            }
-        }
-    }
-
-    /*
-     * Transaction management classes & operations
-     */
-
-    // 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) {
-        if (VDBG) {
-            Log.v(TAG, "connect(): uid=" + uid + ", listener=" + listener + ", events=" + events);
-        }
-
-        if (mClients.get(uid) != null) {
-            Log.e(TAG, "connect: entry already exists for uid=" + uid);
-            return;
-        }
-
-        WifiNanClientState client = new WifiNanClientState(uid, listener, events);
-        mClients.put(uid, client);
-    }
-
-    private void disconnectLocal(int uid) {
-        if (VDBG) {
-            Log.v(TAG, "disconnect(): uid=" + uid);
-        }
-
-        WifiNanClientState client = mClients.get(uid);
-        mClients.delete(uid);
-
-        if (client == null) {
-            Log.e(TAG, "disconnect: no entry for uid=" + uid);
-            return;
-        }
-
-        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);
-        }
-
-        client.destroy();
-
-        if (mClients.size() == 0) {
-            WifiNanNative.getInstance().disable(createTransactionInfo().mTransactionId);
-            return;
-        }
-
-        ConfigRequest merged = mergeConfigRequests();
-
-        WifiNanNative.getInstance()
-                .enableAndConfigure(createTransactionInfoConfig(merged).mTransactionId, merged);
-    }
-
-    private void requestConfigLocal(int uid, ConfigRequest configRequest) {
-        if (VDBG) {
-            Log.v(TAG, "requestConfig(): uid=" + uid + ", configRequest=" + configRequest);
-        }
-
-        WifiNanClientState client = mClients.get(uid);
-        if (client == null) {
-            Log.e(TAG, "requestConfig: no client exists for uid=" + uid);
-            return;
-        }
-
-        client.setConfigRequest(configRequest);
-
-        ConfigRequest merged = mergeConfigRequests();
-
-        WifiNanNative.getInstance()
-                .enableAndConfigure(createTransactionInfoConfig(merged).mTransactionId, merged);
-    }
-
-    private void createSessionLocal(int uid, int sessionId, IWifiNanSessionListener listener,
-            int events) {
-        if (VDBG) {
-            Log.v(TAG, "createSession(): uid=" + uid + ", sessionId=" + sessionId + ", listener="
-                    + listener + ", events=" + events);
-        }
-
-        WifiNanClientState client = mClients.get(uid);
-        if (client == null) {
-            Log.e(TAG, "createSession: no client exists for uid=" + uid);
-            return;
-        }
-
-        client.createSession(sessionId, listener, events);
-    }
-
-    private void destroySessionLocal(int uid, int sessionId) {
-        if (VDBG) {
-            Log.v(TAG, "destroySession(): uid=" + uid + ", sessionId=" + sessionId);
-        }
-
-        WifiNanClientState client = mClients.get(uid);
-        if (client == null) {
-            Log.e(TAG, "destroySession: no client exists for uid=" + uid);
-            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);
-            }
-        }
-        for (Integer id : toRemove) {
-            mPendingResponses.removeAt(id);
-        }
-
-        client.destroySession(sessionId);
-    }
-
-    private void publishLocal(int uid, int sessionId, PublishData publishData,
-            PublishSettings publishSettings) {
-        if (VDBG) {
-            Log.v(TAG, "publish(): uid=" + uid + ", sessionId=" + sessionId + ", data="
-                    + publishData + ", settings=" + publishSettings);
-        }
-
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
-
-        info.mSession.publish(info.mTransactionId, publishData, publishSettings);
-    }
-
-    private void subscribeLocal(int uid, int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        if (VDBG) {
-            Log.v(TAG, "subscribe(): uid=" + uid + ", sessionId=" + sessionId + ", data="
-                    + subscribeData + ", settings=" + subscribeSettings);
-        }
-
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
-
-        info.mSession.subscribe(info.mTransactionId, subscribeData, subscribeSettings);
-    }
-
-    private void sendFollowonMessageLocal(int uid, int sessionId, int peerId, byte[] message,
-            int messageLength, int messageId) {
-        if (VDBG) {
-            Log.v(TAG, "sendMessage(): uid=" + uid + ", sessionId=" + sessionId + ", peerId="
-                    + peerId + ", messageLength=" + messageLength + ", messageId=" + messageId);
-        }
-
-        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);
-    }
-
-    /*
-     * Callbacks (calls from HAL/Native to service)
-     */
-
-    private void onCapabilitiesUpdatedLocal(short transactionId,
-            WifiNanNative.Capabilities capabilities) {
-        if (VDBG) {
-            Log.v(TAG, "onCapabilitiesUpdatedLocal: transactionId=" + transactionId
-                    + ", capabilites=" + capabilities);
-        }
-
-        mCapabilities = capabilities;
-    }
-
-    private void onConfigCompletedLocal(short transactionId) {
-        if (VDBG) {
-            Log.v(TAG, "onConfigCompleted: transactionId=" + transactionId);
-        }
-
-        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);
-        }
-    }
-
-    private void onConfigFailedLocal(short transactionId, int reason) {
-        if (VDBG) {
-            Log.v(TAG, "onEnableFailed: transactionId=" + transactionId + ", reason=" + reason);
-        }
-
-        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);
-        }
-    }
-
-    private void onNanDownLocal(int reason) {
-        if (VDBG) {
-            Log.v(TAG, "onNanDown: reason=" + reason);
-        }
-
-        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!");
-        }
-    }
-
-    private void onInterfaceAddressChangeLocal(byte[] mac) {
-        if (VDBG) {
-            Log.v(TAG, "onInterfaceAddressChange: mac=" + String.valueOf(HexEncoding.encode(mac)));
-        }
-
-        int interested = 0;
-        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!");
-        }
-    }
-
-    private void onClusterChangeLocal(int flag, byte[] clusterId) {
-        if (VDBG) {
-            Log.v(TAG, "onClusterChange: flag=" + flag + ", clusterId="
-                    + 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);
-        }
-    }
-
-    private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
-            byte[] serviceSpecificInfo, int serviceSpecificInfoLength, byte[] matchFilter,
-            int matchFilterLength) {
-        if (VDBG) {
-            Log.v(TAG, "onMatch: pubSubId=" + pubSubId + ", requestorInstanceId="
-                    + requestorInstanceId + ", peerMac="
-                    + String.valueOf(HexEncoding.encode(peerMac)) + ", serviceSpecificInfoLength="
-                    + serviceSpecificInfoLength + ", serviceSpecificInfo=" + serviceSpecificInfo
-                    + ", matchFilterLength=" + matchFilterLength + ", matchFilter=" + matchFilter);
-        }
-
-        WifiNanSessionState session = getNanSessionStateForPubSubId(pubSubId);
-        if (session == null) {
-            Log.e(TAG, "onMatch: no session found for pubSubId=" + pubSubId);
-            return;
-        }
-
-        session.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo,
-                serviceSpecificInfoLength, matchFilter, matchFilterLength);
-    }
-
-    private void onMessageReceivedLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
-            byte[] message, int messageLength) {
-        if (VDBG) {
-            Log.v(TAG,
-                    "onMessageReceived: pubSubId=" + pubSubId + ", requestorInstanceId="
-                            + requestorInstanceId + ", peerMac="
-                            + String.valueOf(HexEncoding.encode(peerMac)) + ", messageLength="
-                            + messageLength);
-        }
-
-        WifiNanSessionState session = getNanSessionStateForPubSubId(pubSubId);
-        if (session == null) {
-            Log.e(TAG, "onMessageReceived: no session found for pubSubId=" + pubSubId);
-            return;
-        }
-
-        session.onMessageReceived(requestorInstanceId, peerMac, message, messageLength);
-    }
-
-    private ConfigRequest mergeConfigRequests() {
-        if (VDBG) {
-            Log.v(TAG, "mergeConfigRequests(): mClients=[" + mClients + "]");
-        }
-
-        if (mClients.size() == 0) {
-            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
-        boolean support5gBand = false;
-        int masterPreference = 0;
-        boolean clusterIdValid = false;
-        int clusterLow = 0;
-        int clusterHigh = ConfigRequest.CLUSTER_ID_MAX;
-        for (int i = 0; i < mClients.size(); ++i) {
-            ConfigRequest cr = mClients.valueAt(i).getConfigRequest();
-
-            if (cr.mSupport5gBand) {
-                support5gBand = true;
-            }
-
-            masterPreference = Math.max(masterPreference, cr.mMasterPreference);
-
-            if (cr.mClusterLow != 0 || cr.mClusterHigh != ConfigRequest.CLUSTER_ID_MAX) {
-                if (!clusterIdValid) {
-                    clusterLow = cr.mClusterLow;
-                    clusterHigh = cr.mClusterHigh;
-                } else {
-                    clusterLow = Math.min(clusterLow, cr.mClusterLow);
-                    clusterHigh = Math.max(clusterHigh, cr.mClusterHigh);
-                }
-                clusterIdValid = true;
-            }
-        }
-        ConfigRequest.Builder builder = new ConfigRequest.Builder();
-        builder.setSupport5gBand(support5gBand).setMasterPreference(masterPreference)
-                .setClusterLow(clusterLow).setClusterHigh(clusterHigh);
-
-        return builder.build();
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("NanStateManager:");
-        pw.println("  mClients: [" + mClients + "]");
-        pw.println("  mPendingResponses: [" + mPendingResponses + "]");
-        pw.println("  mCapabilities: [" + mCapabilities + "]");
-        pw.println("  mNextTransactionId: " + mNextTransactionId);
-        for (int i = 0; i < mClients.size(); ++i) {
-            mClients.valueAt(i).dump(fd, pw, args);
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java
new file mode 100644
index 0000000..802f643
--- /dev/null
+++ b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceCallback.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.p2p;
+
+import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback;
+import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.util.Log;
+
+import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus;
+import com.android.server.wifi.util.NativeUtil;
+
+import libcore.util.HexEncoding;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class used for processing all P2P callbacks.
+ */
+public class SupplicantP2pIfaceCallback extends ISupplicantP2pIfaceCallback.Stub {
+    private static final String TAG = "SupplicantP2pIfaceCallback";
+    private static final boolean DBG = true;
+
+    private final String mInterface;
+    private final WifiP2pMonitor mMonitor;
+
+    public SupplicantP2pIfaceCallback(String iface, WifiP2pMonitor monitor) {
+        mInterface = iface;
+        mMonitor = monitor;
+    }
+
+
+    protected static void logd(String s) {
+        if (DBG) Log.d(TAG, s);
+    }
+
+    /**
+     * Used to indicate that a new network has been added.
+     *
+     * @param networkId Network ID allocated to the corresponding network.
+     */
+    public void onNetworkAdded(int networkId) {
+    }
+
+
+    /**
+     * Used to indicate that a network has been removed.
+     *
+     * @param networkId Network ID allocated to the corresponding network.
+     */
+    public void onNetworkRemoved(int networkId) {
+    }
+
+
+    /**
+     * Used to indicate that a P2P device has been found.
+     *
+     * @param srcAddress MAC address of the device found. This must either
+     *        be the P2P device address or the P2P interface address.
+     * @param p2pDeviceAddress P2P device address.
+     * @param primaryDeviceType Type of device. Refer to section B.1 of Wifi P2P
+     *        Technical specification v1.2.
+     * @param deviceName Name of the device.
+     * @param configMethods Mask of WPS configuration methods supported by the
+     *        device.
+     * @param deviceCapabilities Refer to section 4.1.4 of Wifi P2P Technical
+     *        specification v1.2.
+     * @param groupCapabilities Refer to section 4.1.4 of Wifi P2P Technical
+     *        specification v1.2.
+     * @param wfdDeviceInfo WFD device info as described in section 5.1.2 of WFD
+     *        technical specification v1.0.0.
+     */
+    public void onDeviceFound(byte[] srcAddress, byte[] p2pDeviceAddress, byte[] primaryDeviceType,
+            String deviceName, short configMethods, byte deviceCapabilities, int groupCapabilities,
+            byte[] wfdDeviceInfo) {
+        WifiP2pDevice device = new WifiP2pDevice();
+        device.deviceName = deviceName;
+
+        if (deviceName == null) {
+            Log.e(TAG, "Missing device name.");
+            return;
+        }
+
+        try {
+            device.deviceAddress = NativeUtil.macAddressFromByteArray(p2pDeviceAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode device address.", e);
+            return;
+        }
+
+        try {
+            device.primaryDeviceType = new String(HexEncoding.encode(
+                    primaryDeviceType, 0, primaryDeviceType.length));
+        } catch (Exception e) {
+            Log.e(TAG, "Could not encode device primary type.", e);
+            return;
+        }
+
+        device.deviceCapability = deviceCapabilities;
+        device.groupCapability = groupCapabilities;
+        device.wpsConfigMethodsSupported = configMethods;
+        device.status = WifiP2pDevice.AVAILABLE;
+
+        if (wfdDeviceInfo != null && wfdDeviceInfo.length >= 6) {
+            device.wfdInfo = new WifiP2pWfdInfo(
+                    (wfdDeviceInfo[0] << 8) + wfdDeviceInfo[1],
+                    (wfdDeviceInfo[2] << 8) + wfdDeviceInfo[3],
+                    (wfdDeviceInfo[4] << 8) + wfdDeviceInfo[5]);
+        }
+
+        logd("Device discovered on " + mInterface + ": " + device);
+        mMonitor.broadcastP2pDeviceFound(mInterface, device);
+    }
+
+
+    /**
+     * Used to indicate that a P2P device has been lost.
+     *
+     * @param p2pDeviceAddress P2P device address.
+     */
+    public void onDeviceLost(byte[] p2pDeviceAddress) {
+        WifiP2pDevice device = new WifiP2pDevice();
+
+        try {
+            device.deviceAddress = NativeUtil.macAddressFromByteArray(p2pDeviceAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode device address.", e);
+            return;
+        }
+
+        device.status = WifiP2pDevice.UNAVAILABLE;
+
+        logd("Device lost on " + mInterface + ": " + device);
+        mMonitor.broadcastP2pDeviceLost(mInterface, device);
+    }
+
+
+    /**
+     * Used to indicate the termination of P2P find operation.
+     */
+    public void onFindStopped() {
+        logd("Search stopped on " + mInterface);
+        mMonitor.broadcastP2pFindStopped(mInterface);
+    }
+
+
+    /**
+     * Used to indicate the reception of a P2P Group Owner negotiation request.
+     *
+     * @param srcAddress MAC address of the device that initiated the GO
+     *        negotiation request.
+     * @param passwordId Type of password.
+     */
+    public void onGoNegotiationRequest(byte[] srcAddress, short passwordId) {
+        WifiP2pConfig config = new WifiP2pConfig();
+
+        try {
+            config.deviceAddress = NativeUtil.macAddressFromByteArray(srcAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode device address.", e);
+            return;
+        }
+
+        config.wps = new WpsInfo();
+
+        switch (passwordId) {
+            case WpsDevPasswordId.USER_SPECIFIED:
+                config.wps.setup = WpsInfo.DISPLAY;
+                break;
+
+            case WpsDevPasswordId.PUSHBUTTON:
+                config.wps.setup = WpsInfo.PBC;
+                break;
+
+            case WpsDevPasswordId.REGISTRAR_SPECIFIED:
+                config.wps.setup = WpsInfo.KEYPAD;
+                break;
+
+            default:
+                config.wps.setup = WpsInfo.PBC;
+                break;
+        }
+
+        logd("Group Owner negotiation initiated on " + mInterface + ": " + config);
+        mMonitor.broadcastP2pGoNegotiationRequest(mInterface, config);
+    }
+
+
+    /**
+     * Used to indicate the completion of a P2P Group Owner negotiation request.
+     *
+     * @param status Status of the GO negotiation.
+     */
+    public void onGoNegotiationCompleted(int status) {
+        logd("Group Owner negotiation completed with status: " + status);
+        P2pStatus result = halStatusToP2pStatus(status);
+
+        if (result == P2pStatus.SUCCESS) {
+            mMonitor.broadcastP2pGoNegotiationSuccess(mInterface);
+        } else {
+            mMonitor.broadcastP2pGoNegotiationFailure(mInterface, result);
+        }
+    }
+
+
+    /**
+     * Used to indicate a successful formation of a P2P group.
+     */
+    public void onGroupFormationSuccess() {
+        logd("Group formation successful on " + mInterface);
+        mMonitor.broadcastP2pGroupFormationSuccess(mInterface);
+    }
+
+
+    /**
+     * Used to indicate a failure to form a P2P group.
+     *
+     * @param failureReason Failure reason string for debug purposes.
+     */
+    public void onGroupFormationFailure(String failureReason) {
+        // TODO(ender): failureReason should probably be an int (P2pStatusCode).
+        logd("Group formation failed on " + mInterface + ": " + failureReason);
+        mMonitor.broadcastP2pGroupFormationFailure(mInterface, failureReason);
+    }
+
+
+    /**
+     * Used to indicate the start of a P2P group.
+     *
+     * @param groupIfName Interface name of the group. (For ex: p2p-p2p0-1)
+     * @param isGo Whether this device is owner of the group.
+     * @param ssid SSID of the group.
+     * @param frequency Frequency on which this group is created.
+     * @param psk PSK used to secure the group.
+     * @param passphrase PSK passphrase used to secure the group.
+     * @param goDeviceAddress MAC Address of the owner of this group.
+     * @param isPersistent Whether this group is persisted or not.
+     */
+    public void onGroupStarted(String groupIfName, boolean isGo, ArrayList<Byte> ssid,
+            int frequency, byte[] psk, String passphrase, byte[] goDeviceAddress,
+            boolean isPersistent) {
+        if (groupIfName == null) {
+            Log.e(TAG, "Missing group interface name.");
+            return;
+        }
+
+        logd("Group " + groupIfName + " started on " + mInterface);
+
+        WifiP2pGroup group = new WifiP2pGroup();
+        group.setInterface(groupIfName);
+
+        try {
+            String quotedSsid = NativeUtil.encodeSsid(ssid);
+            group.setNetworkName(NativeUtil.removeEnclosingQuotes(quotedSsid));
+        } catch (Exception e) {
+            Log.e(TAG, "Could not encode SSID.", e);
+            return;
+        }
+
+        group.setIsGroupOwner(isGo);
+        group.setPassphrase(passphrase);
+
+        if (isPersistent) {
+            group.setNetworkId(WifiP2pGroup.PERSISTENT_NET_ID);
+        } else {
+            group.setNetworkId(WifiP2pGroup.TEMPORARY_NET_ID);
+        }
+
+        WifiP2pDevice owner = new WifiP2pDevice();
+
+        try {
+            owner.deviceAddress = NativeUtil.macAddressFromByteArray(goDeviceAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode Group Owner address.", e);
+            return;
+        }
+
+        group.setOwner(owner);
+        mMonitor.broadcastP2pGroupStarted(mInterface, group);
+    }
+
+
+    /**
+     * Used to indicate the removal of a P2P group.
+     *
+     * @param groupIfName Interface name of the group. (For ex: p2p-p2p0-1)
+     * @param isGo Whether this device is owner of the group.
+     */
+    public void onGroupRemoved(String groupIfName, boolean isGo) {
+        if (groupIfName == null) {
+            Log.e(TAG, "Missing group name.");
+            return;
+        }
+
+        logd("Group " + groupIfName + " removed from " + mInterface);
+        WifiP2pGroup group = new WifiP2pGroup();
+        group.setInterface(groupIfName);
+        group.setIsGroupOwner(isGo);
+        mMonitor.broadcastP2pGroupRemoved(mInterface, group);
+    }
+
+
+    /**
+     * Used to indicate the reception of a P2P invitation.
+     *
+     * @param srcAddress MAC address of the device that sent the invitation.
+     * @param goDeviceAddress MAC Address of the owner of this group.
+     * @param bssid Bssid of the group.
+     * @param persistentNetworkId Persistent network Id of the group.
+     * @param operatingFrequency Frequency on which the invitation was received.
+     */
+    public void onInvitationReceived(byte[] srcAddress, byte[] goDeviceAddress,
+            byte[] bssid, int persistentNetworkId, int operatingFrequency) {
+        WifiP2pGroup group = new WifiP2pGroup();
+        group.setNetworkId(persistentNetworkId);
+
+        WifiP2pDevice client = new WifiP2pDevice();
+
+        try {
+            client.deviceAddress = NativeUtil.macAddressFromByteArray(srcAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode MAC address.", e);
+            return;
+        }
+
+        group.addClient(client);
+
+        WifiP2pDevice owner = new WifiP2pDevice();
+
+        try {
+            owner.deviceAddress = NativeUtil.macAddressFromByteArray(goDeviceAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode Group Owner MAC address.", e);
+            return;
+        }
+
+        group.setOwner(owner);
+
+        logd("Invitation received on " + mInterface + ": " + group);
+        mMonitor.broadcastP2pInvitationReceived(mInterface, group);
+    }
+
+
+    /**
+     * Used to indicate the result of the P2P invitation request.
+     *
+     * @param bssid Bssid of the group.
+     * @param status Status of the invitation.
+     */
+    public void onInvitationResult(byte[] bssid, int status) {
+        logd("Invitation completed with status: " + status);
+        mMonitor.broadcastP2pInvitationResult(mInterface, halStatusToP2pStatus(status));
+    }
+
+
+    /**
+     * Used to indicate the completion of a P2P provision discovery request.
+     *
+     * @param p2pDeviceAddress P2P device address.
+     * @param isRequest Whether we received or sent the provision discovery.
+     * @param status Status of the provision discovery (SupplicantStatusCode).
+     * @param configMethods Mask of WPS configuration methods supported.
+     *                      Only one configMethod bit should be set per call.
+     * @param generatedPin 8 digit pin generated.
+     */
+    public void onProvisionDiscoveryCompleted(byte[] p2pDeviceAddress, boolean isRequest,
+            byte status, short configMethods, String generatedPin) {
+        if (status != ISupplicantP2pIfaceCallback.P2pProvDiscStatusCode.SUCCESS) {
+            Log.e(TAG, "Provision discovery failed: " + status);
+            mMonitor.broadcastP2pProvisionDiscoveryFailure(mInterface);
+            return;
+        }
+
+        logd("Provision discovery " + (isRequest ? "request" : "response")
+                + " for WPS Config method: " + configMethods);
+
+        WifiP2pProvDiscEvent event = new WifiP2pProvDiscEvent();
+        event.device = new WifiP2pDevice();
+
+        try {
+            event.device.deviceAddress = NativeUtil.macAddressFromByteArray(p2pDeviceAddress);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode MAC address.", e);
+            return;
+        }
+
+        if ((configMethods & WpsConfigMethods.PUSHBUTTON) != 0) {
+            if (isRequest) {
+                event.event = WifiP2pProvDiscEvent.PBC_REQ;
+                mMonitor.broadcastP2pProvisionDiscoveryPbcRequest(mInterface, event);
+            } else {
+                event.event = WifiP2pProvDiscEvent.PBC_RSP;
+                mMonitor.broadcastP2pProvisionDiscoveryPbcResponse(mInterface, event);
+            }
+        } else if (!isRequest && (configMethods & WpsConfigMethods.KEYPAD) != 0) {
+            event.event = WifiP2pProvDiscEvent.SHOW_PIN;
+            event.pin = generatedPin;
+            mMonitor.broadcastP2pProvisionDiscoveryShowPin(mInterface, event);
+        } else if (!isRequest && (configMethods & WpsConfigMethods.DISPLAY) != 0) {
+            event.event = WifiP2pProvDiscEvent.ENTER_PIN;
+            mMonitor.broadcastP2pProvisionDiscoveryEnterPin(mInterface, event);
+        } else if (isRequest && (configMethods & WpsConfigMethods.DISPLAY) != 0) {
+            event.event = WifiP2pProvDiscEvent.SHOW_PIN;
+            event.pin = generatedPin;
+            mMonitor.broadcastP2pProvisionDiscoveryShowPin(mInterface, event);
+        } else if (isRequest && (configMethods & WpsConfigMethods.KEYPAD) != 0) {
+            event.event = WifiP2pProvDiscEvent.ENTER_PIN;
+            mMonitor.broadcastP2pProvisionDiscoveryEnterPin(mInterface, event);
+        } else {
+            Log.e(TAG, "Unsupported config methods: " + configMethods);
+        }
+    }
+
+
+    /**
+     * Used to indicate the reception of a P2P service discovery response.
+     *
+     * @param srcAddress MAC address of the device that sent the service discovery.
+     * @param updateIndicator Service update indicator. Refer to section 3.1.3 of
+     *        Wifi P2P Technical specification v1.2.
+     * @param tlvs Refer to section 3.1.3.1 of Wifi P2P Technical specification v1.2.
+     */
+    public void onServiceDiscoveryResponse(byte[] srcAddress, short updateIndicator,
+            ArrayList<Byte> tlvs) {
+        List<WifiP2pServiceResponse> response = null;
+
+        logd("Service discovery response received on " + mInterface);
+        try {
+            String srcAddressStr = NativeUtil.macAddressFromByteArray(srcAddress);
+            // updateIndicator is not used
+            response = WifiP2pServiceResponse.newInstance(srcAddressStr,
+                    NativeUtil.byteArrayFromArrayList(tlvs));
+        } catch (Exception e) {
+            Log.e(TAG, "Could not process service discovery response.", e);
+            return;
+        }
+        mMonitor.broadcastP2pServiceDiscoveryResponse(mInterface, response);
+    }
+
+    private WifiP2pDevice createStaEventDevice(byte[] srcAddress, byte[] p2pDeviceAddress) {
+        WifiP2pDevice device = new WifiP2pDevice();
+        byte[] deviceAddressBytes;
+        // Legacy STAs may not supply a p2pDeviceAddress (signaled by a zero'd p2pDeviceAddress)
+        // In this case, use srcAddress instead
+        if (!Arrays.equals(NativeUtil.ANY_MAC_BYTES, p2pDeviceAddress)) {
+            deviceAddressBytes = p2pDeviceAddress;
+        } else {
+            deviceAddressBytes = srcAddress;
+        }
+        try {
+            device.deviceAddress = NativeUtil.macAddressFromByteArray(deviceAddressBytes);
+        } catch (Exception e) {
+            Log.e(TAG, "Could not decode MAC address", e);
+            return null;
+        }
+        return device;
+    }
+
+    /**
+     * Used to indicate when a STA device is connected to this device.
+     *
+     * @param srcAddress MAC address of the device that was authorized.
+     * @param p2pDeviceAddress P2P device address.
+     */
+    public void onStaAuthorized(byte[] srcAddress, byte[] p2pDeviceAddress) {
+        logd("STA authorized on " + mInterface);
+        WifiP2pDevice device = createStaEventDevice(srcAddress, p2pDeviceAddress);
+        if (device == null) {
+            return;
+        }
+        mMonitor.broadcastP2pApStaConnected(mInterface, device);
+    }
+
+
+    /**
+     * Used to indicate when a STA device is disconnected from this device.
+     *
+     * @param srcAddress MAC address of the device that was deauthorized.
+     * @param p2pDeviceAddress P2P device address.
+     */
+    public void onStaDeauthorized(byte[] srcAddress, byte[] p2pDeviceAddress) {
+        logd("STA deauthorized on " + mInterface);
+        WifiP2pDevice device = createStaEventDevice(srcAddress, p2pDeviceAddress);
+        if (device == null) {
+            return;
+        }
+        mMonitor.broadcastP2pApStaDisconnected(mInterface, device);
+    }
+
+
+    private static P2pStatus halStatusToP2pStatus(int status) {
+        P2pStatus result = P2pStatus.UNKNOWN;
+
+        switch (status) {
+            case P2pStatusCode.SUCCESS:
+            case P2pStatusCode.SUCCESS_DEFERRED:
+                result = P2pStatus.SUCCESS;
+                break;
+
+            case P2pStatusCode.FAIL_INFO_CURRENTLY_UNAVAILABLE:
+                result = P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE;
+                break;
+
+            case P2pStatusCode.FAIL_INCOMPATIBLE_PARAMS:
+                result = P2pStatus.INCOMPATIBLE_PARAMETERS;
+                break;
+
+            case P2pStatusCode.FAIL_LIMIT_REACHED:
+                result = P2pStatus.LIMIT_REACHED;
+                break;
+
+            case P2pStatusCode.FAIL_INVALID_PARAMS:
+                result = P2pStatus.INVALID_PARAMETER;
+                break;
+
+            case P2pStatusCode.FAIL_UNABLE_TO_ACCOMMODATE:
+                result = P2pStatus.UNABLE_TO_ACCOMMODATE_REQUEST;
+                break;
+
+            case P2pStatusCode.FAIL_PREV_PROTOCOL_ERROR:
+                result = P2pStatus.PREVIOUS_PROTOCOL_ERROR;
+                break;
+
+            case P2pStatusCode.FAIL_NO_COMMON_CHANNELS:
+                result = P2pStatus.NO_COMMON_CHANNEL;
+                break;
+
+            case P2pStatusCode.FAIL_UNKNOWN_GROUP:
+                result = P2pStatus.UNKNOWN_P2P_GROUP;
+                break;
+
+            case P2pStatusCode.FAIL_BOTH_GO_INTENT_15:
+                result = P2pStatus.BOTH_GO_INTENT_15;
+                break;
+
+            case P2pStatusCode.FAIL_INCOMPATIBLE_PROV_METHOD:
+                result = P2pStatus.INCOMPATIBLE_PROVISIONING_METHOD;
+                break;
+
+            case P2pStatusCode.FAIL_REJECTED_BY_USER:
+                result = P2pStatus.REJECTED_BY_USER;
+                break;
+        }
+        return result;
+    }
+}
+
diff --git a/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
new file mode 100644
index 0000000..3e26828
--- /dev/null
+++ b/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java
@@ -0,0 +1,2236 @@
+/*
+ * 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.p2p;
+
+import android.hardware.wifi.supplicant.V1_0.ISupplicant;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantNetwork;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pNetwork;
+import android.hardware.wifi.supplicant.V1_0.IfaceType;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
+import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pGroupList;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.os.HwRemoteBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.wifi.util.NativeUtil;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Native calls sending requests to the P2P Hals, and callbacks for receiving P2P events
+ *
+ * {@hide}
+ */
+public class SupplicantP2pIfaceHal {
+    private static final boolean DBG = true;
+    private static final String TAG = "SupplicantP2pIfaceHal";
+    private static final int RESULT_NOT_VALID = -1;
+    private static final int DEFAULT_GROUP_OWNER_INTENT = 6;
+    private static final int DEFAULT_OPERATING_CLASS = 81;
+    /**
+     * Regex pattern for extracting the wps device type bytes.
+     * Matches a strings like the following: "<categ>-<OUI>-<subcateg>";
+     */
+    private static final Pattern WPS_DEVICE_TYPE_PATTERN =
+            Pattern.compile("^(\\d{1,2})-([0-9a-fA-F]{8})-(\\d{1,2})$");
+
+    private Object mLock = new Object();
+
+    // Supplicant HAL HIDL interface objects
+    private IServiceManager mIServiceManager = null;
+    private ISupplicant mISupplicant = null;
+    private ISupplicantIface mHidlSupplicantIface = null;
+    private ISupplicantP2pIface mISupplicantP2pIface = null;
+    private final IServiceNotification mServiceNotificationCallback =
+            new IServiceNotification.Stub() {
+        public void onRegistration(String fqName, String name, boolean preexisting) {
+            synchronized (mLock) {
+                if (DBG) {
+                    Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName
+                            + ", " + name + " preexisting=" + preexisting);
+                }
+                if (!initSupplicantService() || !initSupplicantP2pIface()) {
+                    Log.e(TAG, "initalizing ISupplicantIfaces failed.");
+                    supplicantServiceDiedHandler();
+                } else {
+                    Log.i(TAG, "Completed initialization of ISupplicant interfaces.");
+                }
+            }
+        }
+    };
+    private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
+            cookie -> {
+                Log.w(TAG, "IServiceManager died: cookie=" + cookie);
+                synchronized (mLock) {
+                    supplicantServiceDiedHandler();
+                    mIServiceManager = null; // Will need to register a new ServiceNotification
+                }
+            };
+    private final HwRemoteBinder.DeathRecipient mSupplicantDeathRecipient =
+            cookie -> {
+                Log.w(TAG, "ISupplicant/ISupplicantStaIface died: cookie=" + cookie);
+                synchronized (mLock) {
+                    supplicantServiceDiedHandler();
+                }
+            };
+
+    private final WifiP2pMonitor mMonitor;
+    private SupplicantP2pIfaceCallback mCallback = null;
+
+    public SupplicantP2pIfaceHal(WifiP2pMonitor monitor) {
+        mMonitor = monitor;
+    }
+
+    private boolean linkToServiceManagerDeath() {
+        if (mIServiceManager == null) return false;
+        try {
+            if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
+                Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
+                supplicantServiceDiedHandler();
+                mIServiceManager = null; // Will need to register a new ServiceNotification
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "IServiceManager.linkToDeath exception", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Registers a service notification for the ISupplicant service, which triggers intialization of
+     * the ISupplicantP2pIface
+     * @return true if the service notification was successfully registered
+     */
+    public boolean initialize() {
+        if (DBG) Log.i(TAG, "Registering ISupplicant service ready callback.");
+        synchronized (mLock) {
+            if (mIServiceManager != null) {
+                Log.i(TAG, "Supplicant HAL already initialized.");
+                // Already have an IServiceManager and serviceNotification registered, don't
+                // don't register another.
+                return true;
+            }
+            mISupplicant = null;
+            mISupplicantP2pIface = null;
+            try {
+                mIServiceManager = getServiceManagerMockable();
+                if (mIServiceManager == null) {
+                    Log.e(TAG, "Failed to get HIDL Service Manager");
+                    return false;
+                }
+                if (!linkToServiceManagerDeath()) {
+                    return false;
+                }
+                /* TODO(b/33639391) : Use the new ISupplicant.registerForNotifications() once it
+                   exists */
+                if (!mIServiceManager.registerForNotifications(
+                        ISupplicant.kInterfaceName, "", mServiceNotificationCallback)) {
+                    Log.e(TAG, "Failed to register for notifications to "
+                            + ISupplicant.kInterfaceName);
+                    mIServiceManager = null; // Will need to register a new ServiceNotification
+                    return false;
+                }
+
+                // Successful completion by the end of the 'try' block. This will prevent reporting
+                // proper initialization after exception is caught.
+                return true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Exception while trying to register a listener for ISupplicant service: "
+                        + e);
+                supplicantServiceDiedHandler();
+            }
+            return false;
+        }
+    }
+
+    private boolean linkToSupplicantDeath() {
+        if (mISupplicant == null) return false;
+        try {
+            if (!mISupplicant.linkToDeath(mSupplicantDeathRecipient, 0)) {
+                Log.wtf(TAG, "Error on linkToDeath on ISupplicant");
+                supplicantServiceDiedHandler();
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "ISupplicant.linkToDeath exception", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean initSupplicantService() {
+        synchronized (mLock) {
+            try {
+                mISupplicant = getSupplicantMockable();
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicant.getService exception: " + e);
+                return false;
+            }
+            if (mISupplicant == null) {
+                Log.e(TAG, "Got null ISupplicant service. Stopping supplicant HIDL startup");
+                return false;
+            }
+            if (!linkToSupplicantDeath()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean linkToSupplicantP2pIfaceDeath() {
+        if (mISupplicantP2pIface == null) return false;
+        try {
+            if (!mISupplicantP2pIface.linkToDeath(mSupplicantDeathRecipient, 0)) {
+                Log.wtf(TAG, "Error on linkToDeath on ISupplicantP2pIface");
+                supplicantServiceDiedHandler();
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "ISupplicantP2pIface.linkToDeath exception", e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean initSupplicantP2pIface() {
+        synchronized (mLock) {
+            /** List all supplicant Ifaces */
+            final ArrayList<ISupplicant.IfaceInfo> supplicantIfaces = new ArrayList();
+            try {
+                mISupplicant.listInterfaces((SupplicantStatus status,
+                        ArrayList<ISupplicant.IfaceInfo> ifaces) -> {
+                    if (status.code != SupplicantStatusCode.SUCCESS) {
+                        Log.e(TAG, "Getting Supplicant Interfaces failed: " + status.code);
+                        return;
+                    }
+                    supplicantIfaces.addAll(ifaces);
+                });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicant.listInterfaces exception: " + e);
+                return false;
+            }
+            if (supplicantIfaces.size() == 0) {
+                Log.e(TAG, "Got zero HIDL supplicant ifaces. Stopping supplicant HIDL startup.");
+                return false;
+            }
+            SupplicantResult<ISupplicantIface> supplicantIface =
+                    new SupplicantResult("getInterface()");
+            for (ISupplicant.IfaceInfo ifaceInfo : supplicantIfaces) {
+                if (ifaceInfo.type == IfaceType.P2P) {
+                    try {
+                        mISupplicant.getInterface(ifaceInfo,
+                                (SupplicantStatus status, ISupplicantIface iface) -> {
+                                if (status.code != SupplicantStatusCode.SUCCESS) {
+                                    Log.e(TAG, "Failed to get ISupplicantIface " + status.code);
+                                    return;
+                                }
+                                supplicantIface.setResult(status, iface);
+                            });
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "ISupplicant.getInterface exception: " + e);
+                        return false;
+                    }
+                    break;
+                }
+            }
+
+            if (supplicantIface.getResult() == null) {
+                Log.e(TAG, "initSupplicantP2pIface got null iface");
+                return false;
+            }
+            mISupplicantP2pIface = getP2pIfaceMockable(supplicantIface.getResult());
+            if (!linkToSupplicantP2pIfaceDeath()) {
+                return false;
+            }
+        }
+
+        if (mISupplicantP2pIface != null && mMonitor != null) {
+            // TODO(ender): Get rid of hard-coded interface name, which is
+            // assumed to be the group interface name in several other classes
+            // ("p2p0" should probably become getName()).
+            mCallback = new SupplicantP2pIfaceCallback("p2p0", mMonitor);
+            if (!registerCallback(mCallback)) {
+                Log.e(TAG, "Callback registration failed. Initialization incomplete.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void supplicantServiceDiedHandler() {
+        synchronized (mLock) {
+            mISupplicant = null;
+            mISupplicantP2pIface = null;
+        }
+    }
+
+
+    /**
+     * Signals whether Initialization completed successfully.
+     */
+    public boolean isInitializationStarted() {
+        return mIServiceManager != null;
+    }
+
+    /**
+     * Signals whether Initialization completed successfully. Only necessary for testing, is not
+     * needed to guard calls etc.
+     */
+    public boolean isInitializationComplete() {
+        return mISupplicantP2pIface != null;
+    }
+
+    /**
+     * Wrapper functions to access static HAL methods, created to be mockable in unit tests
+     */
+    protected IServiceManager getServiceManagerMockable() throws RemoteException {
+        return IServiceManager.getService();
+    }
+
+    protected ISupplicant getSupplicantMockable() throws RemoteException {
+        return ISupplicant.getService();
+    }
+
+    protected ISupplicantP2pIface getP2pIfaceMockable(ISupplicantIface iface) {
+        return ISupplicantP2pIface.asInterface(iface.asBinder());
+    }
+
+    protected ISupplicantP2pNetwork getP2pNetworkMockable(ISupplicantNetwork network) {
+        return ISupplicantP2pNetwork.asInterface(network.asBinder());
+    }
+
+    protected static void logd(String s) {
+        if (DBG) Log.d(TAG, s);
+    }
+
+    protected static void logCompletion(String operation, SupplicantStatus status) {
+        if (status == null) {
+            Log.w(TAG, operation + " failed: no status code returned.");
+        } else if (status.code == SupplicantStatusCode.SUCCESS) {
+            logd(operation + " completed successfully.");
+        } else {
+            Log.w(TAG, operation + " failed: " + status.code + " (" + status.debugMessage + ")");
+        }
+    }
+
+
+    /**
+     * Returns false if SupplicantP2pIface is null, and logs failure to call methodStr
+     */
+    private boolean checkSupplicantP2pIfaceAndLogFailure(String method) {
+        if (mISupplicantP2pIface == null) {
+            Log.e(TAG, "Can't call " + method + ": ISupplicantP2pIface is null");
+            return false;
+        }
+        return true;
+    }
+
+    private int wpsInfoToConfigMethod(int info) {
+        switch (info) {
+            case WpsInfo.PBC:
+                return ISupplicantP2pIface.WpsProvisionMethod.PBC;
+
+            case WpsInfo.DISPLAY:
+                return ISupplicantP2pIface.WpsProvisionMethod.DISPLAY;
+
+            case WpsInfo.KEYPAD:
+            case WpsInfo.LABEL:
+                return ISupplicantP2pIface.WpsProvisionMethod.KEYPAD;
+
+            default:
+                Log.e(TAG, "Unsupported WPS provision method: " + info);
+                return RESULT_NOT_VALID;
+        }
+    }
+
+    /**
+     * Retrieves the name of the network interface.
+     *
+     * @return name Name of the network interface, e.g., wlan0
+     */
+    public String getName() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getName")) return null;
+            SupplicantResult<String> result = new SupplicantResult("getName()");
+
+            try {
+                mISupplicantP2pIface.getName(
+                        (SupplicantStatus status, String name) -> {
+                            result.setResult(status, name);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.getResult();
+        }
+    }
+
+
+    /**
+     * Register for callbacks from this interface.
+     *
+     * These callbacks are invoked for events that are specific to this interface.
+     * Registration of multiple callback objects is supported. These objects must
+     * be automatically deleted when the corresponding client process is dead or
+     * if this interface is removed.
+     *
+     * @param receiver An instance of the |ISupplicantP2pIfaceCallback| HIDL
+     *        interface object.
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean registerCallback(ISupplicantP2pIfaceCallback receiver) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("registerCallback")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("registerCallback()");
+            try {
+                result.setResult(mISupplicantP2pIface.registerCallback(receiver));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Initiate a P2P service discovery with a (optional) timeout.
+     *
+     * @param timeout Max time to be spent is peforming discovery.
+     *        Set to 0 to indefinely continue discovery untill and explicit
+     *        |stopFind| is sent.
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean find(int timeout) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("find")) return false;
+
+            if (timeout < 0) {
+                Log.e(TAG, "Invalid timeout value: " + timeout);
+                return false;
+            }
+            SupplicantResult<Void> result = new SupplicantResult("find(" + timeout + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.find(timeout));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Stop an ongoing P2P service discovery.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean stopFind() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("stopFind")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("stopFind()");
+            try {
+                result.setResult(mISupplicantP2pIface.stopFind());
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Flush P2P peer table and state.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean flush() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("flush")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("flush()");
+            try {
+                result.setResult(mISupplicantP2pIface.flush());
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * This command can be used to flush all services from the
+     * device.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean serviceFlush() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("serviceFlush")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("serviceFlush()");
+            try {
+                result.setResult(mISupplicantP2pIface.flushServices());
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Turn on/off power save mode for the interface.
+     *
+     * @param groupIfName Group interface name to use.
+     * @param enable Indicate if power save is to be turned on/off.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean setPowerSave(String groupIfName, boolean enable) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setPowerSave")) return false;
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setPowerSave(" + groupIfName + ", " + enable + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setPowerSave(groupIfName, enable));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Set the Maximum idle time in seconds for P2P groups.
+     * This value controls how long a P2P group is maintained after there
+     * is no other members in the group. As a group owner, this means no
+     * associated stations in the group. As a P2P client, this means no
+     * group owner seen in scan results.
+     *
+     * @param groupIfName Group interface name to use.
+     * @param timeoutInSec Timeout value in seconds.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean setGroupIdle(String groupIfName, int timeoutInSec) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setGroupIdle")) return false;
+            // Basic checking here. Leave actual parameter validation to supplicant.
+            if (timeoutInSec < 0) {
+                Log.e(TAG, "Invalid group timeout value " + timeoutInSec);
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setGroupIdle(" + groupIfName + ", " + timeoutInSec + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setGroupIdle(groupIfName, timeoutInSec));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Set the postfix to be used for P2P SSID's.
+     *
+     * @param postfix String to be appended to SSID.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean setSsidPostfix(String postfix) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setSsidPostfix")) return false;
+            // Basic checking here. Leave actual parameter validation to supplicant.
+            if (postfix == null) {
+                Log.e(TAG, "Invalid SSID postfix value (null).");
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult("setSsidPostfix(" + postfix + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setSsidPostfix(
+                        NativeUtil.decodeSsid("\"" + postfix + "\"")));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Could not decode SSID.", e);
+                return false;
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Start P2P group formation with a discovered P2P peer. This includes
+     * optional group owner negotiation, group interface setup, provisioning,
+     * and establishing data connection.
+     *
+     * @param config Configuration to use to connect to remote device.
+     * @param joinExistingGroup Indicates that this is a command to join an
+     *        existing group as a client. It skips the group owner negotiation
+     *        part. This must send a Provision Discovery Request message to the
+     *        target group owner before associating for WPS provisioning.
+     *
+     * @return String containing generated pin, if selected provision method
+     *        uses PIN.
+     */
+    public String connect(WifiP2pConfig config, boolean joinExistingGroup) {
+        if (config == null) return null;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setSsidPostfix")) return null;
+
+            if (config == null) {
+                Log.e(TAG, "Could not connect: null config.");
+                return null;
+            }
+
+            if (config.deviceAddress == null) {
+                Log.e(TAG, "Could not parse null mac address.");
+                return null;
+            }
+
+            if (config.wps.setup == WpsInfo.PBC && !TextUtils.isEmpty(config.wps.pin)) {
+                Log.e(TAG, "Expected empty pin for PBC.");
+                return null;
+            }
+
+            byte[] peerAddress = null;
+            try {
+                peerAddress = NativeUtil.macAddressToByteArray(config.deviceAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse peer mac address.", e);
+                return null;
+            }
+
+            int provisionMethod = wpsInfoToConfigMethod(config.wps.setup);
+            if (provisionMethod == RESULT_NOT_VALID) {
+                Log.e(TAG, "Invalid WPS config method: " + config.wps.setup);
+                return null;
+            }
+            // NOTE: preSelectedPin cannot be null, otherwise hal would crash.
+            String preSelectedPin = TextUtils.isEmpty(config.wps.pin) ? "" : config.wps.pin;
+            boolean persistent = (config.netId == WifiP2pGroup.PERSISTENT_NET_ID);
+
+            int goIntent = 0;
+            if (!joinExistingGroup) {
+                int groupOwnerIntent = config.groupOwnerIntent;
+                if (groupOwnerIntent < 0 || groupOwnerIntent > 15) {
+                    groupOwnerIntent = DEFAULT_GROUP_OWNER_INTENT;
+                }
+                goIntent = groupOwnerIntent;
+            }
+
+            SupplicantResult<String> result = new SupplicantResult(
+                    "connect(" + config.deviceAddress + ")");
+            try {
+                mISupplicantP2pIface.connect(
+                        peerAddress, provisionMethod, preSelectedPin, joinExistingGroup,
+                        persistent, goIntent,
+                        (SupplicantStatus status, String generatedPin) -> {
+                            result.setResult(status, generatedPin);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.getResult();
+        }
+    }
+
+    /**
+     * Cancel an ongoing P2P group formation and joining-a-group related
+     * operation. This operation unauthorizes the specific peer device (if any
+     * had been authorized to start group formation), stops P2P find (if in
+     * progress), stops pending operations for join-a-group, and removes the
+     * P2P group interface (if one was used) that is in the WPS provisioning
+     * step. If the WPS provisioning step has been completed, the group is not
+     * terminated.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean cancelConnect() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("cancelConnect")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("cancelConnect()");
+            try {
+                result.setResult(mISupplicantP2pIface.cancelConnect());
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Send P2P provision discovery request to the specified peer. The
+     * parameters for this command are the P2P device address of the peer and the
+     * desired configuration method.
+     *
+     * @param config Config class describing peer setup.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean provisionDiscovery(WifiP2pConfig config) {
+        if (config == null) return false;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("provisionDiscovery")) return false;
+
+            int targetMethod = wpsInfoToConfigMethod(config.wps.setup);
+            if (targetMethod == RESULT_NOT_VALID) {
+                Log.e(TAG, "Unrecognized WPS configuration method: " + config.wps.setup);
+                return false;
+            }
+            if (targetMethod == ISupplicantP2pIface.WpsProvisionMethod.DISPLAY) {
+                // We are doing display, so provision discovery is keypad.
+                targetMethod = ISupplicantP2pIface.WpsProvisionMethod.KEYPAD;
+            } else if (targetMethod == ISupplicantP2pIface.WpsProvisionMethod.KEYPAD) {
+                // We are doing keypad, so provision discovery is display.
+                targetMethod = ISupplicantP2pIface.WpsProvisionMethod.DISPLAY;
+            }
+
+            if (config.deviceAddress == null) {
+                Log.e(TAG, "Cannot parse null mac address.");
+                return false;
+            }
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(config.deviceAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse peer mac address.", e);
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "provisionDiscovery(" + config.deviceAddress + ", " + config.wps.setup + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.provisionDiscovery(macAddress, targetMethod));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Invite a device to a persistent group.
+     * If the peer device is the group owner of the persistent group, the peer
+     * parameter is not needed. Otherwise it is used to specify which
+     * device to invite. |goDeviceAddress| parameter may be used to override
+     * the group owner device address for Invitation Request should it not be
+     * known for some reason (this should not be needed in most cases).
+     *
+     * @param group Group object to use.
+     * @param peerAddress MAC address of the device to invite.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean invite(WifiP2pGroup group, String peerAddress) {
+        if (TextUtils.isEmpty(peerAddress)) return false;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("invite")) return false;
+            if (group == null) {
+                Log.e(TAG, "Cannot invite to null group.");
+                return false;
+            }
+
+            if (group.getOwner() == null) {
+                Log.e(TAG, "Cannot invite to group with null owner.");
+                return false;
+            }
+
+            if (group.getOwner().deviceAddress == null) {
+                Log.e(TAG, "Group owner has no mac address.");
+                return false;
+            }
+
+            byte[] ownerMacAddress = null;
+            try {
+                ownerMacAddress = NativeUtil.macAddressToByteArray(group.getOwner().deviceAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Group owner mac address parse error.", e);
+                return false;
+            }
+
+            if (peerAddress == null) {
+                Log.e(TAG, "Cannot parse peer mac address.");
+                return false;
+            }
+
+            byte[] peerMacAddress;
+            try {
+                peerMacAddress = NativeUtil.macAddressToByteArray(peerAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Peer mac address parse error.", e);
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "invite(" + group.getInterface() + ", " + group.getOwner().deviceAddress
+                            + ", " + peerAddress + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.invite(
+                        group.getInterface(), ownerMacAddress, peerMacAddress));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Reject connection attempt from a peer (specified with a device
+     * address). This is a mechanism to reject a pending group owner negotiation
+     * with a peer and request to automatically block any further connection or
+     * discovery of the peer.
+     *
+     * @param peerAddress MAC address of the device to reject.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean reject(String peerAddress) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("reject")) return false;
+
+            if (peerAddress == null) {
+                Log.e(TAG, "Cannot parse rejected peer's mac address.");
+                return false;
+            }
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse peer mac address.", e);
+                return false;
+            }
+
+            SupplicantResult<Void> result =
+                    new SupplicantResult("reject(" + peerAddress + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.reject(macAddress));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Gets the MAC address of the device.
+     *
+     * @return MAC address of the device.
+     */
+    public String getDeviceAddress() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getDeviceAddress")) return null;
+            SupplicantResult<String> result = new SupplicantResult("getDeviceAddress()");
+            try {
+                mISupplicantP2pIface.getDeviceAddress((SupplicantStatus status, byte[] address) -> {
+                    String parsedAddress = null;
+                    try {
+                        parsedAddress = NativeUtil.macAddressFromByteArray(address);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Could not process reported address.", e);
+                    }
+                    result.setResult(status, parsedAddress);
+                });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+                return null;
+            }
+
+            return result.getResult();
+        }
+    }
+
+
+    /**
+     * Gets the operational SSID of the device.
+     *
+     * @param address MAC address of the peer.
+     *
+     * @return SSID of the device.
+     */
+    public String getSsid(String address) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getSsid")) return null;
+
+            if (address == null) {
+                Log.e(TAG, "Cannot parse peer mac address.");
+                return null;
+            }
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(address);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse mac address.", e);
+                return null;
+            }
+
+            SupplicantResult<String> result =
+                    new SupplicantResult("getSsid(" + address + ")");
+            try {
+                mISupplicantP2pIface.getSsid(
+                        macAddress, (SupplicantStatus status, ArrayList<Byte> ssid) -> {
+                            String ssidString = null;
+                            if (ssid != null) {
+                                try {
+                                    ssidString = NativeUtil.encodeSsid(ssid);
+                                } catch (Exception e) {
+                                    Log.e(TAG, "Could not encode SSID.", e);
+                                }
+                            }
+                            result.setResult(status, ssidString);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+                return null;
+            }
+
+            return result.getResult();
+        }
+    }
+
+
+    /**
+     * Reinvoke a device from a persistent group.
+     *
+     * @param networkId Used to specify the persistent group.
+     * @param peerAddress MAC address of the device to reinvoke.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean reinvoke(int networkId, String peerAddress) {
+        if (TextUtils.isEmpty(peerAddress) || networkId < 0) return false;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("reinvoke")) return false;
+            if (peerAddress == null) {
+                Log.e(TAG, "Cannot parse peer mac address.");
+                return false;
+            }
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse mac address.", e);
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "reinvoke(" + networkId + ", " + peerAddress + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.reinvoke(networkId, macAddress));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Set up a P2P group owner manually (i.e., without group owner
+     * negotiation with a specific peer). This is also known as autonomous
+     * group owner.
+     *
+     * @param networkId Used to specify the restart of a persistent group.
+     * @param isPersistent Used to request a persistent group to be formed.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean groupAdd(int networkId, boolean isPersistent) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("groupAdd")) return false;
+            SupplicantResult<Void> result =
+                    new SupplicantResult("groupAdd(" + networkId + ", " + isPersistent + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.addGroup(isPersistent, networkId));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Set up a P2P group owner manually.
+     * This is a helper method that invokes groupAdd(networkId, isPersistent) internally.
+     *
+     * @param isPersistent Used to request a persistent group to be formed.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean groupAdd(boolean isPersistent) {
+        // Supplicant expects networkId to be -1 if not supplied.
+        return groupAdd(-1, isPersistent);
+    }
+
+
+    /**
+     * Terminate a P2P group. If a new virtual network interface was used for
+     * the group, it must also be removed. The network interface name of the
+     * group interface is used as a parameter for this command.
+     *
+     * @param groupName Group interface name to use.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean groupRemove(String groupName) {
+        if (TextUtils.isEmpty(groupName)) return false;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("groupRemove")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("groupRemove(" + groupName + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.removeGroup(groupName));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Gets the capability of the group which the device is a
+     * member of.
+     *
+     * @param peerAddress MAC address of the peer.
+     *
+     * @return combination of |GroupCapabilityMask| values.
+     */
+    public int getGroupCapability(String peerAddress) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getGroupCapability")) {
+                return RESULT_NOT_VALID;
+            }
+
+            if (peerAddress == null) {
+                Log.e(TAG, "Cannot parse peer mac address.");
+                return RESULT_NOT_VALID;
+            }
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse group address.", e);
+                return RESULT_NOT_VALID;
+            }
+
+            SupplicantResult<Integer> capability = new SupplicantResult(
+                    "getGroupCapability(" + peerAddress + ")");
+            try {
+                mISupplicantP2pIface.getGroupCapability(
+                        macAddress, (SupplicantStatus status, int cap) -> {
+                            capability.setResult(status, cap);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            if (!capability.isSuccess()) {
+                return RESULT_NOT_VALID;
+            }
+
+            return capability.getResult();
+        }
+    }
+
+
+    /**
+     * Configure Extended Listen Timing.
+     *
+     * If enabled, listen state must be entered every |intervalInMillis| for at
+     * least |periodInMillis|. Both values have acceptable range of 1-65535
+     * (with interval obviously having to be larger than or equal to duration).
+     * If the P2P module is not idle at the time the Extended Listen Timing
+     * timeout occurs, the Listen State operation must be skipped.
+     *
+     * @param enable Enables or disables listening.
+     * @param periodInMillis Period in milliseconds.
+     * @param intervalInMillis Interval in milliseconds.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean configureExtListen(boolean enable, int periodInMillis, int intervalInMillis) {
+        if (enable && intervalInMillis < periodInMillis) {
+            return false;
+        }
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("configureExtListen")) return false;
+
+            // If listening is disabled, wpa supplicant expects zeroes.
+            if (!enable) {
+                periodInMillis = 0;
+                intervalInMillis = 0;
+            }
+
+            // Verify that the integers are not negative. Leave actual parameter validation to
+            // supplicant.
+            if (periodInMillis < 0 || intervalInMillis < 0) {
+                Log.e(TAG, "Invalid parameters supplied to configureExtListen: " + periodInMillis
+                        + ", " + intervalInMillis);
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "configureExtListen(" + periodInMillis + ", " + intervalInMillis + ")");
+            try {
+                result.setResult(
+                        mISupplicantP2pIface.configureExtListen(periodInMillis, intervalInMillis));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Set P2P Listen channel and operating chanel.
+     *
+     * @param listenChannel Wifi channel. eg, 1, 6, 11.
+     * @param operatingChannel Wifi channel. eg, 1, 6, 11.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean setListenChannel(int listenChannel, int operatingChannel) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setListenChannel")) return false;
+
+            if (listenChannel >= 1 && listenChannel <= 11) {
+                SupplicantResult<Void> result = new SupplicantResult(
+                        "setListenChannel(" + listenChannel + ", " + DEFAULT_OPERATING_CLASS + ")");
+                try {
+                    result.setResult(mISupplicantP2pIface.setListenChannel(
+                            listenChannel, DEFAULT_OPERATING_CLASS));
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+                if (!result.isSuccess()) {
+                    return false;
+                }
+            } else if (listenChannel != 0) {
+                // listenChannel == 0 does not set any listen channel.
+                return false;
+            }
+
+            if (operatingChannel >= 0 && operatingChannel <= 165) {
+                ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
+                // operatingChannel == 0 enables all freqs.
+                if (operatingChannel >= 1 && operatingChannel <= 165) {
+                    int freq = (operatingChannel <= 14 ? 2407 : 5000) + operatingChannel * 5;
+                    ISupplicantP2pIface.FreqRange range1 =  new ISupplicantP2pIface.FreqRange();
+                    range1.min = 1000;
+                    range1.max = freq - 5;
+                    ISupplicantP2pIface.FreqRange range2 =  new ISupplicantP2pIface.FreqRange();
+                    range2.min = freq + 5;
+                    range2.max = 6000;
+                    ranges.add(range1);
+                    ranges.add(range2);
+                }
+                SupplicantResult<Void> result = new SupplicantResult(
+                        "setDisallowedFrequencies(" + ranges + ")");
+                try {
+                    result.setResult(mISupplicantP2pIface.setDisallowedFrequencies(ranges));
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+                return result.isSuccess();
+            }
+            return false;
+        }
+    }
+
+
+    /**
+     * This command can be used to add a upnp/bonjour service.
+     *
+     * @param servInfo List of service queries.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean serviceAdd(WifiP2pServiceInfo servInfo) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("serviceAdd")) return false;
+
+            if (servInfo == null) {
+                Log.e(TAG, "Null service info passed.");
+                return false;
+            }
+
+            for (String s : servInfo.getSupplicantQueryList()) {
+                if (s == null) {
+                    Log.e(TAG, "Invalid service description (null).");
+                    return false;
+                }
+
+                String[] data = s.split(" ");
+                if (data.length < 3) {
+                    Log.e(TAG, "Service specification invalid: " + s);
+                    return false;
+                }
+
+                SupplicantResult<Void> result = null;
+                try {
+                    if ("upnp".equals(data[0])) {
+                        int version = 0;
+                        try {
+                            version = Integer.parseInt(data[1], 16);
+                        } catch (NumberFormatException e) {
+                            Log.e(TAG, "UPnP Service specification invalid: " + s, e);
+                            return false;
+                        }
+
+                        result = new SupplicantResult(
+                                "addUpnpService(" + data[1] + ", " + data[2] + ")");
+                        result.setResult(mISupplicantP2pIface.addUpnpService(version, data[2]));
+                    } else if ("bonjour".equals(data[0])) {
+                        if (data[1] != null && data[2] != null) {
+                            ArrayList<Byte> request = null;
+                            ArrayList<Byte> response = null;
+                            try {
+                                request = NativeUtil.byteArrayToArrayList(
+                                        NativeUtil.hexStringToByteArray(data[1]));
+                                response = NativeUtil.byteArrayToArrayList(
+                                        NativeUtil.hexStringToByteArray(data[2]));
+                            } catch (Exception e) {
+                                Log.e(TAG, "Invalid bonjour service description.");
+                                return false;
+                            }
+                            result = new SupplicantResult(
+                                    "addBonjourService(" + data[1] + ", " + data[2] + ")");
+                            result.setResult(
+                                    mISupplicantP2pIface.addBonjourService(request, response));
+                        }
+                    } else {
+                        return false;
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+
+                if (result == null || !result.isSuccess()) return false;
+            }
+
+            return true;
+        }
+    }
+
+
+    /**
+     * This command can be used to remove a upnp/bonjour service.
+     *
+     * @param servInfo List of service queries.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean serviceRemove(WifiP2pServiceInfo servInfo) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("serviceRemove")) return false;
+
+            if (servInfo == null) {
+                Log.e(TAG, "Null service info passed.");
+                return false;
+            }
+
+            for (String s : servInfo.getSupplicantQueryList()) {
+                if (s == null) {
+                    Log.e(TAG, "Invalid service description (null).");
+                    return false;
+                }
+
+                String[] data = s.split(" ");
+                if (data.length < 3) {
+                    Log.e(TAG, "Service specification invalid: " + s);
+                    return false;
+                }
+
+                SupplicantResult<Void> result = null;
+                try {
+                    if ("upnp".equals(data[0])) {
+                        int version = 0;
+                        try {
+                            version = Integer.parseInt(data[1], 16);
+                        } catch (NumberFormatException e) {
+                            Log.e(TAG, "UPnP Service specification invalid: " + s, e);
+                            return false;
+                        }
+                        result = new SupplicantResult(
+                                "removeUpnpService(" + data[1] + ", " + data[2] + ")");
+                        result.setResult(mISupplicantP2pIface.removeUpnpService(version, data[2]));
+                    } else if ("bonjour".equals(data[0])) {
+                        if (data[1] != null) {
+                            ArrayList<Byte> request = null;
+                            try {
+                                request = NativeUtil.byteArrayToArrayList(
+                                    NativeUtil.hexStringToByteArray(data[1]));
+                            } catch (Exception e) {
+                                Log.e(TAG, "Invalid bonjour service description.");
+                                return false;
+                            }
+                            result = new SupplicantResult("removeBonjourService(" + data[1] + ")");
+                            result.setResult(mISupplicantP2pIface.removeBonjourService(request));
+                        }
+                    } else {
+                        Log.e(TAG, "Unknown / unsupported P2P service requested: " + data[0]);
+                        return false;
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+
+                if (result == null || !result.isSuccess()) return false;
+            }
+
+            return true;
+        }
+    }
+
+
+    /**
+     * Schedule a P2P service discovery request. The parameters for this command
+     * are the device address of the peer device (or 00:00:00:00:00:00 for
+     * wildcard query that is sent to every discovered P2P peer that supports
+     * service discovery) and P2P Service Query TLV(s) as hexdump.
+     *
+     * @param peerAddress MAC address of the device to discover.
+     * @param query Hex dump of the query data.
+     * @return identifier Identifier for the request. Can be used to cancel the
+     *         request.
+     */
+    public String requestServiceDiscovery(String peerAddress, String query) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("requestServiceDiscovery")) return null;
+
+            if (peerAddress == null) {
+                Log.e(TAG, "Cannot parse peer mac address.");
+                return null;
+            }
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(peerAddress);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not process peer MAC address.", e);
+                return null;
+            }
+
+            if (query == null) {
+                Log.e(TAG, "Cannot parse service discovery query: " + query);
+                return null;
+            }
+            ArrayList<Byte> binQuery = null;
+            try {
+                binQuery = NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(query));
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse service query.", e);
+                return null;
+            }
+
+            SupplicantResult<Long> result = new SupplicantResult(
+                    "requestServiceDiscovery(" + peerAddress + ", " + query + ")");
+            try {
+                mISupplicantP2pIface.requestServiceDiscovery(
+                        macAddress, binQuery,
+                        (SupplicantStatus status, long identifier) -> {
+                            result.setResult(status, new Long(identifier));
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            Long value = result.getResult();
+            if (value == null) return null;
+            return value.toString();
+        }
+    }
+
+
+    /**
+     * Cancel a previous service discovery request.
+     *
+     * @param identifier Identifier for the request to cancel.
+     * @return true, if operation was successful.
+     */
+    public boolean cancelServiceDiscovery(String identifier) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("cancelServiceDiscovery")) return false;
+            if (identifier == null) {
+                Log.e(TAG, "cancelServiceDiscovery requires a valid tag.");
+                return false;
+            }
+
+            long id = 0;
+            try {
+                id = Long.parseLong(identifier);
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "Service discovery identifier invalid: " + identifier, e);
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "cancelServiceDiscovery(" + identifier + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.cancelServiceDiscovery(id));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Send driver command to set Miracast mode.
+     *
+     * @param mode Mode of Miracast.
+     * @return true, if operation was successful.
+     */
+    public boolean setMiracastMode(int mode) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setMiracastMode")) return false;
+            byte targetMode = ISupplicantP2pIface.MiracastMode.DISABLED;
+
+            switch (mode) {
+                case WifiP2pManager.MIRACAST_SOURCE:
+                    targetMode = ISupplicantP2pIface.MiracastMode.SOURCE;
+                    break;
+
+                case WifiP2pManager.MIRACAST_SINK:
+                    targetMode = ISupplicantP2pIface.MiracastMode.SINK;
+                    break;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setMiracastMode(" + mode + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setMiracastMode(targetMode));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Initiate WPS Push Button setup.
+     * The PBC operation requires that a button is also pressed at the
+     * AP/Registrar at about the same time (2 minute window).
+     *
+     * @param groupIfName Group interface name to use.
+     * @param bssid BSSID of the AP. Use empty bssid to indicate wildcard.
+     * @return true, if operation was successful.
+     */
+    public boolean startWpsPbc(String groupIfName, String bssid) {
+        if (TextUtils.isEmpty(groupIfName)) {
+            Log.e(TAG, "Group name required when requesting WPS PBC. Got (" + groupIfName + ")");
+            return false;
+        }
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPbc")) return false;
+            // Null values should be fine, since bssid can be empty.
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(bssid);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse BSSID.", e);
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "startWpsPbc(" + groupIfName + ", " + bssid + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.startWpsPbc(groupIfName, macAddress));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Initiate WPS Pin Keypad setup.
+     *
+     * @param groupIfName Group interface name to use.
+     * @param pin 8 digit pin to be used.
+     * @return true, if operation was successful.
+     */
+    public boolean startWpsPinKeypad(String groupIfName, String pin) {
+        if (TextUtils.isEmpty(groupIfName) || TextUtils.isEmpty(pin)) return false;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPinKeypad")) return false;
+            if (groupIfName == null) {
+                Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
+                return false;
+            }
+            if (pin == null) {
+                Log.e(TAG, "PIN required when requesting WPS KEYPAD.");
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "startWpsPinKeypad(" + groupIfName + ", " + pin + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.startWpsPinKeypad(groupIfName, pin));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Initiate WPS Pin Display setup.
+     *
+     * @param groupIfName Group interface name to use.
+     * @param bssid BSSID of the AP. Use empty bssid to indicate wildcard.
+     * @return generated pin if operation was successful, null otherwise.
+     */
+    public String startWpsPinDisplay(String groupIfName, String bssid) {
+        if (TextUtils.isEmpty(groupIfName)) return null;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("startWpsPinDisplay")) return null;
+            if (groupIfName == null) {
+                Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
+                return null;
+            }
+
+            // Null values should be fine, since bssid can be empty.
+            byte[] macAddress = null;
+            try {
+                macAddress = NativeUtil.macAddressToByteArray(bssid);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse BSSID.", e);
+                return null;
+            }
+
+            SupplicantResult<String> result = new SupplicantResult(
+                    "startWpsPinDisplay(" + groupIfName + ", " + bssid + ")");
+            try {
+                mISupplicantP2pIface.startWpsPinDisplay(
+                        groupIfName, macAddress,
+                        (SupplicantStatus status, String generatedPin) -> {
+                            result.setResult(status, generatedPin);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.getResult();
+        }
+    }
+
+
+    /**
+     * Cancel any ongoing WPS operations.
+     *
+     * @param groupIfName Group interface name to use.
+     * @return true, if operation was successful.
+     */
+    public boolean cancelWps(String groupIfName) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("cancelWps")) return false;
+            if (groupIfName == null) {
+                Log.e(TAG, "Group name required when requesting WPS KEYPAD.");
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "cancelWps(" + groupIfName + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.cancelWps(groupIfName));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Enable/Disable Wifi Display.
+     *
+     * @param enable true to enable, false to disable.
+     * @return true, if operation was successful.
+     */
+    public boolean enableWfd(boolean enable) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("enableWfd")) return false;
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "enableWfd(" + enable + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.enableWfd(enable));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+
+    /**
+     * Set Wifi Display device info.
+     *
+     * @param info WFD device info as described in section 5.1.2 of WFD technical
+     *        specification v1.0.0.
+     * @return true, if operation was successful.
+     */
+    public boolean setWfdDeviceInfo(String info) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setWfdDeviceInfo")) return false;
+
+            if (info == null) {
+                Log.e(TAG, "Cannot parse null WFD info string.");
+                return false;
+            }
+            byte[] wfdInfo = null;
+            try {
+                wfdInfo = NativeUtil.hexStringToByteArray(info);
+            } catch (Exception e) {
+                Log.e(TAG, "Could not parse WFD Device Info string.");
+                return false;
+            }
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setWfdDeviceInfo(" + info + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setWfdDeviceInfo(wfdInfo));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Remove network with provided id.
+     *
+     * @param networkId Id of the network to lookup.
+     * @return true, if operation was successful.
+     */
+    public boolean removeNetwork(int networkId) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("removeNetwork")) return false;
+
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "removeNetwork(" + networkId + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.removeNetwork(networkId));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * List the networks saved in wpa_supplicant.
+     *
+     * @return List of network ids.
+     */
+    private List<Integer> listNetworks() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("listNetworks")) return null;
+            SupplicantResult<ArrayList> result = new SupplicantResult("listNetworks()");
+            try {
+                mISupplicantP2pIface.listNetworks(
+                        (SupplicantStatus status, ArrayList<Integer> networkIds) -> {
+                            result.setResult(status, networkIds);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.getResult();
+        }
+    }
+
+    /**
+     * Get the supplicant P2p network object for the specified network ID.
+     *
+     * @param networkId Id of the network to lookup.
+     * @return ISupplicantP2pNetwork instance on success, null on failure.
+     */
+    private ISupplicantP2pNetwork getNetwork(int networkId) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getNetwork")) return null;
+            SupplicantResult<ISupplicantNetwork> result =
+                    new SupplicantResult("getNetwork(" + networkId + ")");
+            try {
+                mISupplicantP2pIface.getNetwork(
+                        networkId,
+                        (SupplicantStatus status, ISupplicantNetwork network) -> {
+                            result.setResult(status, network);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            if (result.getResult() == null) {
+                Log.e(TAG, "getNetwork got null network");
+                return null;
+            }
+            return getP2pNetworkMockable(result.getResult());
+        }
+    }
+
+    /**
+     * Get the persistent group list from wpa_supplicant's p2p mgmt interface
+     *
+     * @param groups WifiP2pGroupList to store persistent groups in
+     * @return true, if list has been modified.
+     */
+    public boolean loadGroups(WifiP2pGroupList groups) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("loadGroups")) return false;
+            List<Integer> networkIds = listNetworks();
+            if (networkIds == null || networkIds.isEmpty()) {
+                return false;
+            }
+            for (Integer networkId : networkIds) {
+                ISupplicantP2pNetwork network = getNetwork(networkId);
+                if (network == null) {
+                    Log.e(TAG, "Failed to retrieve network object for " + networkId);
+                    continue;
+                }
+                SupplicantResult<Boolean> resultIsCurrent =
+                        new SupplicantResult("isCurrent(" + networkId + ")");
+                try {
+                    network.isCurrent(
+                            (SupplicantStatus status, boolean isCurrent) -> {
+                                resultIsCurrent.setResult(status, isCurrent);
+                            });
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+                /** Skip the current network, if we're somehow getting networks from the p2p GO
+                    interface, instead of p2p mgmt interface*/
+                if (!resultIsCurrent.isSuccess() || resultIsCurrent.getResult()) {
+                    Log.i(TAG, "Skipping current network");
+                    continue;
+                }
+
+                WifiP2pGroup group = new WifiP2pGroup();
+                group.setNetworkId(networkId);
+
+                // Now get the ssid, bssid and other flags for this network.
+                SupplicantResult<ArrayList> resultSsid =
+                        new SupplicantResult("getSsid(" + networkId + ")");
+                try {
+                    network.getSsid(
+                            (SupplicantStatus status, ArrayList<Byte> ssid) -> {
+                                resultSsid.setResult(status, ssid);
+                            });
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+                if (resultSsid.isSuccess() && resultSsid.getResult() != null
+                        && !resultSsid.getResult().isEmpty()) {
+                    group.setNetworkName(NativeUtil.removeEnclosingQuotes(
+                            NativeUtil.encodeSsid(resultSsid.getResult())));
+                }
+
+                SupplicantResult<byte[]> resultBssid =
+                        new SupplicantResult("getBssid(" + networkId + ")");
+                try {
+                    network.getBssid(
+                            (SupplicantStatus status, byte[] bssid) -> {
+                                resultBssid.setResult(status, bssid);
+                            });
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+                if (resultBssid.isSuccess() && !ArrayUtils.isEmpty(resultBssid.getResult())) {
+                    WifiP2pDevice device = new WifiP2pDevice();
+                    device.deviceAddress =
+                            NativeUtil.macAddressFromByteArray(resultBssid.getResult());
+                    group.setOwner(device);
+                }
+
+                SupplicantResult<Boolean> resultIsGo =
+                        new SupplicantResult("isGo(" + networkId + ")");
+                try {
+                    network.isGo(
+                            (SupplicantStatus status, boolean isGo) -> {
+                                resultIsGo.setResult(status, isGo);
+                            });
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+                if (resultIsGo.isSuccess()) {
+                    group.setIsGroupOwner(resultIsGo.getResult());
+                }
+                groups.add(group);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Set WPS device name.
+     *
+     * @param name String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsDeviceName(String name) {
+        if (name == null) {
+            return false;
+        }
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setWpsDeviceName")) return false;
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setWpsDeviceName(" + name + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.setWpsDeviceName(name));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Set WPS device type.
+     *
+     * @param typeStr Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsDeviceType(String typeStr) {
+        try {
+            Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr);
+            if (!match.find() || match.groupCount() != 3) {
+                Log.e(TAG, "Malformed WPS device type " + typeStr);
+                return false;
+            }
+            short categ = Short.parseShort(match.group(1));
+            byte[] oui = NativeUtil.hexStringToByteArray(match.group(2));
+            short subCateg = Short.parseShort(match.group(3));
+
+            byte[] bytes = new byte[8];
+            ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
+            byteBuffer.putShort(categ);
+            byteBuffer.put(oui);
+            byteBuffer.putShort(subCateg);
+            synchronized (mLock) {
+                if (!checkSupplicantP2pIfaceAndLogFailure("setWpsDeviceType")) return false;
+                SupplicantResult<Void> result = new SupplicantResult(
+                        "setWpsDeviceType(" + typeStr + ")");
+                try {
+                    result.setResult(mISupplicantP2pIface.setWpsDeviceType(bytes));
+                } catch (RemoteException e) {
+                    Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                    supplicantServiceDiedHandler();
+                }
+                return result.isSuccess();
+            }
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + typeStr, e);
+            return false;
+        }
+    }
+
+    /**
+     * Set WPS config methods
+     *
+     * @param configMethodsStr List of config methods.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setWpsConfigMethods(String configMethodsStr) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setWpsConfigMethods")) return false;
+            SupplicantResult<Void> result =
+                    new SupplicantResult("setWpsConfigMethods(" + configMethodsStr + ")");
+            short configMethodsMask = 0;
+            String[] configMethodsStrArr = configMethodsStr.split("\\s+");
+            for (int i = 0; i < configMethodsStrArr.length; i++) {
+                configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]);
+            }
+            try {
+                result.setResult(mISupplicantP2pIface.setWpsConfigMethods(configMethodsMask));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Get NFC handover request message.
+     *
+     * @return select message if created successfully, null otherwise.
+     */
+    public String getNfcHandoverRequest() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getNfcHandoverRequest")) return null;
+            SupplicantResult<ArrayList> result = new SupplicantResult(
+                    "getNfcHandoverRequest()");
+            try {
+                mISupplicantP2pIface.createNfcHandoverRequestMessage(
+                        (SupplicantStatus status, ArrayList<Byte> message) -> {
+                            result.setResult(status, message);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            if (!result.isSuccess()) {
+                return null;
+
+            }
+            return NativeUtil.hexStringFromByteArray(
+                    NativeUtil.byteArrayFromArrayList(result.getResult()));
+        }
+    }
+
+    /**
+     * Get NFC handover select message.
+     *
+     * @return select message if created successfully, null otherwise.
+     */
+    public String getNfcHandoverSelect() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getNfcHandoverSelect")) return null;
+            SupplicantResult<ArrayList> result = new SupplicantResult(
+                    "getNfcHandoverSelect()");
+            try {
+                mISupplicantP2pIface.createNfcHandoverSelectMessage(
+                        (SupplicantStatus status, ArrayList<Byte> message) -> {
+                            result.setResult(status, message);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            if (!result.isSuccess()) {
+                return null;
+
+            }
+            return NativeUtil.hexStringFromByteArray(
+                    NativeUtil.byteArrayFromArrayList(result.getResult()));
+        }
+    }
+
+    /**
+     * Report NFC handover select message.
+     *
+     * @return true if reported successfully, false otherwise.
+     */
+    public boolean initiatorReportNfcHandover(String selectMessage) {
+        if (selectMessage == null) return false;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("initiatorReportNfcHandover")) return false;
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "initiatorReportNfcHandover(" + selectMessage + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.reportNfcHandoverInitiation(
+                        NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(
+                            selectMessage))));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + selectMessage, e);
+                return false;
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Report NFC handover request message.
+     *
+     * @return true if reported successfully, false otherwise.
+     */
+    public boolean responderReportNfcHandover(String requestMessage) {
+        if (requestMessage == null) return false;
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("responderReportNfcHandover")) return false;
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "responderReportNfcHandover(" + requestMessage + ")");
+            try {
+                result.setResult(mISupplicantP2pIface.reportNfcHandoverResponse(
+                        NativeUtil.byteArrayToArrayList(NativeUtil.hexStringToByteArray(
+                            requestMessage))));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + requestMessage, e);
+                return false;
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Set the client list for the provided network.
+     *
+     * @param networkId Id of the network.
+     * @param clientListStr Space separated list of clients.
+     * @return true, if operation was successful.
+     */
+    public boolean setClientList(int networkId, String clientListStr) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("setClientList")) return false;
+            if (TextUtils.isEmpty(clientListStr)) {
+                Log.e(TAG, "Invalid client list");
+                return false;
+            }
+            ISupplicantP2pNetwork network = getNetwork(networkId);
+            if (network == null) {
+                Log.e(TAG, "Invalid network id ");
+                return false;
+            }
+            SupplicantResult<Void> result = new SupplicantResult(
+                    "setClientList(" + networkId + ", " + clientListStr + ")");
+            try {
+                ArrayList<byte[]> clients = new ArrayList<>();
+                for (String clientStr : Arrays.asList(clientListStr.split("\\s+"))) {
+                    clients.add(NativeUtil.macAddressToByteArray(clientStr));
+                }
+                result.setResult(network.setClientList(clients));
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Illegal argument " + clientListStr, e);
+                return false;
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Set the client list for the provided network.
+     *
+     * @param networkId Id of the network.
+     * @return  Space separated list of clients if successfull, null otherwise.
+     */
+    public String getClientList(int networkId) {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("getClientList")) return null;
+            ISupplicantP2pNetwork network = getNetwork(networkId);
+            if (network == null) {
+                Log.e(TAG, "Invalid network id ");
+                return null;
+            }
+            SupplicantResult<ArrayList> result = new SupplicantResult(
+                    "getClientList(" + networkId + ")");
+            try {
+                network.getClientList(
+                        (SupplicantStatus status, ArrayList<byte[]> clients) -> {
+                            result.setResult(status, clients);
+                        });
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            if (!result.isSuccess()) {
+                return null;
+            }
+            ArrayList<byte[]> clients = result.getResult();
+            return clients.stream()
+                    .map(NativeUtil::macAddressFromByteArray)
+                    .collect(Collectors.joining(" "));
+        }
+    }
+
+    /**
+     * Persist the current configurations to disk.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean saveConfig() {
+        synchronized (mLock) {
+            if (!checkSupplicantP2pIfaceAndLogFailure("saveConfig")) return false;
+            SupplicantResult<Void> result = new SupplicantResult("saveConfig()");
+            try {
+                result.setResult(mISupplicantP2pIface.saveConfig());
+            } catch (RemoteException e) {
+                Log.e(TAG, "ISupplicantP2pIface exception: " + e);
+                supplicantServiceDiedHandler();
+            }
+            return result.isSuccess();
+        }
+    }
+
+    /**
+     * Converts the Wps config method string to the equivalent enum value.
+     */
+    private static short stringToWpsConfigMethod(String configMethod) {
+        switch (configMethod) {
+            case "usba":
+                return WpsConfigMethods.USBA;
+            case "ethernet":
+                return WpsConfigMethods.ETHERNET;
+            case "label":
+                return WpsConfigMethods.LABEL;
+            case "display":
+                return WpsConfigMethods.DISPLAY;
+            case "int_nfc_token":
+                return WpsConfigMethods.INT_NFC_TOKEN;
+            case "ext_nfc_token":
+                return WpsConfigMethods.EXT_NFC_TOKEN;
+            case "nfc_interface":
+                return WpsConfigMethods.NFC_INTERFACE;
+            case "push_button":
+                return WpsConfigMethods.PUSHBUTTON;
+            case "keypad":
+                return WpsConfigMethods.KEYPAD;
+            case "virtual_push_button":
+                return WpsConfigMethods.VIRT_PUSHBUTTON;
+            case "physical_push_button":
+                return WpsConfigMethods.PHY_PUSHBUTTON;
+            case "p2ps":
+                return WpsConfigMethods.P2PS;
+            case "virtual_display":
+                return WpsConfigMethods.VIRT_DISPLAY;
+            case "physical_display":
+                return WpsConfigMethods.PHY_DISPLAY;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid WPS config method: " + configMethod);
+        }
+    }
+
+    /** Container class allowing propagation of status and/or value
+     * from callbacks.
+     *
+     * Primary purpose is to allow callback lambdas to provide results
+     * to parent methods.
+     */
+    private static class SupplicantResult<E> {
+        private String mMethodName;
+        private SupplicantStatus mStatus;
+        private E mValue;
+
+        SupplicantResult(String methodName) {
+            mMethodName = methodName;
+            mStatus = null;
+            mValue = null;
+            logd("entering " + mMethodName);
+        }
+
+        public void setResult(SupplicantStatus status, E value) {
+            logCompletion(mMethodName, status);
+            logd("leaving " + mMethodName + " with result = " + value);
+            mStatus = status;
+            mValue = value;
+        }
+
+        public void setResult(SupplicantStatus status) {
+            logCompletion(mMethodName, status);
+            logd("leaving " + mMethodName);
+            mStatus = status;
+        }
+
+        public boolean isSuccess() {
+            return (mStatus != null && mStatus.code == SupplicantStatusCode.SUCCESS);
+        }
+
+        public E getResult() {
+            return (isSuccess() ? mValue : null);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pMonitor.java b/service/java/com/android/server/wifi/p2p/WifiP2pMonitor.java
new file mode 100644
index 0000000..e14c10c
--- /dev/null
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pMonitor.java
@@ -0,0 +1,519 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wifi.p2p;
+
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.os.Handler;
+import android.os.Message;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Protocol;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.p2p.WifiP2pServiceImpl.P2pStatus;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Listens for events from the wpa_supplicant, and passes them on
+ * to the {@link WifiP2pServiceImpl} for handling.
+ *
+ * @hide
+ */
+public class WifiP2pMonitor {
+    private static final String TAG = "WifiP2pMonitor";
+
+    /* Supplicant events reported to a state machine */
+    private static final int BASE = Protocol.BASE_WIFI_MONITOR;
+
+    /* Connection to supplicant established */
+    public static final int SUP_CONNECTION_EVENT                 = BASE + 1;
+    /* Connection to supplicant lost */
+    public static final int SUP_DISCONNECTION_EVENT              = BASE + 2;
+
+    /* P2P events */
+    public static final int P2P_DEVICE_FOUND_EVENT               = BASE + 21;
+    public static final int P2P_DEVICE_LOST_EVENT                = BASE + 22;
+    public static final int P2P_GO_NEGOTIATION_REQUEST_EVENT     = BASE + 23;
+    public static final int P2P_GO_NEGOTIATION_SUCCESS_EVENT     = BASE + 25;
+    public static final int P2P_GO_NEGOTIATION_FAILURE_EVENT     = BASE + 26;
+    public static final int P2P_GROUP_FORMATION_SUCCESS_EVENT    = BASE + 27;
+    public static final int P2P_GROUP_FORMATION_FAILURE_EVENT    = BASE + 28;
+    public static final int P2P_GROUP_STARTED_EVENT              = BASE + 29;
+    public static final int P2P_GROUP_REMOVED_EVENT              = BASE + 30;
+    public static final int P2P_INVITATION_RECEIVED_EVENT        = BASE + 31;
+    public static final int P2P_INVITATION_RESULT_EVENT          = BASE + 32;
+    public static final int P2P_PROV_DISC_PBC_REQ_EVENT          = BASE + 33;
+    public static final int P2P_PROV_DISC_PBC_RSP_EVENT          = BASE + 34;
+    public static final int P2P_PROV_DISC_ENTER_PIN_EVENT        = BASE + 35;
+    public static final int P2P_PROV_DISC_SHOW_PIN_EVENT         = BASE + 36;
+    public static final int P2P_FIND_STOPPED_EVENT               = BASE + 37;
+    public static final int P2P_SERV_DISC_RESP_EVENT             = BASE + 38;
+    public static final int P2P_PROV_DISC_FAILURE_EVENT          = BASE + 39;
+
+    /* hostap events */
+    public static final int AP_STA_DISCONNECTED_EVENT            = BASE + 41;
+    public static final int AP_STA_CONNECTED_EVENT               = BASE + 42;
+
+
+    private final WifiInjector mWifiInjector;
+    private boolean mVerboseLoggingEnabled = false;
+    private boolean mConnected = false;
+
+    public WifiP2pMonitor(WifiInjector wifiInjector) {
+        mWifiInjector = wifiInjector;
+    }
+
+    void enableVerboseLogging(int verbose) {
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
+        } else {
+            mVerboseLoggingEnabled = false;
+        }
+    }
+
+    // TODO(b/27569474) remove support for multiple handlers for the same event
+    private final Map<String, SparseArray<Set<Handler>>> mHandlerMap = new HashMap<>();
+
+    /**
+     * Registers a callback handler for the provided event.
+     */
+    public synchronized void registerHandler(String iface, int what, Handler handler) {
+        SparseArray<Set<Handler>> ifaceHandlers = mHandlerMap.get(iface);
+        if (ifaceHandlers == null) {
+            ifaceHandlers = new SparseArray<>();
+            mHandlerMap.put(iface, ifaceHandlers);
+        }
+        Set<Handler> ifaceWhatHandlers = ifaceHandlers.get(what);
+        if (ifaceWhatHandlers == null) {
+            ifaceWhatHandlers = new ArraySet<>();
+            ifaceHandlers.put(what, ifaceWhatHandlers);
+        }
+        ifaceWhatHandlers.add(handler);
+    }
+
+    private final Map<String, Boolean> mMonitoringMap = new HashMap<>();
+    private boolean isMonitoring(String iface) {
+        Boolean val = mMonitoringMap.get(iface);
+        if (val == null) {
+            return false;
+        } else {
+            return val.booleanValue();
+        }
+    }
+
+    /**
+     * Enable/Disable monitoring for the provided iface.
+     *
+     * @param iface Name of the iface.
+     * @param enabled true to enable, false to disable.
+     */
+    @VisibleForTesting
+    public void setMonitoring(String iface, boolean enabled) {
+        mMonitoringMap.put(iface, enabled);
+    }
+
+    private void setMonitoringNone() {
+        for (String iface : mMonitoringMap.keySet()) {
+            setMonitoring(iface, false);
+        }
+    }
+
+    /**
+     * Wait for wpa_supplicant's control interface to be ready.
+     *
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    private boolean ensureConnectedLocked() {
+        if (mConnected) {
+            return true;
+        }
+        if (mVerboseLoggingEnabled) Log.d(TAG, "connecting to supplicant");
+        int connectTries = 0;
+        while (true) {
+            mConnected = mWifiInjector.getWifiP2pNative().connectToSupplicant();
+            if (mConnected) {
+                return true;
+            }
+            if (connectTries++ < 50) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException ignore) {
+                }
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Start Monitoring for wpa_supplicant events.
+     *
+     * @param iface Name of iface.
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    public synchronized void startMonitoring(String iface) {
+        if (ensureConnectedLocked()) {
+            setMonitoring(iface, true);
+            broadcastSupplicantConnectionEvent(iface);
+        } else {
+            boolean originalMonitoring = isMonitoring(iface);
+            setMonitoring(iface, true);
+            broadcastSupplicantDisconnectionEvent(iface);
+            setMonitoring(iface, originalMonitoring);
+            Log.e(TAG, "startMonitoring(" + iface + ") failed!");
+        }
+    }
+
+    /**
+     * Stop Monitoring for wpa_supplicant events.
+     *
+     * @param iface Name of iface.
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    public synchronized void stopMonitoring(String iface) {
+        if (mVerboseLoggingEnabled) Log.d(TAG, "stopMonitoring(" + iface + ")");
+        setMonitoring(iface, true);
+        broadcastSupplicantDisconnectionEvent(iface);
+        setMonitoring(iface, false);
+    }
+
+    /**
+     * Stop Monitoring for wpa_supplicant events.
+     *
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    public synchronized void stopAllMonitoring() {
+        mConnected = false;
+        setMonitoringNone();
+    }
+
+    /**
+     * Similar functions to Handler#sendMessage that send the message to the registered handler
+     * for the given interface and message what.
+     * All of these should be called with the WifiMonitor class lock
+     */
+    private void sendMessage(String iface, int what) {
+        sendMessage(iface, Message.obtain(null, what));
+    }
+
+    private void sendMessage(String iface, int what, Object obj) {
+        sendMessage(iface, Message.obtain(null, what, obj));
+    }
+
+    private void sendMessage(String iface, int what, int arg1) {
+        sendMessage(iface, Message.obtain(null, what, arg1, 0));
+    }
+
+    private void sendMessage(String iface, int what, int arg1, int arg2) {
+        sendMessage(iface, Message.obtain(null, what, arg1, arg2));
+    }
+
+    private void sendMessage(String iface, int what, int arg1, int arg2, Object obj) {
+        sendMessage(iface, Message.obtain(null, what, arg1, arg2, obj));
+    }
+
+    private void sendMessage(String iface, Message message) {
+        SparseArray<Set<Handler>> ifaceHandlers = mHandlerMap.get(iface);
+        if (iface != null && ifaceHandlers != null) {
+            if (isMonitoring(iface)) {
+                Set<Handler> ifaceWhatHandlers = ifaceHandlers.get(message.what);
+                if (ifaceWhatHandlers != null) {
+                    for (Handler handler : ifaceWhatHandlers) {
+                        if (handler != null) {
+                            sendMessage(handler, Message.obtain(message));
+                        }
+                    }
+                }
+            } else {
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
+                }
+            }
+        } else {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Sending to all monitors because there's no matching iface");
+            }
+            for (Map.Entry<String, SparseArray<Set<Handler>>> entry : mHandlerMap.entrySet()) {
+                if (isMonitoring(entry.getKey())) {
+                    Set<Handler> ifaceWhatHandlers = entry.getValue().get(message.what);
+                    for (Handler handler : ifaceWhatHandlers) {
+                        if (handler != null) {
+                            sendMessage(handler, Message.obtain(message));
+                        }
+                    }
+                }
+            }
+        }
+
+        message.recycle();
+    }
+
+    private void sendMessage(Handler handler, Message message) {
+        message.setTarget(handler);
+        message.sendToTarget();
+    }
+
+    /**
+     * Broadcast the connection to wpa_supplicant event to all the handlers registered for
+     * this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastSupplicantConnectionEvent(String iface) {
+        sendMessage(iface, SUP_CONNECTION_EVENT);
+    }
+
+    /**
+     * Broadcast the loss of connection to wpa_supplicant event to all the handlers registered for
+     * this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastSupplicantDisconnectionEvent(String iface) {
+        sendMessage(iface, SUP_DISCONNECTION_EVENT);
+    }
+
+    /**
+     * Broadcast new p2p device discovered event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param device Device that has been discovered during recent scan.
+     */
+    public void broadcastP2pDeviceFound(String iface, WifiP2pDevice device) {
+        if (device != null) {
+            sendMessage(iface, P2P_DEVICE_FOUND_EVENT, device);
+        }
+    }
+
+    /**
+     * Broadcast p2p device lost event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param device Device that has been lost in recent scan.
+     */
+    public void broadcastP2pDeviceLost(String iface, WifiP2pDevice device) {
+        if (device != null) {
+            sendMessage(iface, P2P_DEVICE_LOST_EVENT, device);
+        }
+    }
+
+    /**
+     * Broadcast scan termination event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastP2pFindStopped(String iface) {
+        sendMessage(iface, P2P_FIND_STOPPED_EVENT);
+    }
+
+    /**
+     * Broadcast group owner negotiation request event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param config P2p configuration.
+     */
+    public void broadcastP2pGoNegotiationRequest(String iface, WifiP2pConfig config) {
+        if (config != null) {
+            sendMessage(iface, P2P_GO_NEGOTIATION_REQUEST_EVENT, config);
+        }
+    }
+
+    /**
+     * Broadcast group owner negotiation success event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastP2pGoNegotiationSuccess(String iface) {
+        sendMessage(iface, P2P_GO_NEGOTIATION_SUCCESS_EVENT);
+    }
+
+    /**
+     * Broadcast group owner negotiation failure event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param reason Failure reason.
+     */
+    public void broadcastP2pGoNegotiationFailure(String iface, P2pStatus reason) {
+        sendMessage(iface, P2P_GO_NEGOTIATION_FAILURE_EVENT, reason);
+    }
+
+    /**
+     * Broadcast group formation success event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastP2pGroupFormationSuccess(String iface) {
+        sendMessage(iface, P2P_GROUP_FORMATION_SUCCESS_EVENT);
+    }
+
+    /**
+     * Broadcast group formation failure event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param reason Failure reason.
+     */
+    public void broadcastP2pGroupFormationFailure(String iface, String reason) {
+        P2pStatus err = P2pStatus.UNKNOWN;
+        if (reason.equals("FREQ_CONFLICT")) {
+            err = P2pStatus.NO_COMMON_CHANNEL;
+        }
+        sendMessage(iface, P2P_GROUP_FORMATION_FAILURE_EVENT, err);
+    }
+
+    /**
+     * Broadcast group started event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param group Started group.
+     */
+    public void broadcastP2pGroupStarted(String iface, WifiP2pGroup group) {
+        if (group != null) {
+            sendMessage(iface, P2P_GROUP_STARTED_EVENT, group);
+        }
+    }
+
+    /**
+     * Broadcast group removed event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param group Removed group.
+     */
+    public void broadcastP2pGroupRemoved(String iface, WifiP2pGroup group) {
+        if (group != null) {
+            sendMessage(iface, P2P_GROUP_REMOVED_EVENT, group);
+        }
+    }
+
+    /**
+     * Broadcast invitation received event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param group Group to which invitation has been received.
+     */
+    public void broadcastP2pInvitationReceived(String iface, WifiP2pGroup group) {
+        if (group != null) {
+            sendMessage(iface, P2P_INVITATION_RECEIVED_EVENT, group);
+        }
+    }
+
+    /**
+     * Broadcast invitation result event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param result Result of invitation.
+     */
+    public void broadcastP2pInvitationResult(String iface, P2pStatus result) {
+        sendMessage(iface, P2P_INVITATION_RESULT_EVENT, result);
+    }
+
+    /**
+     * Broadcast PB discovery request event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param event Provision discovery request event.
+     */
+    public void broadcastP2pProvisionDiscoveryPbcRequest(String iface, WifiP2pProvDiscEvent event) {
+        if (event != null) {
+            sendMessage(iface, P2P_PROV_DISC_PBC_REQ_EVENT, event);
+        }
+    }
+
+    /**
+     * Broadcast PB discovery response event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param event Provision discovery response event.
+     */
+    public void broadcastP2pProvisionDiscoveryPbcResponse(
+            String iface, WifiP2pProvDiscEvent event) {
+        if (event != null) {
+            sendMessage(iface, P2P_PROV_DISC_PBC_RSP_EVENT, event);
+        }
+    }
+
+    /**
+     * Broadcast PIN discovery request event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param event Provision discovery request event.
+     */
+    public void broadcastP2pProvisionDiscoveryEnterPin(String iface, WifiP2pProvDiscEvent event) {
+        if (event != null) {
+            sendMessage(iface, P2P_PROV_DISC_ENTER_PIN_EVENT, event);
+        }
+    }
+
+    /**
+     * Broadcast PIN discovery response event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param event Provision discovery response event.
+     */
+    public void broadcastP2pProvisionDiscoveryShowPin(String iface, WifiP2pProvDiscEvent event) {
+        if (event != null) {
+            sendMessage(iface, P2P_PROV_DISC_SHOW_PIN_EVENT, event);
+        }
+    }
+
+    /**
+     * Broadcast P2P discovery failure event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastP2pProvisionDiscoveryFailure(String iface) {
+        sendMessage(iface, P2P_PROV_DISC_FAILURE_EVENT);
+    }
+
+    /**
+     * Broadcast service discovery response event to all handlers registered for this event.
+     *
+     * @param iface Name of iface on which this occurred.
+     * @param services List of discovered services.
+     */
+    public void broadcastP2pServiceDiscoveryResponse(
+            String iface, List<WifiP2pServiceResponse> services) {
+        sendMessage(iface, P2P_SERV_DISC_RESP_EVENT, services);
+    }
+
+    /**
+     * Broadcast AP STA connection event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastP2pApStaConnected(String iface, WifiP2pDevice device) {
+        sendMessage(iface, AP_STA_CONNECTED_EVENT, device);
+    }
+
+    /**
+     * Broadcast AP STA disconnection event.
+     *
+     * @param iface Name of iface on which this occurred.
+     */
+    public void broadcastP2pApStaDisconnected(String iface, WifiP2pDevice device) {
+        sendMessage(iface, AP_STA_DISCONNECTED_EVENT, device);
+    }
+}
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pNative.java b/service/java/com/android/server/wifi/p2p/WifiP2pNative.java
new file mode 100644
index 0000000..bae3faa
--- /dev/null
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pNative.java
@@ -0,0 +1,600 @@
+/*
+ * 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.
+ */
+
+package com.android.server.wifi.p2p;
+
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pGroupList;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+
+/**
+ * Native calls for bring up/shut down of the supplicant daemon and for
+ * sending requests to the supplicant daemon
+ *
+ * {@hide}
+ */
+public class WifiP2pNative {
+    private final String mTAG;
+    private final String mInterfaceName;
+    private final SupplicantP2pIfaceHal mSupplicantP2pIfaceHal;
+
+    public WifiP2pNative(String interfaceName, SupplicantP2pIfaceHal p2pIfaceHal) {
+        mTAG = "WifiP2pNative-" + interfaceName;
+        mInterfaceName = interfaceName;
+        mSupplicantP2pIfaceHal = p2pIfaceHal;
+    }
+
+    public String getInterfaceName() {
+        return mInterfaceName;
+    }
+
+    /**
+     * Enable verbose logging for all sub modules.
+     */
+    public void enableVerboseLogging(int verbose) {
+    }
+
+    /********************************************************
+     * Supplicant operations
+     ********************************************************/
+    /**
+     * This method is called repeatedly until the connection to wpa_supplicant is established.
+     *
+     * @return true if connection is established, false otherwise.
+     * TODO: Add unit tests for these once we remove the legacy code.
+     */
+    public boolean connectToSupplicant() {
+        // Start initialization if not already started.
+        if (!mSupplicantP2pIfaceHal.isInitializationStarted()
+                && !mSupplicantP2pIfaceHal.initialize()) {
+            return false;
+        }
+        // Check if the initialization is complete.
+        return mSupplicantP2pIfaceHal.isInitializationComplete();
+    }
+
+    /**
+     * Close supplicant connection.
+     */
+    public void closeSupplicantConnection() {
+        // Nothing to do for HIDL.
+    }
+
+    /**
+     * Set WPS device name.
+     *
+     * @param name String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setDeviceName(String name) {
+        return mSupplicantP2pIfaceHal.setWpsDeviceName(name);
+    }
+
+    /**
+     * Populate list of available networks or update existing list.
+     *
+     * @return true, if list has been modified.
+     */
+    public boolean p2pListNetworks(WifiP2pGroupList groups) {
+        return mSupplicantP2pIfaceHal.loadGroups(groups);
+    }
+
+    /**
+     * Initiate WPS Push Button setup.
+     * The PBC operation requires that a button is also pressed at the
+     * AP/Registrar at about the same time (2 minute window).
+     *
+     * @param iface Group interface name to use.
+     * @param bssid BSSID of the AP. Use zero'ed bssid to indicate wildcard.
+     * @return true, if operation was successful.
+     */
+    public boolean startWpsPbc(String iface, String bssid) {
+        return mSupplicantP2pIfaceHal.startWpsPbc(iface, bssid);
+    }
+
+    /**
+     * Initiate WPS Pin Keypad setup.
+     *
+     * @param iface Group interface name to use.
+     * @param pin 8 digit pin to be used.
+     * @return true, if operation was successful.
+     */
+    public boolean startWpsPinKeypad(String iface, String pin) {
+        return mSupplicantP2pIfaceHal.startWpsPinKeypad(iface, pin);
+    }
+
+    /**
+     * Initiate WPS Pin Display setup.
+     *
+     * @param iface Group interface name to use.
+     * @param bssid BSSID of the AP. Use zero'ed bssid to indicate wildcard.
+     * @return generated pin if operation was successful, null otherwise.
+     */
+    public String startWpsPinDisplay(String iface, String bssid) {
+        return mSupplicantP2pIfaceHal.startWpsPinDisplay(iface, bssid);
+    }
+
+    /**
+     * Remove network with provided id.
+     *
+     * @param netId Id of the network to lookup.
+     * @return true, if operation was successful.
+     */
+    public boolean removeP2pNetwork(int netId) {
+        return mSupplicantP2pIfaceHal.removeNetwork(netId);
+    }
+
+    /**
+     * Set WPS device name.
+     *
+     * @param name String to be set.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setP2pDeviceName(String name) {
+        return mSupplicantP2pIfaceHal.setWpsDeviceName(name);
+    }
+
+    /**
+     * Set WPS device type.
+     *
+     * @param type Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setP2pDeviceType(String type) {
+        return mSupplicantP2pIfaceHal.setWpsDeviceType(type);
+    }
+
+    /**
+     * Set WPS config methods
+     *
+     * @param cfg List of config methods.
+     * @return true if request is sent successfully, false otherwise.
+     */
+    public boolean setConfigMethods(String cfg) {
+        return mSupplicantP2pIfaceHal.setWpsConfigMethods(cfg);
+    }
+
+    /**
+     * Set the postfix to be used for P2P SSID's.
+     *
+     * @param postfix String to be appended to SSID.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean setP2pSsidPostfix(String postfix) {
+        return mSupplicantP2pIfaceHal.setSsidPostfix(postfix);
+    }
+
+    /**
+     * Set the Maximum idle time in seconds for P2P groups.
+     * This value controls how long a P2P group is maintained after there
+     * is no other members in the group. As a group owner, this means no
+     * associated stations in the group. As a P2P client, this means no
+     * group owner seen in scan results.
+     *
+     * @param iface Group interface name to use.
+     * @param time Timeout value in seconds.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean setP2pGroupIdle(String iface, int time) {
+        return mSupplicantP2pIfaceHal.setGroupIdle(iface, time);
+    }
+
+    /**
+     * Turn on/off power save mode for the interface.
+     *
+     * @param iface Group interface name to use.
+     * @param enabled Indicate if power save is to be turned on/off.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean setP2pPowerSave(String iface, boolean enabled) {
+        return mSupplicantP2pIfaceHal.setPowerSave(iface, enabled);
+    }
+
+    /**
+     * Enable/Disable Wifi Display.
+     *
+     * @param enable true to enable, false to disable.
+     * @return true, if operation was successful.
+     */
+    public boolean setWfdEnable(boolean enable) {
+        return mSupplicantP2pIfaceHal.enableWfd(enable);
+    }
+
+    /**
+     * Set Wifi Display device info.
+     *
+     * @param hex WFD device info as described in section 5.1.2 of WFD technical
+     *        specification v1.0.0.
+     * @return true, if operation was successful.
+     */
+    public boolean setWfdDeviceInfo(String hex) {
+        return mSupplicantP2pIfaceHal.setWfdDeviceInfo(hex);
+    }
+
+    /**
+     * Initiate a P2P service discovery indefinitely.
+     * Will trigger {@link WifiP2pMonitor#P2P_DEVICE_FOUND_EVENT} on finding devices.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pFind() {
+        return p2pFind(0);
+    }
+
+    /**
+     * Initiate a P2P service discovery with a (optional) timeout.
+     *
+     * @param timeout Max time to be spent is peforming discovery.
+     *        Set to 0 to indefinely continue discovery untill and explicit
+     *        |stopFind| is sent.
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pFind(int timeout) {
+        return mSupplicantP2pIfaceHal.find(timeout);
+    }
+
+    /**
+     * Stop an ongoing P2P service discovery.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pStopFind() {
+        return mSupplicantP2pIfaceHal.stopFind();
+    }
+
+    /**
+     * Configure Extended Listen Timing.
+     *
+     * If enabled, listen state must be entered every |intervalInMillis| for at
+     * least |periodInMillis|. Both values have acceptable range of 1-65535
+     * (with interval obviously having to be larger than or equal to duration).
+     * If the P2P module is not idle at the time the Extended Listen Timing
+     * timeout occurs, the Listen State operation must be skipped.
+     *
+     * @param enable Enables or disables listening.
+     * @param period Period in milliseconds.
+     * @param interval Interval in milliseconds.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean p2pExtListen(boolean enable, int period, int interval) {
+        return mSupplicantP2pIfaceHal.configureExtListen(enable, period, interval);
+    }
+
+    /**
+     * Set P2P Listen channel.
+     *
+     * When specifying a social channel on the 2.4 GHz band (1/6/11) there is no
+     * need to specify the operating class since it defaults to 81. When
+     * specifying a social channel on the 60 GHz band (2), specify the 60 GHz
+     * operating class (180).
+     *
+     * @param lc Wifi channel. eg, 1, 6, 11.
+     * @param oc Operating Class indicates the channel set of the AP
+     *        indicated by this BSSID
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean p2pSetChannel(int lc, int oc) {
+        return mSupplicantP2pIfaceHal.setListenChannel(lc, oc);
+    }
+
+    /**
+     * Flush P2P peer table and state.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pFlush() {
+        return mSupplicantP2pIfaceHal.flush();
+    }
+
+    /**
+     * Start P2P group formation with a discovered P2P peer. This includes
+     * optional group owner negotiation, group interface setup, provisioning,
+     * and establishing data connection.
+     *
+     * @param config Configuration to use to connect to remote device.
+     * @param joinExistingGroup Indicates that this is a command to join an
+     *        existing group as a client. It skips the group owner negotiation
+     *        part. This must send a Provision Discovery Request message to the
+     *        target group owner before associating for WPS provisioning.
+     *
+     * @return String containing generated pin, if selected provision method
+     *        uses PIN.
+     */
+    public String p2pConnect(WifiP2pConfig config, boolean joinExistingGroup) {
+        return mSupplicantP2pIfaceHal.connect(config, joinExistingGroup);
+    }
+
+    /**
+     * Cancel an ongoing P2P group formation and joining-a-group related
+     * operation. This operation unauthorizes the specific peer device (if any
+     * had been authorized to start group formation), stops P2P find (if in
+     * progress), stops pending operations for join-a-group, and removes the
+     * P2P group interface (if one was used) that is in the WPS provisioning
+     * step. If the WPS provisioning step has been completed, the group is not
+     * terminated.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pCancelConnect() {
+        return mSupplicantP2pIfaceHal.cancelConnect();
+    }
+
+    /**
+     * Send P2P provision discovery request to the specified peer. The
+     * parameters for this command are the P2P device address of the peer and the
+     * desired configuration method.
+     *
+     * @param config Config class describing peer setup.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pProvisionDiscovery(WifiP2pConfig config) {
+        return mSupplicantP2pIfaceHal.provisionDiscovery(config);
+    }
+
+    /**
+     * Set up a P2P group owner manually.
+     * This is a helper method that invokes groupAdd(networkId, isPersistent) internally.
+     *
+     * @param persistent Used to request a persistent group to be formed.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean p2pGroupAdd(boolean persistent) {
+        return mSupplicantP2pIfaceHal.groupAdd(persistent);
+    }
+
+    /**
+     * Set up a P2P group owner manually (i.e., without group owner
+     * negotiation with a specific peer). This is also known as autonomous
+     * group owner.
+     *
+     * @param netId Used to specify the restart of a persistent group.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean p2pGroupAdd(int netId) {
+        return mSupplicantP2pIfaceHal.groupAdd(netId, true);
+    }
+
+    /**
+     * Terminate a P2P group. If a new virtual network interface was used for
+     * the group, it must also be removed. The network interface name of the
+     * group interface is used as a parameter for this command.
+     *
+     * @param iface Group interface name to use.
+     * @return true, if operation was successful.
+     */
+    public boolean p2pGroupRemove(String iface) {
+        return mSupplicantP2pIfaceHal.groupRemove(iface);
+    }
+
+    /**
+     * Reject connection attempt from a peer (specified with a device
+     * address). This is a mechanism to reject a pending group owner negotiation
+     * with a peer and request to automatically block any further connection or
+     * discovery of the peer.
+     *
+     * @param deviceAddress MAC address of the device to reject.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pReject(String deviceAddress) {
+        return mSupplicantP2pIfaceHal.reject(deviceAddress);
+    }
+
+    /**
+     * Invite a device to a persistent group.
+     * If the peer device is the group owner of the persistent group, the peer
+     * parameter is not needed. Otherwise it is used to specify which
+     * device to invite. |goDeviceAddress| parameter may be used to override
+     * the group owner device address for Invitation Request should it not be
+     * known for some reason (this should not be needed in most cases).
+     *
+     * @param group Group object to use.
+     * @param deviceAddress MAC address of the device to invite.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pInvite(WifiP2pGroup group, String deviceAddress) {
+        return mSupplicantP2pIfaceHal.invite(group, deviceAddress);
+    }
+
+    /**
+     * Reinvoke a device from a persistent group.
+     *
+     * @param netId Used to specify the persistent group.
+     * @param deviceAddress MAC address of the device to reinvoke.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean p2pReinvoke(int netId, String deviceAddress) {
+        return mSupplicantP2pIfaceHal.reinvoke(netId, deviceAddress);
+    }
+
+    /**
+     * Gets the operational SSID of the device.
+     *
+     * @param deviceAddress MAC address of the peer.
+     *
+     * @return SSID of the device.
+     */
+    public String p2pGetSsid(String deviceAddress) {
+        return mSupplicantP2pIfaceHal.getSsid(deviceAddress);
+    }
+
+    /**
+     * Gets the MAC address of the device.
+     *
+     * @return MAC address of the device.
+     */
+    public String p2pGetDeviceAddress() {
+        return mSupplicantP2pIfaceHal.getDeviceAddress();
+    }
+
+    /**
+     * Gets the capability of the group which the device is a
+     * member of.
+     *
+     * @param deviceAddress MAC address of the peer.
+     *
+     * @return combination of |GroupCapabilityMask| values.
+     */
+    public int getGroupCapability(String deviceAddress) {
+        return mSupplicantP2pIfaceHal.getGroupCapability(deviceAddress);
+    }
+
+    /**
+     * This command can be used to add a upnp/bonjour service.
+     *
+     * @param servInfo List of service queries.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
+        return mSupplicantP2pIfaceHal.serviceAdd(servInfo);
+    }
+
+    /**
+     * This command can be used to remove a upnp/bonjour service.
+     *
+     * @param servInfo List of service queries.
+     *
+     * @return true, if operation was successful.
+     */
+    public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
+        return mSupplicantP2pIfaceHal.serviceRemove(servInfo);
+    }
+
+    /**
+     * This command can be used to flush all services from the
+     * device.
+     *
+     * @return boolean value indicating whether operation was successful.
+     */
+    public boolean p2pServiceFlush() {
+        return mSupplicantP2pIfaceHal.serviceFlush();
+    }
+
+    /**
+     * Schedule a P2P service discovery request. The parameters for this command
+     * are the device address of the peer device (or 00:00:00:00:00:00 for
+     * wildcard query that is sent to every discovered P2P peer that supports
+     * service discovery) and P2P Service Query TLV(s) as hexdump.
+     *
+     * @param addr MAC address of the device to discover.
+     * @param query Hex dump of the query data.
+     * @return identifier Identifier for the request. Can be used to cancel the
+     *         request.
+     */
+    public String p2pServDiscReq(String addr, String query) {
+        return mSupplicantP2pIfaceHal.requestServiceDiscovery(addr, query);
+    }
+
+    /**
+     * Cancel a previous service discovery request.
+     *
+     * @param id Identifier for the request to cancel.
+     * @return true, if operation was successful.
+     */
+    public boolean p2pServDiscCancelReq(String id) {
+        return mSupplicantP2pIfaceHal.cancelServiceDiscovery(id);
+    }
+
+    /**
+     * Send driver command to set Miracast mode.
+     *
+     * @param mode Mode of Miracast.
+     *        0 = disabled
+     *        1 = operating as source
+     *        2 = operating as sink
+     */
+    public void setMiracastMode(int mode) {
+        mSupplicantP2pIfaceHal.setMiracastMode(mode);
+    }
+
+    /**
+     * Get NFC handover request message.
+     *
+     * @return select message if created successfully, null otherwise.
+     */
+    public String getNfcHandoverRequest() {
+        return mSupplicantP2pIfaceHal.getNfcHandoverRequest();
+    }
+
+    /**
+     * Get NFC handover select message.
+     *
+     * @return select message if created successfully, null otherwise.
+     */
+    public String getNfcHandoverSelect() {
+        return mSupplicantP2pIfaceHal.getNfcHandoverSelect();
+    }
+
+    /**
+     * Report NFC handover select message.
+     *
+     * @return true if reported successfully, false otherwise.
+     */
+    public boolean initiatorReportNfcHandover(String selectMessage) {
+        return mSupplicantP2pIfaceHal.initiatorReportNfcHandover(selectMessage);
+    }
+
+    /**
+     * Report NFC handover request message.
+     *
+     * @return true if reported successfully, false otherwise.
+     */
+    public boolean responderReportNfcHandover(String requestMessage) {
+        return mSupplicantP2pIfaceHal.responderReportNfcHandover(requestMessage);
+    }
+
+    /**
+     * Set the client list for the provided network.
+     *
+     * @param netId Id of the network.
+     * @return  Space separated list of clients if successfull, null otherwise.
+     */
+    public String getP2pClientList(int netId) {
+        return mSupplicantP2pIfaceHal.getClientList(netId);
+    }
+
+    /**
+     * Set the client list for the provided network.
+     *
+     * @param netId Id of the network.
+     * @param list Space separated list of clients.
+     * @return true, if operation was successful.
+     */
+    public boolean setP2pClientList(int netId, String list) {
+        return mSupplicantP2pIfaceHal.setClientList(netId, list);
+    }
+
+    /**
+     * Save the current configuration to p2p_supplicant.conf.
+     *
+     * @return true on success, false otherwise.
+     */
+    public boolean saveConfig() {
+        return mSupplicantP2pIfaceHal.saveConfig();
+    }
+}
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pService.java b/service/java/com/android/server/wifi/p2p/WifiP2pService.java
index dacde3a..97c0189 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pService.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pService.java
@@ -18,8 +18,14 @@
 
 import android.content.Context;
 import android.util.Log;
+
 import com.android.server.SystemService;
 
+/**
+ * Wifi P2p Service class, instantiates P2p service
+ * Overrides onStart() and onBootPhase() methods in
+ * the super class.
+ */
 public final class WifiP2pService extends SystemService {
 
     private static final String TAG = "WifiP2pService";
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index c1d9445..623a2f0 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -48,8 +48,8 @@
 import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
 import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
@@ -61,6 +61,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.KeyEvent;
@@ -76,9 +77,12 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.server.wifi.WifiMonitor;
-import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiStateMachine;
+import com.android.server.wifi.util.WifiAsyncChannel;
+import com.android.server.wifi.util.WifiHandler;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -88,7 +92,6 @@
 import java.util.HashMap;
 import java.util.List;
 
-
 /**
  * WifiP2pService includes a state machine to perform Wi-Fi p2p operations. Applications
  * communicate with this service to issue device discovery and connectivity requests
@@ -111,8 +114,9 @@
     private DhcpResults mDhcpResults;
 
     private P2pStateMachine mP2pStateMachine;
-    private AsyncChannel mReplyChannel = new AsyncChannel();
+    private AsyncChannel mReplyChannel = new WifiAsyncChannel(TAG);
     private AsyncChannel mWifiChannel;
+    private WifiInjector mWifiInjector;
 
     private static final Boolean JOIN_GROUP = true;
     private static final Boolean FORM_GROUP = false;
@@ -120,50 +124,47 @@
     private static final Boolean RELOAD = true;
     private static final Boolean NO_RELOAD = false;
 
-    /* Two minutes comes from the wpa_supplicant setting */
+    // Two minutes comes from the wpa_supplicant setting
     private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000;
-    private static int mGroupCreatingTimeoutIndex = 0;
+    private static int sGroupCreatingTimeoutIndex = 0;
 
     private static final int DISABLE_P2P_WAIT_TIME_MS = 5 * 1000;
-    private static int mDisableP2pTimeoutIndex = 0;
+    private static int sDisableP2pTimeoutIndex = 0;
 
-    /* Set a two minute discover timeout to avoid STA scans from being blocked */
+    // Set a two minute discover timeout to avoid STA scans from being blocked
     private static final int DISCOVER_TIMEOUT_S = 120;
 
-    /* Idle time after a peer is gone when the group is torn down */
+    // Idle time after a peer is gone when the group is torn down
     private static final int GROUP_IDLE_TIME_S = 10;
 
     private static final int BASE = Protocol.BASE_WIFI_P2P_SERVICE;
 
-    /* Delayed message to timeout group creation */
+    // Delayed message to timeout group creation
     public static final int GROUP_CREATING_TIMED_OUT        =   BASE + 1;
 
-    /* User accepted a peer request */
+    // User accepted a peer request
     private static final int PEER_CONNECTION_USER_ACCEPT    =   BASE + 2;
-    /* User rejected a peer request */
+    // User rejected a peer request
     private static final int PEER_CONNECTION_USER_REJECT    =   BASE + 3;
-    /* User wants to disconnect wifi in favour of p2p */
+    // User wants to disconnect wifi in favour of p2p
     private static final int DROP_WIFI_USER_ACCEPT          =   BASE + 4;
-    /* User wants to keep his wifi connection and drop p2p */
+    // User wants to keep his wifi connection and drop p2p
     private static final int DROP_WIFI_USER_REJECT          =   BASE + 5;
-    /* Delayed message to timeout p2p disable */
+    // Delayed message to timeout p2p disable
     public static final int DISABLE_P2P_TIMED_OUT           =   BASE + 6;
 
 
-    /* Commands to the WifiStateMachine */
+    // Commands to the WifiStateMachine
     public static final int P2P_CONNECTION_CHANGED          =   BASE + 11;
 
-    /* These commands are used to temporarily disconnect wifi when we detect
-     * a frequency conflict which would make it impossible to have with p2p
-     * and wifi active at the same time.
-     *
-     * If the user chooses to disable wifi temporarily, we keep wifi disconnected
-     * until the p2p connection is done and terminated at which point we will
-     * bring back wifi up
-     *
-     * DISCONNECT_WIFI_REQUEST
-     *      msg.arg1 = 1 enables temporary disconnect and 0 disables it.
-     */
+    // These commands are used to temporarily disconnect wifi when we detect
+    // a frequency conflict which would make it impossible to have with p2p
+    // and wifi active at the same time.
+    // If the user chooses to disable wifi temporarily, we keep wifi disconnected
+    // until the p2p connection is done and terminated at which point we will
+    // bring back wifi up
+    // DISCONNECT_WIFI_REQUEST
+    //      msg.arg1 = 1 enables temporary disconnect and 0 disables it.
     public static final int DISCONNECT_WIFI_REQUEST         =   BASE + 12;
     public static final int DISCONNECT_WIFI_RESPONSE        =   BASE + 13;
 
@@ -190,120 +191,122 @@
 
     private WifiP2pDevice mThisDevice = new WifiP2pDevice();
 
-    /* When a group has been explicitly created by an app, we persist the group
-     * even after all clients have been disconnected until an explicit remove
-     * is invoked */
+    // When a group has been explicitly created by an app, we persist the group
+    // even after all clients have been disconnected until an explicit remove
+    // is invoked
     private boolean mAutonomousGroup;
 
-    /* Invitation to join an existing p2p group */
+    // Invitation to join an existing p2p group
     private boolean mJoinExistingGroup;
 
-    /* Track whether we are in p2p discovery. This is used to avoid sending duplicate
-     * broadcasts
-     */
+    // Track whether we are in p2p discovery. This is used to avoid sending duplicate
+    // broadcasts
     private boolean mDiscoveryStarted;
-    /* Track whether servcice/peer discovery is blocked in favor of other wifi actions
-     * (notably dhcp)
-     */
+
+    // Track whether servcice/peer discovery is blocked in favor of other wifi actions
+    // (notably dhcp)
     private boolean mDiscoveryBlocked;
 
-    /*
-     * remember if we were in a scan when it had to be stopped
-     */
+    // remember if we were in a scan when it had to be stopped
     private boolean mDiscoveryPostponed = false;
 
     private NetworkInfo mNetworkInfo;
 
     private boolean mTemporarilyDisconnectedWifi = false;
 
-    /* The transaction Id of service discovery request */
+    // The transaction Id of service discovery request
     private byte mServiceTransactionId = 0;
 
-    /* Service discovery request ID of wpa_supplicant.
-     * null means it's not set yet. */
+    // Service discovery request ID of wpa_supplicant.
+    // null means it's not set yet.
     private String mServiceDiscReqId;
 
-    /* clients(application) information list. */
+    // clients(application) information list
     private HashMap<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>();
 
-    /* Is chosen as a unique address to avoid conflict with
-       the ranges defined in Tethering.java */
+    // Is chosen as a unique address to avoid conflict with
+    // the ranges defined in Tethering.java
     private static final String SERVER_ADDRESS = "192.168.49.1";
 
     /**
      * Error code definition.
      * see the Table.8 in the WiFi Direct specification for the detail.
      */
-    public static enum P2pStatus {
-        /* Success. */
+    public enum P2pStatus {
+        // Success
         SUCCESS,
 
-        /* The target device is currently unavailable. */
+        // The target device is currently unavailable
         INFORMATION_IS_CURRENTLY_UNAVAILABLE,
 
-        /* Protocol error. */
+        // Protocol error
         INCOMPATIBLE_PARAMETERS,
 
-        /* The target device reached the limit of the number of the connectable device.
-         * For example, device limit or group limit is set. */
+        // The target device reached the limit of the number of the connectable device.
+        // For example, device limit or group limit is set
         LIMIT_REACHED,
 
-        /* Protocol error. */
+        // Protocol error
         INVALID_PARAMETER,
 
-        /* Unable to accommodate request. */
+        // Unable to accommodate request
         UNABLE_TO_ACCOMMODATE_REQUEST,
 
-        /* Previous protocol error, or disruptive behavior. */
+        // Previous protocol error, or disruptive behavior
         PREVIOUS_PROTOCOL_ERROR,
 
-        /* There is no common channels the both devices can use. */
+        // There is no common channels the both devices can use
         NO_COMMON_CHANNEL,
 
-        /* Unknown p2p group. For example, Device A tries to invoke the previous persistent group,
-         *  but device B has removed the specified credential already. */
+        // Unknown p2p group. For example, Device A tries to invoke the previous persistent group,
+        // but device B has removed the specified credential already
         UNKNOWN_P2P_GROUP,
 
-        /* Both p2p devices indicated an intent of 15 in group owner negotiation. */
+        // Both p2p devices indicated an intent of 15 in group owner negotiation
         BOTH_GO_INTENT_15,
 
-        /* Incompatible provisioning method. */
+        // Incompatible provisioning method
         INCOMPATIBLE_PROVISIONING_METHOD,
 
-        /* Rejected by user */
+        // Rejected by user
         REJECTED_BY_USER,
 
-        /* Unknown error */
+        // Unknown error
         UNKNOWN;
 
+        /**
+         * Returns P2p status corresponding to a given error value
+         * @param error integer error value
+         * @return P2pStatus enum for value
+         */
         public static P2pStatus valueOf(int error) {
             switch(error) {
-            case 0 :
-                return SUCCESS;
-            case 1:
-                return INFORMATION_IS_CURRENTLY_UNAVAILABLE;
-            case 2:
-                return INCOMPATIBLE_PARAMETERS;
-            case 3:
-                return LIMIT_REACHED;
-            case 4:
-                return INVALID_PARAMETER;
-            case 5:
-                return UNABLE_TO_ACCOMMODATE_REQUEST;
-            case 6:
-                return PREVIOUS_PROTOCOL_ERROR;
-            case 7:
-                return NO_COMMON_CHANNEL;
-            case 8:
-                return UNKNOWN_P2P_GROUP;
-            case 9:
-                return BOTH_GO_INTENT_15;
-            case 10:
-                return INCOMPATIBLE_PROVISIONING_METHOD;
-            case 11:
-                return REJECTED_BY_USER;
-            default:
-                return UNKNOWN;
+                case 0 :
+                    return SUCCESS;
+                case 1:
+                    return INFORMATION_IS_CURRENTLY_UNAVAILABLE;
+                case 2:
+                    return INCOMPATIBLE_PARAMETERS;
+                case 3:
+                    return LIMIT_REACHED;
+                case 4:
+                    return INVALID_PARAMETER;
+                case 5:
+                    return UNABLE_TO_ACCOMMODATE_REQUEST;
+                case 6:
+                    return PREVIOUS_PROTOCOL_ERROR;
+                case 7:
+                    return NO_COMMON_CHANNEL;
+                case 8:
+                    return UNKNOWN_P2P_GROUP;
+                case 9:
+                    return BOTH_GO_INTENT_15;
+                case 10:
+                    return INCOMPATIBLE_PROVISIONING_METHOD;
+                case 11:
+                    return REJECTED_BY_USER;
+                default:
+                    return UNKNOWN;
             }
         }
     }
@@ -311,44 +314,45 @@
     /**
      * Handles client connections
      */
-    private class ClientHandler extends Handler {
+    private class ClientHandler extends WifiHandler {
 
-        ClientHandler(android.os.Looper looper) {
-            super(looper);
+        ClientHandler(String tag, android.os.Looper looper) {
+            super(tag, looper);
         }
 
         @Override
         public void handleMessage(Message msg) {
+            super.handleMessage(msg);
             switch (msg.what) {
-              case WifiP2pManager.SET_DEVICE_NAME:
-              case WifiP2pManager.SET_WFD_INFO:
-              case WifiP2pManager.DISCOVER_PEERS:
-              case WifiP2pManager.STOP_DISCOVERY:
-              case WifiP2pManager.CONNECT:
-              case WifiP2pManager.CANCEL_CONNECT:
-              case WifiP2pManager.CREATE_GROUP:
-              case WifiP2pManager.REMOVE_GROUP:
-              case WifiP2pManager.START_LISTEN:
-              case WifiP2pManager.STOP_LISTEN:
-              case WifiP2pManager.SET_CHANNEL:
-              case WifiP2pManager.START_WPS:
-              case WifiP2pManager.ADD_LOCAL_SERVICE:
-              case WifiP2pManager.REMOVE_LOCAL_SERVICE:
-              case WifiP2pManager.CLEAR_LOCAL_SERVICES:
-              case WifiP2pManager.DISCOVER_SERVICES:
-              case WifiP2pManager.ADD_SERVICE_REQUEST:
-              case WifiP2pManager.REMOVE_SERVICE_REQUEST:
-              case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
-              case WifiP2pManager.REQUEST_PEERS:
-              case WifiP2pManager.REQUEST_CONNECTION_INFO:
-              case WifiP2pManager.REQUEST_GROUP_INFO:
-              case WifiP2pManager.DELETE_PERSISTENT_GROUP:
-              case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
-                mP2pStateMachine.sendMessage(Message.obtain(msg));
-                break;
-              default:
-                Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
-                break;
+                case WifiP2pManager.SET_DEVICE_NAME:
+                case WifiP2pManager.SET_WFD_INFO:
+                case WifiP2pManager.DISCOVER_PEERS:
+                case WifiP2pManager.STOP_DISCOVERY:
+                case WifiP2pManager.CONNECT:
+                case WifiP2pManager.CANCEL_CONNECT:
+                case WifiP2pManager.CREATE_GROUP:
+                case WifiP2pManager.REMOVE_GROUP:
+                case WifiP2pManager.START_LISTEN:
+                case WifiP2pManager.STOP_LISTEN:
+                case WifiP2pManager.SET_CHANNEL:
+                case WifiP2pManager.START_WPS:
+                case WifiP2pManager.ADD_LOCAL_SERVICE:
+                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+                case WifiP2pManager.DISCOVER_SERVICES:
+                case WifiP2pManager.ADD_SERVICE_REQUEST:
+                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+                case WifiP2pManager.REQUEST_PEERS:
+                case WifiP2pManager.REQUEST_CONNECTION_INFO:
+                case WifiP2pManager.REQUEST_GROUP_INFO:
+                case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+                case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
+                    mP2pStateMachine.sendMessage(Message.obtain(msg));
+                    break;
+                default:
+                    Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
+                    break;
             }
         }
     }
@@ -367,12 +371,14 @@
 
         HandlerThread wifiP2pThread = new HandlerThread("WifiP2pService");
         wifiP2pThread.start();
-        mClientHandler = new ClientHandler(wifiP2pThread.getLooper());
-
+        mClientHandler = new ClientHandler(TAG, wifiP2pThread.getLooper());
         mP2pStateMachine = new P2pStateMachine(TAG, wifiP2pThread.getLooper(), mP2pSupported);
         mP2pStateMachine.start();
     }
 
+    /**
+     * Obtains the service interface for Managements services
+     */
     public void connectivityServiceReady() {
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         mNwService = INetworkManagementService.Stub.asInterface(b);
@@ -461,6 +467,7 @@
      * Get a reference to handler. This is used by a client to establish
      * an AsyncChannel communication with WifiP2pService
      */
+    @Override
     public Messenger getMessenger() {
         enforceAccessPermission();
         enforceChangePermission();
@@ -472,6 +479,7 @@
      * an AsyncChannel communication with P2pStateMachine
      * @hide
      */
+    @Override
     public Messenger getP2pStateMachineMessenger() {
         enforceConnectivityInternalOrLocationHardwarePermission();
         enforceAccessPermission();
@@ -487,13 +495,34 @@
      *
      * As an example, the driver could reduce the channel dwell time during scanning
      * when acting as a source or sink to minimize impact on miracast.
+     * @param int mode of operation
      */
+    @Override
     public void setMiracastMode(int mode) {
         enforceConnectivityInternalPermission();
+        checkConfigureWifiDisplayPermission();
         mP2pStateMachine.sendMessage(SET_MIRACAST_MODE, mode);
     }
 
     @Override
+    public void checkConfigureWifiDisplayPermission() {
+        if (!getWfdPermission(Binder.getCallingUid())) {
+            throw new SecurityException("Wifi Display Permission denied for uid = "
+                    + Binder.getCallingUid());
+        }
+    }
+
+    private boolean getWfdPermission(int uid) {
+        if (mWifiInjector == null) {
+            mWifiInjector = WifiInjector.getInstance();
+        }
+        WifiPermissionsWrapper wifiPermissionsWrapper = mWifiInjector.getWifiPermissionsWrapper();
+        return wifiPermissionsWrapper.getUidPermission(
+                android.Manifest.permission.CONFIGURE_WIFI_DISPLAY, uid)
+                != PackageManager.PERMISSION_DENIED;
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -533,10 +562,10 @@
         // Inactive is when p2p is enabled with no connectivity
         private InactiveState mInactiveState = new InactiveState();
         private GroupCreatingState mGroupCreatingState = new GroupCreatingState();
-        private UserAuthorizingInviteRequestState mUserAuthorizingInviteRequestState
-                = new UserAuthorizingInviteRequestState();
-        private UserAuthorizingNegotiationRequestState mUserAuthorizingNegotiationRequestState
-                = new UserAuthorizingNegotiationRequestState();
+        private UserAuthorizingInviteRequestState mUserAuthorizingInviteRequestState =
+                new UserAuthorizingInviteRequestState();
+        private UserAuthorizingNegotiationRequestState mUserAuthorizingNegotiationRequestState =
+                new UserAuthorizingNegotiationRequestState();
         private ProvisionDiscoveryState mProvisionDiscoveryState = new ProvisionDiscoveryState();
         private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState();
         private FrequencyConflictState mFrequencyConflictState = new FrequencyConflictState();
@@ -545,28 +574,28 @@
         private UserAuthorizingJoinState mUserAuthorizingJoinState = new UserAuthorizingJoinState();
         private OngoingGroupRemovalState mOngoingGroupRemovalState = new OngoingGroupRemovalState();
 
-        private WifiNative mWifiNative = WifiNative.getP2pNativeInterface();
-        private WifiMonitor mWifiMonitor = WifiMonitor.getInstance();
+        private WifiP2pNative mWifiNative = WifiInjector.getInstance().getWifiP2pNative();
+        private WifiP2pMonitor mWifiMonitor = WifiInjector.getInstance().getWifiP2pMonitor();
         private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
-        /* During a connection, supplicant can tell us that a device was lost. From a supplicant's
-         * perspective, the discovery stops during connection and it purges device since it does
-         * not get latest updates about the device without being in discovery state.
-         *
-         * From the framework perspective, the device is still there since we are connecting or
-         * connected to it. so we keep these devices in a separate list, so that they are removed
-         * when connection is cancelled or lost
-         */
+        // WifiInjector is lazy initialized in P2p Service
+        private WifiInjector mWifiInjector;
+        // During a connection, supplicant can tell us that a device was lost. From a supplicant's
+        // perspective, the discovery stops during connection and it purges device since it does
+        // not get latest updates about the device without being in discovery state.
+        // From the framework perspective, the device is still there since we are connecting or
+        // connected to it. so we keep these devices in a separate list, so that they are removed
+        // when connection is cancelled or lost
         private final WifiP2pDeviceList mPeersLostDuringConnection = new WifiP2pDeviceList();
         private final WifiP2pGroupList mGroups = new WifiP2pGroupList(null,
                 new GroupDeleteListener() {
-            @Override
-            public void onDeleteGroup(int netId) {
-                if (DBG) logd("called onDeleteGroup() netId=" + netId);
-                mWifiNative.removeNetwork(netId);
-                mWifiNative.saveConfig();
-                sendP2pPersistentGroupsChangedBroadcast();
-            }
-        });
+                    @Override
+                    public void onDeleteGroup(int netId) {
+                        if (DBG) logd("called onDeleteGroup() netId=" + netId);
+                        mWifiNative.removeP2pNetwork(netId);
+                        mWifiNative.saveConfig();
+                        sendP2pPersistentGroupsChangedBroadcast();
+                    }
+                });
         private final WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
         private WifiP2pGroup mGroup;
 
@@ -578,6 +607,7 @@
         P2pStateMachine(String name, Looper looper, boolean p2pSupported) {
             super(name, looper);
 
+            // CHECKSTYLE:OFF IndentationCheck
             addState(mDefaultState);
                 addState(mP2pNotSupportedState, mDefaultState);
                 addState(mP2pDisablingState, mDefaultState);
@@ -594,6 +624,7 @@
                     addState(mGroupCreatedState, mP2pEnabledState);
                         addState(mUserAuthorizingJoinState, mGroupCreatedState);
                         addState(mOngoingGroupRemovalState, mGroupCreatedState);
+            // CHECKSTYLE:ON IndentationCheck
 
             if (p2pSupported) {
                 setInitialState(mP2pDisabledState);
@@ -602,2611 +633,2730 @@
             }
             setLogRecSize(50);
             setLogOnlyTransitions(true);
-
             String interfaceName = mWifiNative.getInterfaceName();
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.AP_STA_CONNECTED_EVENT, getHandler());
+                    WifiP2pMonitor.AP_STA_CONNECTED_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.AP_STA_DISCONNECTED_EVENT, getHandler());
+                    WifiP2pMonitor.AP_STA_DISCONNECTED_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.AUTHENTICATION_FAILURE_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_DEVICE_FOUND_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.NETWORK_CONNECTION_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_DEVICE_LOST_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.NETWORK_DISCONNECTION_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_FIND_STOPPED_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_DEVICE_FOUND_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_DEVICE_LOST_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_FIND_STOPPED_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_GROUP_STARTED_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_INVITATION_RECEIVED_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_GROUP_REMOVED_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_INVITATION_RESULT_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_GROUP_STARTED_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_INVITATION_RECEIVED_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_INVITATION_RESULT_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_PROV_DISC_PBC_RSP_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT, getHandler());
+                    WifiP2pMonitor.P2P_SERV_DISC_RESP_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_PROV_DISC_PBC_RSP_EVENT, getHandler());
+                    WifiP2pMonitor.SUP_CONNECTION_EVENT, getHandler());
             mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.P2P_SERV_DISC_RESP_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.SCAN_RESULTS_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.SUP_CONNECTION_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.SUP_DISCONNECTION_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.WPS_FAIL_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.WPS_OVERLAP_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.WPS_SUCCESS_EVENT, getHandler());
-            mWifiMonitor.registerHandler(interfaceName,
-                    WifiMonitor.WPS_TIMEOUT_EVENT, getHandler());
+                    WifiP2pMonitor.SUP_DISCONNECTION_EVENT, getHandler());
         }
 
-    class DefaultState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        if (DBG) logd("Full connection with WifiStateMachine established");
-                        mWifiChannel = (AsyncChannel) message.obj;
-                    } else {
-                        loge("Full connection failure, error = " + message.arg1);
+        class DefaultState extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                        if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                            if (DBG) logd("Full connection with WifiStateMachine established");
+                            mWifiChannel = (AsyncChannel) message.obj;
+                        } else {
+                            loge("Full connection failure, error = " + message.arg1);
+                            mWifiChannel = null;
+                            transitionTo(mP2pDisabledState);
+                        }
+                        break;
+
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                        if (message.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+                            loge("Send failed, client connection lost");
+                        } else {
+                            loge("Client connection lost with reason: " + message.arg1);
+                        }
                         mWifiChannel = null;
-                    }
-                    break;
-
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    if (message.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
-                        loge("Send failed, client connection lost");
-                    } else {
-                        loge("Client connection lost with reason: " + message.arg1);
-                    }
-                    mWifiChannel = null;
-                    break;
-
-                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
-                    AsyncChannel ac = new AsyncChannel();
-                    ac.connect(mContext, getHandler(), message.replyTo);
-                    break;
-                case BLOCK_DISCOVERY:
-                    mDiscoveryBlocked = (message.arg1 == ENABLED ? true : false);
-                    // always reset this - we went to a state that doesn't support discovery so
-                    // it would have stopped regardless
-                    mDiscoveryPostponed = false;
-                    if (mDiscoveryBlocked) {
-                        try {
-                            StateMachine m = (StateMachine)message.obj;
-                            m.sendMessage(message.arg2);
-                        } catch (Exception e) {
-                            loge("unable to send BLOCK_DISCOVERY response: " + e);
-                        }
-                    }
-                    break;
-                case WifiP2pManager.DISCOVER_PEERS:
-                    replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.STOP_DISCOVERY:
-                    replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.DISCOVER_SERVICES:
-                    replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.CONNECT:
-                    replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.CANCEL_CONNECT:
-                    replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.CREATE_GROUP:
-                    replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.REMOVE_GROUP:
-                    replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.ADD_LOCAL_SERVICE:
-                    replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
-                    replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
-                    replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.ADD_SERVICE_REQUEST:
-                    replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
-                    replyToMessage(message,
-                            WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
-                    replyToMessage(message,
-                            WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.SET_DEVICE_NAME:
-                    replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.DELETE_PERSISTENT_GROUP:
-                    replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.SET_WFD_INFO:
-                    replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.REQUEST_PEERS:
-                    replyToMessage(message, WifiP2pManager.RESPONSE_PEERS,
-                            new WifiP2pDeviceList(mPeers));
-                    break;
-                case WifiP2pManager.REQUEST_CONNECTION_INFO:
-                    replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO,
-                            new WifiP2pInfo(mWifiP2pInfo));
-                    break;
-                case WifiP2pManager.REQUEST_GROUP_INFO:
-                    replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO,
-                            mGroup != null ? new WifiP2pGroup(mGroup) : null);
-                    break;
-                case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
-                    replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
-                            new WifiP2pGroupList(mGroups, null));
-                    break;
-                case WifiP2pManager.START_WPS:
-                    replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
-                        WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.GET_HANDOVER_REQUEST:
-                case WifiP2pManager.GET_HANDOVER_SELECT:
-                    replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE, null);
-                    break;
-                case WifiP2pManager.INITIATOR_REPORT_NFC_HANDOVER:
-                case WifiP2pManager.RESPONDER_REPORT_NFC_HANDOVER:
-                    replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                    // Ignore
-                case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
-                case WifiMonitor.SCAN_RESULTS_EVENT:
-                case WifiMonitor.SUP_CONNECTION_EVENT:
-                case WifiMonitor.SUP_DISCONNECTION_EVENT:
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
-                case WifiMonitor.WPS_SUCCESS_EVENT:
-                case WifiMonitor.WPS_FAIL_EVENT:
-                case WifiMonitor.WPS_OVERLAP_EVENT:
-                case WifiMonitor.WPS_TIMEOUT_EVENT:
-                case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
-                case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
-                case WifiMonitor.P2P_DEVICE_LOST_EVENT:
-                case WifiMonitor.P2P_FIND_STOPPED_EVENT:
-                case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
-                case PEER_CONNECTION_USER_ACCEPT:
-                case PEER_CONNECTION_USER_REJECT:
-                case DISCONNECT_WIFI_RESPONSE:
-                case DROP_WIFI_USER_ACCEPT:
-                case DROP_WIFI_USER_REJECT:
-                case GROUP_CREATING_TIMED_OUT:
-                case DISABLE_P2P_TIMED_OUT:
-                case IPM_PRE_DHCP_ACTION:
-                case IPM_POST_DHCP_ACTION:
-                case IPM_DHCP_RESULTS:
-                case IPM_PROVISIONING_SUCCESS:
-                case IPM_PROVISIONING_FAILURE:
-                case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
-                case SET_MIRACAST_MODE:
-                case WifiP2pManager.START_LISTEN:
-                case WifiP2pManager.STOP_LISTEN:
-                case WifiP2pManager.SET_CHANNEL:
-                case WifiStateMachine.CMD_ENABLE_P2P:
-                    // Enable is lazy and has no response
-                    break;
-                case WifiStateMachine.CMD_DISABLE_P2P_REQ:
-                    // If we end up handling in default, p2p is not enabled
-                    mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
-                    break;
-                    /* unexpected group created, remove */
-                case WifiMonitor.P2P_GROUP_STARTED_EVENT:
-                    mGroup = (WifiP2pGroup) message.obj;
-                    loge("Unexpected group creation, remove " + mGroup);
-                    mWifiNative.p2pGroupRemove(mGroup.getInterface());
-                    break;
-                // A group formation failure is always followed by
-                // a group removed event. Flushing things at group formation
-                // failure causes supplicant issues. Ignore right now.
-                case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
-                    break;
-                default:
-                    loge("Unhandled message " + message);
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class P2pNotSupportedState extends State {
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-               case WifiP2pManager.DISCOVER_PEERS:
-                    replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.STOP_DISCOVERY:
-                    replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.DISCOVER_SERVICES:
-                    replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.CONNECT:
-                    replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.CANCEL_CONNECT:
-                    replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-               case WifiP2pManager.CREATE_GROUP:
-                    replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.REMOVE_GROUP:
-                    replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.ADD_LOCAL_SERVICE:
-                    replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
-                    replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
-                    replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.ADD_SERVICE_REQUEST:
-                    replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
-                    replyToMessage(message,
-                            WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
-                    replyToMessage(message,
-                            WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.SET_DEVICE_NAME:
-                    replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.DELETE_PERSISTENT_GROUP:
-                    replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.SET_WFD_INFO:
-                    replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.START_WPS:
-                    replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.START_LISTEN:
-                    replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-                case WifiP2pManager.STOP_LISTEN:
-                    replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED,
-                            WifiP2pManager.P2P_UNSUPPORTED);
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class P2pDisablingState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            sendMessageDelayed(obtainMessage(DISABLE_P2P_TIMED_OUT,
-                    ++mDisableP2pTimeoutIndex, 0), DISABLE_P2P_WAIT_TIME_MS);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiMonitor.SUP_DISCONNECTION_EVENT:
-                    if (DBG) logd("p2p socket connection lost");
-                    transitionTo(mP2pDisabledState);
-                    break;
-                case WifiStateMachine.CMD_ENABLE_P2P:
-                case WifiStateMachine.CMD_DISABLE_P2P_REQ:
-                    deferMessage(message);
-                    break;
-                case DISABLE_P2P_TIMED_OUT:
-                    if (mDisableP2pTimeoutIndex == message.arg1) {
-                        loge("P2p disable timed out");
                         transitionTo(mP2pDisabledState);
-                    }
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
+                        break;
 
-        @Override
-        public void exit() {
-            mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
-        }
-    }
-
-    class P2pDisabledState extends State {
-       @Override
-        public void enter() {
-            if (DBG) logd(getName());
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiStateMachine.CMD_ENABLE_P2P:
-                    try {
-                        mNwService.setInterfaceUp(mWifiNative.getInterfaceName());
-                    } catch (RemoteException re) {
-                        loge("Unable to change interface settings: " + re);
-                    } catch (IllegalStateException ie) {
-                        loge("Unable to change interface settings: " + ie);
-                    }
-                    mWifiMonitor.startMonitoring(mWifiNative.getInterfaceName());
-                    transitionTo(mP2pEnablingState);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class P2pEnablingState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiMonitor.SUP_CONNECTION_EVENT:
-                    if (DBG) logd("P2p socket connection successful");
-                    transitionTo(mInactiveState);
-                    break;
-                case WifiMonitor.SUP_DISCONNECTION_EVENT:
-                    loge("P2p socket connection failed");
-                    transitionTo(mP2pDisabledState);
-                    break;
-                case WifiStateMachine.CMD_ENABLE_P2P:
-                case WifiStateMachine.CMD_DISABLE_P2P_REQ:
-                    deferMessage(message);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class P2pEnabledState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            sendP2pStateChangedBroadcast(true);
-            mNetworkInfo.setIsAvailable(true);
-            sendP2pConnectionChangedBroadcast();
-            initializeP2pSettings();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiMonitor.SUP_DISCONNECTION_EVENT:
-                    loge("Unexpected loss of p2p socket connection");
-                    transitionTo(mP2pDisabledState);
-                    break;
-                case WifiStateMachine.CMD_ENABLE_P2P:
-                    //Nothing to do
-                    break;
-                case WifiStateMachine.CMD_DISABLE_P2P_REQ:
-                    if (mPeers.clear()) {
-                        sendPeersChangedBroadcast();
-                    }
-                    if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast();
-
-                    mWifiMonitor.stopMonitoring(mWifiNative.getInterfaceName());
-                    transitionTo(mP2pDisablingState);
-                    break;
-                case WifiP2pManager.SET_DEVICE_NAME:
-                {
-                    WifiP2pDevice d = (WifiP2pDevice) message.obj;
-                    if (d != null && setAndPersistDeviceName(d.deviceName)) {
-                        if (DBG) logd("set device name " + d.deviceName);
-                        replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    break;
-                }
-                case WifiP2pManager.SET_WFD_INFO:
-                {
-                    WifiP2pWfdInfo d = (WifiP2pWfdInfo) message.obj;
-                    if (d != null && setWfdInfo(d)) {
-                        replyToMessage(message, WifiP2pManager.SET_WFD_INFO_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    break;
-                }
-                case BLOCK_DISCOVERY:
-                    boolean blocked = (message.arg1 == ENABLED ? true : false);
-                    if (mDiscoveryBlocked == blocked) break;
-                    mDiscoveryBlocked = blocked;
-                    if (blocked && mDiscoveryStarted) {
-                        mWifiNative.p2pStopFind();
-                        mDiscoveryPostponed = true;
-                    }
-                    if (!blocked && mDiscoveryPostponed) {
+                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+                        AsyncChannel ac = new WifiAsyncChannel(TAG);
+                        ac.connect(mContext, getHandler(), message.replyTo);
+                        break;
+                    case BLOCK_DISCOVERY:
+                        mDiscoveryBlocked = (message.arg1 == ENABLED ? true : false);
+                        // always reset this - we went to a state that doesn't support discovery so
+                        // it would have stopped regardless
                         mDiscoveryPostponed = false;
-                        mWifiNative.p2pFind(DISCOVER_TIMEOUT_S);
-                    }
-                    if (blocked) {
-                        try {
-                            StateMachine m = (StateMachine)message.obj;
-                            m.sendMessage(message.arg2);
-                        } catch (Exception e) {
-                            loge("unable to send BLOCK_DISCOVERY response: " + e);
+                        if (mDiscoveryBlocked) {
+                            if (message.obj == null) {
+                                Log.e(TAG, "Illegal argument(s)");
+                                break;
+                            }
+                            StateMachine m = (StateMachine) message.obj;
+                            try {
+                                m.sendMessage(message.arg2);
+                            } catch (Exception e) {
+                                loge("unable to send BLOCK_DISCOVERY response: " + e);
+                            }
                         }
-                    }
-                    break;
-                case WifiP2pManager.DISCOVER_PEERS:
-                    if (mDiscoveryBlocked) {
+                        break;
+                    case WifiP2pManager.DISCOVER_PEERS:
                         replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
                                 WifiP2pManager.BUSY);
                         break;
-                    }
-                    // do not send service discovery request while normal find operation.
-                    clearSupplicantServiceRequest();
-                    if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
-                        replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
-                        sendP2pDiscoveryChangedBroadcast(true);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    break;
-                case WifiMonitor.P2P_FIND_STOPPED_EVENT:
-                    sendP2pDiscoveryChangedBroadcast(false);
-                    break;
-                case WifiP2pManager.STOP_DISCOVERY:
-                    if (mWifiNative.p2pStopFind()) {
-                        replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
-                    } else {
+                    case WifiP2pManager.STOP_DISCOVERY:
                         replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    break;
-                case WifiP2pManager.DISCOVER_SERVICES:
-                    if (mDiscoveryBlocked) {
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.DISCOVER_SERVICES:
                         replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
                                 WifiP2pManager.BUSY);
                         break;
-                    }
-                    if (DBG) logd(getName() + " discover services");
-                    if (!updateSupplicantServiceRequest()) {
-                        replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
-                                WifiP2pManager.NO_SERVICE_REQUESTS);
+                    case WifiP2pManager.CONNECT:
+                        replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+                                WifiP2pManager.BUSY);
                         break;
-                    }
-                    if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
-                        replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    break;
-                case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
-                    WifiP2pDevice device = (WifiP2pDevice) message.obj;
-                    if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
-                    mPeers.updateSupplicantDetails(device);
-                    sendPeersChangedBroadcast();
-                    break;
-                case WifiMonitor.P2P_DEVICE_LOST_EVENT:
-                    device = (WifiP2pDevice) message.obj;
-                    // Gets current details for the one removed
-                    device = mPeers.remove(device.deviceAddress);
-                    if (device != null) {
-                        sendPeersChangedBroadcast();
-                    }
-                    break;
-                case WifiP2pManager.ADD_LOCAL_SERVICE:
-                    if (DBG) logd(getName() + " add service");
-                    WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj;
-                    if (addLocalService(message.replyTo, servInfo)) {
-                        replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
-                    }
-                    break;
-                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
-                    if (DBG) logd(getName() + " remove service");
-                    servInfo = (WifiP2pServiceInfo)message.obj;
-                    removeLocalService(message.replyTo, servInfo);
-                    replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED);
-                    break;
-                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
-                    if (DBG) logd(getName() + " clear service");
-                    clearLocalServices(message.replyTo);
-                    replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED);
-                    break;
-                case WifiP2pManager.ADD_SERVICE_REQUEST:
-                    if (DBG) logd(getName() + " add service request");
-                    if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) {
-                        replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED);
+                    case WifiP2pManager.CANCEL_CONNECT:
+                        replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
+                                WifiP2pManager.BUSY);
                         break;
-                    }
-                    replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED);
-                    break;
-                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
-                    if (DBG) logd(getName() + " remove service request");
-                    removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj);
-                    replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED);
-                    break;
-                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
-                    if (DBG) logd(getName() + " clear service request");
-                    clearServiceRequests(message.replyTo);
-                    replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
-                    break;
-                case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
-                    if (DBG) logd(getName() + " receive service response");
-                    List<WifiP2pServiceResponse> sdRespList =
-                        (List<WifiP2pServiceResponse>) message.obj;
-                    for (WifiP2pServiceResponse resp : sdRespList) {
-                        WifiP2pDevice dev =
-                            mPeers.get(resp.getSrcDevice().deviceAddress);
-                        resp.setSrcDevice(dev);
-                        sendServiceResponse(resp);
-                    }
-                    break;
-                case WifiP2pManager.DELETE_PERSISTENT_GROUP:
-                   if (DBG) logd(getName() + " delete persistent group");
-                   mGroups.remove(message.arg1);
-                   replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED);
-                   break;
-                case SET_MIRACAST_MODE:
-                    mWifiNative.setMiracastMode(message.arg1);
-                    break;
-                case WifiP2pManager.START_LISTEN:
-                    if (DBG) logd(getName() + " start listen mode");
-                    mWifiNative.p2pFlush();
-                    if (mWifiNative.p2pExtListen(true, 500, 500)) {
-                        replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
-                    }
-                    break;
-                case WifiP2pManager.STOP_LISTEN:
-                    if (DBG) logd(getName() + " stop listen mode");
-                    if (mWifiNative.p2pExtListen(false, 0, 0)) {
-                        replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
-                    }
-                    mWifiNative.p2pFlush();
-                    break;
-                case WifiP2pManager.SET_CHANNEL:
-                    Bundle p2pChannels = (Bundle) message.obj;
-                    int lc = p2pChannels.getInt("lc", 0);
-                    int oc = p2pChannels.getInt("oc", 0);
-                    if (DBG) logd(getName() + " set listen and operating channel");
-                    if (mWifiNative.p2pSetChannel(lc, oc)) {
-                        replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
-                    }
-                    break;
-                case WifiP2pManager.GET_HANDOVER_REQUEST:
-                    Bundle requestBundle = new Bundle();
-                    requestBundle.putString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE,
-                            mWifiNative.getNfcHandoverRequest());
-                    replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE,
-                            requestBundle);
-                    break;
-                case WifiP2pManager.GET_HANDOVER_SELECT:
-                    Bundle selectBundle = new Bundle();
-                    selectBundle.putString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE,
-                            mWifiNative.getNfcHandoverSelect());
-                    replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE,
-                            selectBundle);
-                    break;
-                default:
-                   return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-
-        @Override
-        public void exit() {
-            sendP2pDiscoveryChangedBroadcast(false);
-            sendP2pStateChangedBroadcast(false);
-            mNetworkInfo.setIsAvailable(false);
-        }
-    }
-
-    class InactiveState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            mSavedPeerConfig.invalidate();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiP2pManager.CONNECT:
-                    if (DBG) logd(getName() + " sending connect");
-                    WifiP2pConfig config = (WifiP2pConfig) message.obj;
-                    if (isConfigInvalid(config)) {
-                        loge("Dropping connect requeset " + config);
-                        replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
-                        break;
-                    }
-
-                    mAutonomousGroup = false;
-                    mWifiNative.p2pStopFind();
-                    if (reinvokePersistentGroup(config)) {
-                        transitionTo(mGroupNegotiationState);
-                    } else {
-                        transitionTo(mProvisionDiscoveryState);
-                    }
-                    mSavedPeerConfig = config;
-                    mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
-                    sendPeersChangedBroadcast();
-                    replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
-                    break;
-                case WifiP2pManager.STOP_DISCOVERY:
-                    if (mWifiNative.p2pStopFind()) {
-                        // When discovery stops in inactive state, flush to clear
-                        // state peer data
-                        mWifiNative.p2pFlush();
-                        mServiceDiscReqId = null;
-                        replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    break;
-                case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
-                    config = (WifiP2pConfig) message.obj;
-                    if (isConfigInvalid(config)) {
-                        loge("Dropping GO neg request " + config);
-                        break;
-                    }
-                    mSavedPeerConfig = config;
-                    mAutonomousGroup = false;
-                    mJoinExistingGroup = false;
-                    transitionTo(mUserAuthorizingNegotiationRequestState);
-                    break;
-                case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
-                    WifiP2pGroup group = (WifiP2pGroup) message.obj;
-                    WifiP2pDevice owner = group.getOwner();
-
-                    if (owner == null) {
-                        int id = group.getNetworkId();
-                        if (id < 0) {
-                            loge("Ignored invitation from null owner");
-                            break;
-                        }
-
-                        String addr = mGroups.getOwnerAddr(id);
-                        if (addr != null) {
-                            group.setOwner(new WifiP2pDevice(addr));
-                            owner = group.getOwner();
-                        } else {
-                            loge("Ignored invitation from null owner");
-                            break;
-                        }
-                    }
-
-                    config = new WifiP2pConfig();
-                    config.deviceAddress = group.getOwner().deviceAddress;
-
-                    if (isConfigInvalid(config)) {
-                        loge("Dropping invitation request " + config);
-                        break;
-                    }
-                    mSavedPeerConfig = config;
-
-                    //Check if we have the owner in peer list and use appropriate
-                    //wps method. Default is to use PBC.
-                    if ((owner = mPeers.get(owner.deviceAddress)) != null) {
-                        if (owner.wpsPbcSupported()) {
-                            mSavedPeerConfig.wps.setup = WpsInfo.PBC;
-                        } else if (owner.wpsKeypadSupported()) {
-                            mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
-                        } else if (owner.wpsDisplaySupported()) {
-                            mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
-                        }
-                    }
-
-                    mAutonomousGroup = false;
-                    mJoinExistingGroup = true;
-                    transitionTo(mUserAuthorizingInviteRequestState);
-                    break;
-                case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
-                case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
-                case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
-                    //We let the supplicant handle the provision discovery response
-                    //and wait instead for the GO_NEGOTIATION_REQUEST_EVENT.
-                    //Handling provision discovery and issuing a p2p_connect before
-                    //group negotiation comes through causes issues
-                    break;
-                case WifiP2pManager.CREATE_GROUP:
-                    mAutonomousGroup = true;
-                    int netId = message.arg1;
-                    boolean ret = false;
-                    if (netId == WifiP2pGroup.PERSISTENT_NET_ID) {
-                        // check if the go persistent group is present.
-                        netId = mGroups.getNetworkId(mThisDevice.deviceAddress);
-                        if (netId != -1) {
-                            ret = mWifiNative.p2pGroupAdd(netId);
-                        } else {
-                            ret = mWifiNative.p2pGroupAdd(true);
-                        }
-                    } else {
-                        ret = mWifiNative.p2pGroupAdd(false);
-                    }
-
-                    if (ret) {
-                        replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
-                        transitionTo(mGroupNegotiationState);
-                    } else {
+                    case WifiP2pManager.CREATE_GROUP:
                         replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
-                                WifiP2pManager.ERROR);
-                        // remain at this state.
-                    }
-                    break;
-                case WifiMonitor.P2P_GROUP_STARTED_EVENT:
-                    mGroup = (WifiP2pGroup) message.obj;
-                    if (DBG) logd(getName() + " group started");
-
-                    // We hit this scenario when a persistent group is reinvoked
-                    if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
-                        mAutonomousGroup = false;
-                        deferMessage(message);
-                        transitionTo(mGroupNegotiationState);
-                    } else {
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.REMOVE_GROUP:
+                        replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.ADD_LOCAL_SERVICE:
+                        replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+                        replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+                        replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.ADD_SERVICE_REQUEST:
+                        replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+                        replyToMessage(message,
+                                WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+                        replyToMessage(message,
+                                WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.SET_DEVICE_NAME:
+                        replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+                        replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.SET_WFD_INFO:
+                        if (!getWfdPermission(message.sendingUid)) {
+                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+                                    WifiP2pManager.ERROR);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+                                    WifiP2pManager.BUSY);
+                        }
+                        break;
+                    case WifiP2pManager.REQUEST_PEERS:
+                        replyToMessage(message, WifiP2pManager.RESPONSE_PEERS,
+                                getPeers((Bundle) message.obj, message.sendingUid));
+                        break;
+                    case WifiP2pManager.REQUEST_CONNECTION_INFO:
+                        replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO,
+                                new WifiP2pInfo(mWifiP2pInfo));
+                        break;
+                    case WifiP2pManager.REQUEST_GROUP_INFO:
+                        replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO,
+                                mGroup != null ? new WifiP2pGroup(mGroup) : null);
+                        break;
+                    case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
+                        replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
+                                new WifiP2pGroupList(mGroups, null));
+                        break;
+                    case WifiP2pManager.START_WPS:
+                        replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.GET_HANDOVER_REQUEST:
+                    case WifiP2pManager.GET_HANDOVER_SELECT:
+                        replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE, null);
+                        break;
+                    case WifiP2pManager.INITIATOR_REPORT_NFC_HANDOVER:
+                    case WifiP2pManager.RESPONDER_REPORT_NFC_HANDOVER:
+                        replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pMonitor.P2P_INVITATION_RESULT_EVENT:
+                    case WifiP2pMonitor.SUP_CONNECTION_EVENT:
+                    case WifiP2pMonitor.SUP_DISCONNECTION_EVENT:
+                    case WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
+                    case WifiP2pMonitor.P2P_DEVICE_FOUND_EVENT:
+                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
+                    case WifiP2pMonitor.P2P_FIND_STOPPED_EVENT:
+                    case WifiP2pMonitor.P2P_SERV_DISC_RESP_EVENT:
+                    case PEER_CONNECTION_USER_ACCEPT:
+                    case PEER_CONNECTION_USER_REJECT:
+                    case DISCONNECT_WIFI_RESPONSE:
+                    case DROP_WIFI_USER_ACCEPT:
+                    case DROP_WIFI_USER_REJECT:
+                    case GROUP_CREATING_TIMED_OUT:
+                    case DISABLE_P2P_TIMED_OUT:
+                    case IPM_PRE_DHCP_ACTION:
+                    case IPM_POST_DHCP_ACTION:
+                    case IPM_DHCP_RESULTS:
+                    case IPM_PROVISIONING_SUCCESS:
+                    case IPM_PROVISIONING_FAILURE:
+                    case WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT:
+                    case SET_MIRACAST_MODE:
+                    case WifiP2pManager.START_LISTEN:
+                    case WifiP2pManager.STOP_LISTEN:
+                    case WifiP2pManager.SET_CHANNEL:
+                    case WifiStateMachine.CMD_ENABLE_P2P:
+                        // Enable is lazy and has no response
+                        break;
+                    case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+                        // If we end up handling in default, p2p is not enabled
+                        if (mWifiChannel !=  null) {
+                            mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
+                        } else {
+                            loge("Unexpected disable request when WifiChannel is null");
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
+                        // unexpected group created, remove
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal arguments");
+                            break;
+                        }
+                        mGroup = (WifiP2pGroup) message.obj;
                         loge("Unexpected group creation, remove " + mGroup);
                         mWifiNative.p2pGroupRemove(mGroup.getInterface());
-                    }
-                    break;
-                case WifiP2pManager.START_LISTEN:
-                    if (DBG) logd(getName() + " start listen mode");
-                    mWifiNative.p2pFlush();
-                    if (mWifiNative.p2pExtListen(true, 500, 500)) {
-                        replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
-                    }
-                    break;
-                case WifiP2pManager.STOP_LISTEN:
-                    if (DBG) logd(getName() + " stop listen mode");
-                    if (mWifiNative.p2pExtListen(false, 0, 0)) {
-                        replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
-                    }
-                    mWifiNative.p2pFlush();
-                    break;
-                case WifiP2pManager.SET_CHANNEL:
-                    Bundle p2pChannels = (Bundle) message.obj;
-                    int lc = p2pChannels.getInt("lc", 0);
-                    int oc = p2pChannels.getInt("oc", 0);
-                    if (DBG) logd(getName() + " set listen and operating channel");
-                    if (mWifiNative.p2pSetChannel(lc, oc)) {
-                        replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
-                    }
-                    break;
-                case WifiP2pManager.INITIATOR_REPORT_NFC_HANDOVER:
-                    String handoverSelect = null;
-
-                    if (message.obj != null) {
-                        handoverSelect = ((Bundle) message.obj)
-                                .getString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE);
-                    }
-
-                    if (handoverSelect != null
-                            && mWifiNative.initiatorReportNfcHandover(handoverSelect)) {
-                        replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_SUCCEEDED);
-                        transitionTo(mGroupCreatingState);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED);
-                    }
-                    break;
-                case WifiP2pManager.RESPONDER_REPORT_NFC_HANDOVER:
-                    String handoverRequest = null;
-
-                    if (message.obj != null) {
-                        handoverRequest = ((Bundle) message.obj)
-                                .getString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE);
-                    }
-
-                    if (handoverRequest != null
-                            && mWifiNative.responderReportNfcHandover(handoverRequest)) {
-                        replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_SUCCEEDED);
-                        transitionTo(mGroupCreatingState);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED);
-                    }
-                    break;
-                default:
-                    return NOT_HANDLED;
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+                        // A group formation failure is always followed by
+                        // a group removed event. Flushing things at group formation
+                        // failure causes supplicant issues. Ignore right now.
+                        break;
+                    default:
+                        loge("Unhandled message " + message);
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
             }
-            return HANDLED;
-        }
-    }
-
-    class GroupCreatingState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            sendMessageDelayed(obtainMessage(GROUP_CREATING_TIMED_OUT,
-                    ++mGroupCreatingTimeoutIndex, 0), GROUP_CREATING_WAIT_TIME_MS);
         }
 
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            boolean ret = HANDLED;
-            switch (message.what) {
-               case GROUP_CREATING_TIMED_OUT:
-                    if (mGroupCreatingTimeoutIndex == message.arg1) {
-                        if (DBG) logd("Group negotiation timed out");
-                        handleGroupCreationFailure();
-                        transitionTo(mInactiveState);
-                    }
-                    break;
-                case WifiMonitor.P2P_DEVICE_LOST_EVENT:
-                    WifiP2pDevice device = (WifiP2pDevice) message.obj;
-                    if (!mSavedPeerConfig.deviceAddress.equals(device.deviceAddress)) {
-                        if (DBG) {
-                            logd("mSavedPeerConfig " + mSavedPeerConfig.deviceAddress +
-                                "device " + device.deviceAddress);
+        class P2pNotSupportedState extends State {
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case WifiP2pManager.DISCOVER_PEERS:
+                        replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.STOP_DISCOVERY:
+                        replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.DISCOVER_SERVICES:
+                        replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.CONNECT:
+                        replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.CANCEL_CONNECT:
+                        replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.CREATE_GROUP:
+                        replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.REMOVE_GROUP:
+                        replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.ADD_LOCAL_SERVICE:
+                        replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+                        replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+                        replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.ADD_SERVICE_REQUEST:
+                        replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+                        replyToMessage(message,
+                                WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+                        replyToMessage(message,
+                                WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.SET_DEVICE_NAME:
+                        replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+                        replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.SET_WFD_INFO:
+                        if (!getWfdPermission(message.sendingUid)) {
+                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+                                    WifiP2pManager.ERROR);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+                                    WifiP2pManager.P2P_UNSUPPORTED);
                         }
-                        // Do the regular device lost handling
-                        ret = NOT_HANDLED;
+                        break;
+                    case WifiP2pManager.START_WPS:
+                        replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.START_LISTEN:
+                        replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+                    case WifiP2pManager.STOP_LISTEN:
+                        replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED,
+                                WifiP2pManager.P2P_UNSUPPORTED);
+                        break;
+
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class P2pDisablingState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                sendMessageDelayed(obtainMessage(DISABLE_P2P_TIMED_OUT,
+                        ++sDisableP2pTimeoutIndex, 0), DISABLE_P2P_WAIT_TIME_MS);
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case WifiP2pMonitor.SUP_DISCONNECTION_EVENT:
+                        if (DBG) logd("p2p socket connection lost");
+                        transitionTo(mP2pDisabledState);
+                        break;
+                    case WifiStateMachine.CMD_ENABLE_P2P:
+                    case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+                        deferMessage(message);
+                        break;
+                    case DISABLE_P2P_TIMED_OUT:
+                        if (sDisableP2pTimeoutIndex == message.arg1) {
+                            loge("P2p disable timed out");
+                            transitionTo(mP2pDisabledState);
+                        }
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                if (mWifiChannel != null) {
+                    mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
+                } else {
+                    loge("P2pDisablingState exit(): WifiChannel is null");
+                }
+            }
+        }
+
+        class P2pDisabledState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case WifiStateMachine.CMD_ENABLE_P2P:
+                        try {
+                            mNwService.setInterfaceUp(mWifiNative.getInterfaceName());
+                        } catch (RemoteException re) {
+                            loge("Unable to change interface settings: " + re);
+                        } catch (IllegalStateException ie) {
+                            loge("Unable to change interface settings: " + ie);
+                        }
+                        mWifiMonitor.startMonitoring(mWifiNative.getInterfaceName());
+                        transitionTo(mP2pEnablingState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class P2pEnablingState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case WifiP2pMonitor.SUP_CONNECTION_EVENT:
+                        if (DBG) logd("P2p socket connection successful");
+                        transitionTo(mInactiveState);
+                        break;
+                    case WifiP2pMonitor.SUP_DISCONNECTION_EVENT:
+                        loge("P2p socket connection failed");
+                        transitionTo(mP2pDisabledState);
+                        break;
+                    case WifiStateMachine.CMD_ENABLE_P2P:
+                    case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+                        deferMessage(message);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class P2pEnabledState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                sendP2pStateChangedBroadcast(true);
+                mNetworkInfo.setIsAvailable(true);
+                sendP2pConnectionChangedBroadcast();
+                initializeP2pSettings();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case WifiP2pMonitor.SUP_DISCONNECTION_EVENT:
+                        loge("Unexpected loss of p2p socket connection");
+                        transitionTo(mP2pDisabledState);
+                        break;
+                    case WifiStateMachine.CMD_ENABLE_P2P:
+                        // Nothing to do
+                        break;
+                    case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+                        if (mPeers.clear()) {
+                            sendPeersChangedBroadcast();
+                        }
+                        if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast();
+
+                        mWifiMonitor.stopMonitoring(mWifiNative.getInterfaceName());
+                        transitionTo(mP2pDisablingState);
+                        break;
+                    case WifiP2pManager.SET_DEVICE_NAME:
+                    {
+                        WifiP2pDevice d = (WifiP2pDevice) message.obj;
+                        if (d != null && setAndPersistDeviceName(d.deviceName)) {
+                            if (DBG) logd("set device name " + d.deviceName);
+                            replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED,
+                                    WifiP2pManager.ERROR);
+                        }
                         break;
                     }
-                    // Do nothing
-                    if (DBG) logd("Add device to lost list " + device);
-                    mPeersLostDuringConnection.updateSupplicantDetails(device);
-                    break;
-                case WifiP2pManager.DISCOVER_PEERS:
-                    /* Discovery will break negotiation */
-                    replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
-                            WifiP2pManager.BUSY);
-                    break;
-                case WifiP2pManager.CANCEL_CONNECT:
-                    //Do a supplicant p2p_cancel which only cancels an ongoing
-                    //group negotiation. This will fail for a pending provision
-                    //discovery or for a pending user action, but at the framework
-                    //level, we always treat cancel as succeeded and enter
-                    //an inactive state
-                    mWifiNative.p2pCancelConnect();
-                    handleGroupCreationFailure();
-                    transitionTo(mInactiveState);
-                    replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED);
-                    break;
-                case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
-                    // We hit this scenario when NFC handover is invoked.
-                    mAutonomousGroup = false;
-                    transitionTo(mGroupNegotiationState);
-                    break;
-                default:
-                    ret = NOT_HANDLED;
-            }
-            return ret;
-        }
-    }
-
-    class UserAuthorizingNegotiationRequestState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            notifyInvitationReceived();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            boolean ret = HANDLED;
-            switch (message.what) {
-                case PEER_CONNECTION_USER_ACCEPT:
-                    mWifiNative.p2pStopFind();
-                    p2pConnectWithPinDisplay(mSavedPeerConfig);
-                    mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
-                    sendPeersChangedBroadcast();
-                    transitionTo(mGroupNegotiationState);
-                   break;
-                case PEER_CONNECTION_USER_REJECT:
-                    if (DBG) logd("User rejected negotiation " + mSavedPeerConfig);
-                    transitionTo(mInactiveState);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return ret;
-        }
-
-        @Override
-        public void exit() {
-            //TODO: dismiss dialog if not already done
-        }
-    }
-
-    class UserAuthorizingInviteRequestState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            notifyInvitationReceived();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            boolean ret = HANDLED;
-            switch (message.what) {
-                case PEER_CONNECTION_USER_ACCEPT:
-                    mWifiNative.p2pStopFind();
-                    if (!reinvokePersistentGroup(mSavedPeerConfig)) {
-                        // Do negotiation when persistence fails
-                        p2pConnectWithPinDisplay(mSavedPeerConfig);
+                    case WifiP2pManager.SET_WFD_INFO:
+                    {
+                        WifiP2pWfdInfo d = (WifiP2pWfdInfo) message.obj;
+                        if (!getWfdPermission(message.sendingUid)) {
+                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+                                    WifiP2pManager.ERROR);
+                        } else if (d != null && setWfdInfo(d)) {
+                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
+                                    WifiP2pManager.ERROR);
+                        }
+                        break;
                     }
-                    mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
-                    sendPeersChangedBroadcast();
-                    transitionTo(mGroupNegotiationState);
-                   break;
-                case PEER_CONNECTION_USER_REJECT:
-                    if (DBG) logd("User rejected invitation " + mSavedPeerConfig);
-                    transitionTo(mInactiveState);
-                    break;
-                default:
-                    return NOT_HANDLED;
+                    case BLOCK_DISCOVERY:
+                        boolean blocked = (message.arg1 == ENABLED ? true : false);
+                        if (mDiscoveryBlocked == blocked) break;
+                        mDiscoveryBlocked = blocked;
+                        if (blocked && mDiscoveryStarted) {
+                            mWifiNative.p2pStopFind();
+                            mDiscoveryPostponed = true;
+                        }
+                        if (!blocked && mDiscoveryPostponed) {
+                            mDiscoveryPostponed = false;
+                            mWifiNative.p2pFind(DISCOVER_TIMEOUT_S);
+                        }
+                        if (blocked) {
+                            if (message.obj == null) {
+                                Log.e(TAG, "Illegal argument(s)");
+                                break;
+                            }
+                            StateMachine m = (StateMachine) message.obj;
+                            try {
+                                m.sendMessage(message.arg2);
+                            } catch (Exception e) {
+                                loge("unable to send BLOCK_DISCOVERY response: " + e);
+                            }
+                        }
+                        break;
+                    case WifiP2pManager.DISCOVER_PEERS:
+                        if (mDiscoveryBlocked) {
+                            replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+                                    WifiP2pManager.BUSY);
+                            break;
+                        }
+                        // do not send service discovery request while normal find operation.
+                        clearSupplicantServiceRequest();
+                        if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+                            replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
+                            sendP2pDiscoveryChangedBroadcast(true);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+                                    WifiP2pManager.ERROR);
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_FIND_STOPPED_EVENT:
+                        sendP2pDiscoveryChangedBroadcast(false);
+                        break;
+                    case WifiP2pManager.STOP_DISCOVERY:
+                        if (mWifiNative.p2pStopFind()) {
+                            replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+                                    WifiP2pManager.ERROR);
+                        }
+                        break;
+                    case WifiP2pManager.DISCOVER_SERVICES:
+                        if (mDiscoveryBlocked) {
+                            replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                                    WifiP2pManager.BUSY);
+                            break;
+                        }
+                        if (DBG) logd(getName() + " discover services");
+                        if (!updateSupplicantServiceRequest()) {
+                            replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                                    WifiP2pManager.NO_SERVICE_REQUESTS);
+                            break;
+                        }
+                        if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+                            replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+                                    WifiP2pManager.ERROR);
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_DEVICE_FOUND_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        WifiP2pDevice device = (WifiP2pDevice) message.obj;
+                        if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
+                        mPeers.updateSupplicantDetails(device);
+                        sendPeersChangedBroadcast();
+                        break;
+                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        device = (WifiP2pDevice) message.obj;
+                        // Gets current details for the one removed
+                        device = mPeers.remove(device.deviceAddress);
+                        if (device != null) {
+                            sendPeersChangedBroadcast();
+                        }
+                        break;
+                    case WifiP2pManager.ADD_LOCAL_SERVICE:
+                        if (DBG) logd(getName() + " add service");
+                        WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo) message.obj;
+                        if (addLocalService(message.replyTo, servInfo)) {
+                            replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
+                        }
+                        break;
+                    case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+                        if (DBG) logd(getName() + " remove service");
+                        servInfo = (WifiP2pServiceInfo) message.obj;
+                        removeLocalService(message.replyTo, servInfo);
+                        replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED);
+                        break;
+                    case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+                        if (DBG) logd(getName() + " clear service");
+                        clearLocalServices(message.replyTo);
+                        replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED);
+                        break;
+                    case WifiP2pManager.ADD_SERVICE_REQUEST:
+                        if (DBG) logd(getName() + " add service request");
+                        if (!addServiceRequest(message.replyTo,
+                                (WifiP2pServiceRequest) message.obj)) {
+                            replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED);
+                            break;
+                        }
+                        replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED);
+                        break;
+                    case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+                        if (DBG) logd(getName() + " remove service request");
+                        removeServiceRequest(message.replyTo, (WifiP2pServiceRequest) message.obj);
+                        replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED);
+                        break;
+                    case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+                        if (DBG) logd(getName() + " clear service request");
+                        clearServiceRequests(message.replyTo);
+                        replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
+                        break;
+                    case WifiP2pMonitor.P2P_SERV_DISC_RESP_EVENT:
+                        if (DBG) logd(getName() + " receive service response");
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        List<WifiP2pServiceResponse> sdRespList =
+                                (List<WifiP2pServiceResponse>) message.obj;
+                        for (WifiP2pServiceResponse resp : sdRespList) {
+                            WifiP2pDevice dev =
+                                    mPeers.get(resp.getSrcDevice().deviceAddress);
+                            resp.setSrcDevice(dev);
+                            sendServiceResponse(resp);
+                        }
+                        break;
+                    case WifiP2pManager.DELETE_PERSISTENT_GROUP:
+                        if (DBG) logd(getName() + " delete persistent group");
+                        mGroups.remove(message.arg1);
+                        replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED);
+                        break;
+                    case SET_MIRACAST_MODE:
+                        mWifiNative.setMiracastMode(message.arg1);
+                        break;
+                    case WifiP2pManager.START_LISTEN:
+                        if (DBG) logd(getName() + " start listen mode");
+                        mWifiNative.p2pFlush();
+                        if (mWifiNative.p2pExtListen(true, 500, 500)) {
+                            replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
+                        }
+                        break;
+                    case WifiP2pManager.STOP_LISTEN:
+                        if (DBG) logd(getName() + " stop listen mode");
+                        if (mWifiNative.p2pExtListen(false, 0, 0)) {
+                            replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
+                        }
+                        mWifiNative.p2pFlush();
+                        break;
+                    case WifiP2pManager.SET_CHANNEL:
+                        Bundle p2pChannels = (Bundle) message.obj;
+                        int lc = p2pChannels.getInt("lc", 0);
+                        int oc = p2pChannels.getInt("oc", 0);
+                        if (DBG) logd(getName() + " set listen and operating channel");
+                        if (mWifiNative.p2pSetChannel(lc, oc)) {
+                            replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
+                        }
+                        break;
+                    case WifiP2pManager.GET_HANDOVER_REQUEST:
+                        Bundle requestBundle = new Bundle();
+                        requestBundle.putString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE,
+                                mWifiNative.getNfcHandoverRequest());
+                        replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE,
+                                requestBundle);
+                        break;
+                    case WifiP2pManager.GET_HANDOVER_SELECT:
+                        Bundle selectBundle = new Bundle();
+                        selectBundle.putString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE,
+                                mWifiNative.getNfcHandoverSelect());
+                        replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE,
+                                selectBundle);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
             }
-            return ret;
+
+            @Override
+            public void exit() {
+                sendP2pDiscoveryChangedBroadcast(false);
+                sendP2pStateChangedBroadcast(false);
+                mNetworkInfo.setIsAvailable(false);
+            }
         }
 
-        @Override
-        public void exit() {
-            //TODO: dismiss dialog if not already done
-        }
-    }
+        class InactiveState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                mSavedPeerConfig.invalidate();
+            }
 
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case WifiP2pManager.CONNECT:
+                        if (DBG) logd(getName() + " sending connect");
+                        WifiP2pConfig config = (WifiP2pConfig) message.obj;
+                        if (isConfigInvalid(config)) {
+                            loge("Dropping connect requeset " + config);
+                            replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
+                            break;
+                        }
 
-
-    class ProvisionDiscoveryState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig);
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            WifiP2pProvDiscEvent provDisc;
-            WifiP2pDevice device;
-            switch (message.what) {
-                case WifiMonitor.P2P_PROV_DISC_PBC_RSP_EVENT:
-                    provDisc = (WifiP2pProvDiscEvent) message.obj;
-                    device = provDisc.device;
-                    if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
-
-                    if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
-                        if (DBG) logd("Found a match " + mSavedPeerConfig);
-                        p2pConnectWithPinDisplay(mSavedPeerConfig);
-                        transitionTo(mGroupNegotiationState);
-                    }
-                    break;
-                case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
-                    provDisc = (WifiP2pProvDiscEvent) message.obj;
-                    device = provDisc.device;
-                    if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
-
-                    if (mSavedPeerConfig.wps.setup == WpsInfo.KEYPAD) {
-                        if (DBG) logd("Found a match " + mSavedPeerConfig);
-                        /* we already have the pin */
-                        if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
-                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                        mAutonomousGroup = false;
+                        mWifiNative.p2pStopFind();
+                        if (reinvokePersistentGroup(config)) {
                             transitionTo(mGroupNegotiationState);
                         } else {
-                            mJoinExistingGroup = false;
-                            transitionTo(mUserAuthorizingNegotiationRequestState);
+                            transitionTo(mProvisionDiscoveryState);
                         }
-                    }
-                    break;
-                case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
-                    provDisc = (WifiP2pProvDiscEvent) message.obj;
-                    device = provDisc.device;
-                    if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
-
-                    if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) {
-                        if (DBG) logd("Found a match " + mSavedPeerConfig);
-                        mSavedPeerConfig.wps.pin = provDisc.pin;
-                        p2pConnectWithPinDisplay(mSavedPeerConfig);
-                        notifyInvitationSent(provDisc.pin, device.deviceAddress);
-                        transitionTo(mGroupNegotiationState);
-                    }
-                    break;
-                case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
-                    loge("provision discovery failed");
-                    handleGroupCreationFailure();
-                    transitionTo(mInactiveState);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    class GroupNegotiationState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                // We ignore these right now, since we get a GROUP_STARTED notification
-                // afterwards
-                case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
-                case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
-                    if (DBG) logd(getName() + " go success");
-                    break;
-                case WifiMonitor.P2P_GROUP_STARTED_EVENT:
-                    mGroup = (WifiP2pGroup) message.obj;
-                    if (DBG) logd(getName() + " group started");
-
-                    if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
-                        /*
-                         * update cache information and set network id to mGroup.
-                         */
-                        updatePersistentNetworks(NO_RELOAD);
-                        String devAddr = mGroup.getOwner().deviceAddress;
-                        mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
-                                mGroup.getNetworkName()));
-                    }
-
-                    if (mGroup.isGroupOwner()) {
-                        /* Setting an idle time out on GO causes issues with certain scenarios
-                         * on clients where it can be off-channel for longer and with the power
-                         * save modes used.
-                         *
-                         * TODO: Verify multi-channel scenarios and supplicant behavior are
-                         * better before adding a time out in future
-                         */
-                        //Set group idle timeout of 10 sec, to avoid GO beaconing incase of any
-                        //failure during 4-way Handshake.
-                        if (!mAutonomousGroup) {
-                            mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
-                        }
-                        startDhcpServer(mGroup.getInterface());
-                    } else {
-                        mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
-                        startIpManager(mGroup.getInterface());
-                        WifiP2pDevice groupOwner = mGroup.getOwner();
-                        WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress);
-                        if (peer != null) {
-                            // update group owner details with peer details found at discovery
-                            groupOwner.updateSupplicantDetails(peer);
-                            mPeers.updateStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED);
-                            sendPeersChangedBroadcast();
+                        mSavedPeerConfig = config;
+                        mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+                        sendPeersChangedBroadcast();
+                        replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
+                        break;
+                    case WifiP2pManager.STOP_DISCOVERY:
+                        if (mWifiNative.p2pStopFind()) {
+                            // When discovery stops in inactive state, flush to clear
+                            // state peer data
+                            mWifiNative.p2pFlush();
+                            mServiceDiscReqId = null;
+                            replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
                         } else {
-                            // A supplicant bug can lead to reporting an invalid
-                            // group owner address (all zeroes) at times. Avoid a
-                            // crash, but continue group creation since it is not
-                            // essential.
-                            logw("Unknown group owner " + groupOwner);
+                            replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
+                                    WifiP2pManager.ERROR);
                         }
-                    }
-                    transitionTo(mGroupCreatedState);
-                    break;
-                case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
-                    P2pStatus status = (P2pStatus) message.obj;
-                    if (status == P2pStatus.NO_COMMON_CHANNEL) {
-                        transitionTo(mFrequencyConflictState);
                         break;
-                    }
-                    /* continue with group removal handling */
-                case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
-                    if (DBG) logd(getName() + " go failure");
-                    handleGroupCreationFailure();
-                    transitionTo(mInactiveState);
-                    break;
-                // A group formation failure is always followed by
-                // a group removed event. Flushing things at group formation
-                // failure causes supplicant issues. Ignore right now.
-                case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
-                    status = (P2pStatus) message.obj;
-                    if (status == P2pStatus.NO_COMMON_CHANNEL) {
-                        transitionTo(mFrequencyConflictState);
+                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
+                        config = (WifiP2pConfig) message.obj;
+                        if (isConfigInvalid(config)) {
+                            loge("Dropping GO neg request " + config);
+                            break;
+                        }
+                        mSavedPeerConfig = config;
+                        mAutonomousGroup = false;
+                        mJoinExistingGroup = false;
+                        transitionTo(mUserAuthorizingNegotiationRequestState);
                         break;
-                    }
-                    break;
-                case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
-                    status = (P2pStatus)message.obj;
-                    if (status == P2pStatus.SUCCESS) {
-                        // invocation was succeeded.
-                        // wait P2P_GROUP_STARTED_EVENT.
-                        break;
-                    }
-                    loge("Invitation result " + status);
-                    if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
-                        // target device has already removed the credential.
-                        // So, remove this credential accordingly.
-                        int netId = mSavedPeerConfig.netId;
-                        if (netId >= 0) {
-                            if (DBG) logd("Remove unknown client from the list");
-                            removeClientFromList(netId, mSavedPeerConfig.deviceAddress, true);
+                    case WifiP2pMonitor.P2P_INVITATION_RECEIVED_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Invalid argument(s)");
+                            break;
+                        }
+                        WifiP2pGroup group = (WifiP2pGroup) message.obj;
+                        WifiP2pDevice owner = group.getOwner();
+                        if (owner == null) {
+                            int id = group.getNetworkId();
+                            if (id < 0) {
+                                loge("Ignored invitation from null owner");
+                                break;
+                            }
+
+                            String addr = mGroups.getOwnerAddr(id);
+                            if (addr != null) {
+                                group.setOwner(new WifiP2pDevice(addr));
+                                owner = group.getOwner();
+                            } else {
+                                loge("Ignored invitation from null owner");
+                                break;
+                            }
+                        }
+                        config = new WifiP2pConfig();
+                        config.deviceAddress = group.getOwner().deviceAddress;
+                        if (isConfigInvalid(config)) {
+                            loge("Dropping invitation request " + config);
+                            break;
+                        }
+                        mSavedPeerConfig = config;
+
+                        // Check if we have the owner in peer list and use appropriate
+                        // wps method. Default is to use PBC.
+                        if (owner != null && ((owner = mPeers.get(owner.deviceAddress)) != null)) {
+                            if (owner.wpsPbcSupported()) {
+                                mSavedPeerConfig.wps.setup = WpsInfo.PBC;
+                            } else if (owner.wpsKeypadSupported()) {
+                                mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
+                            } else if (owner.wpsDisplaySupported()) {
+                                mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
+                            }
                         }
 
-                        // Reinvocation has failed, try group negotiation
-                        mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
-                        p2pConnectWithPinDisplay(mSavedPeerConfig);
-                    } else if (status == P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE) {
+                        mAutonomousGroup = false;
+                        mJoinExistingGroup = true;
+                        transitionTo(mUserAuthorizingInviteRequestState);
+                        break;
+                    case WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+                    case WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+                    case WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+                        // We let the supplicant handle the provision discovery response
+                        // and wait instead for the GO_NEGOTIATION_REQUEST_EVENT.
+                        // Handling provision discovery and issuing a p2p_connect before
+                        // group negotiation comes through causes issues
+                        break;
+                    case WifiP2pManager.CREATE_GROUP:
+                        mAutonomousGroup = true;
+                        int netId = message.arg1;
+                        boolean ret = false;
+                        if (netId == WifiP2pGroup.PERSISTENT_NET_ID) {
+                            // check if the go persistent group is present.
+                            netId = mGroups.getNetworkId(mThisDevice.deviceAddress);
+                            if (netId != -1) {
+                                ret = mWifiNative.p2pGroupAdd(netId);
+                            } else {
+                                ret = mWifiNative.p2pGroupAdd(true);
+                            }
+                        } else {
+                            ret = mWifiNative.p2pGroupAdd(false);
+                        }
 
-                        // Devices setting persistent_reconnect to 0 in wpa_supplicant
-                        // always defer the invocation request and return
-                        // "information is currently unable" error.
-                        // So, try another way to connect for interoperability.
-                        mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
-                        p2pConnectWithPinDisplay(mSavedPeerConfig);
-                    } else if (status == P2pStatus.NO_COMMON_CHANNEL) {
-                        transitionTo(mFrequencyConflictState);
-                    } else {
+                        if (ret) {
+                            replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
+                            transitionTo(mGroupNegotiationState);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
+                                    WifiP2pManager.ERROR);
+                            // remain at this state.
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Invalid argument(s)");
+                            break;
+                        }
+                        mGroup = (WifiP2pGroup) message.obj;
+                        if (DBG) logd(getName() + " group started");
+
+                        // We hit this scenario when a persistent group is reinvoked
+                        if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+                            mAutonomousGroup = false;
+                            deferMessage(message);
+                            transitionTo(mGroupNegotiationState);
+                        } else {
+                            loge("Unexpected group creation, remove " + mGroup);
+                            mWifiNative.p2pGroupRemove(mGroup.getInterface());
+                        }
+                        break;
+                    case WifiP2pManager.START_LISTEN:
+                        if (DBG) logd(getName() + " start listen mode");
+                        mWifiNative.p2pFlush();
+                        if (mWifiNative.p2pExtListen(true, 500, 500)) {
+                            replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
+                        }
+                        break;
+                    case WifiP2pManager.STOP_LISTEN:
+                        if (DBG) logd(getName() + " stop listen mode");
+                        if (mWifiNative.p2pExtListen(false, 0, 0)) {
+                            replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
+                        }
+                        mWifiNative.p2pFlush();
+                        break;
+                    case WifiP2pManager.SET_CHANNEL:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal arguments(s)");
+                            break;
+                        }
+                        Bundle p2pChannels = (Bundle) message.obj;
+                        int lc = p2pChannels.getInt("lc", 0);
+                        int oc = p2pChannels.getInt("oc", 0);
+                        if (DBG) logd(getName() + " set listen and operating channel");
+                        if (mWifiNative.p2pSetChannel(lc, oc)) {
+                            replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
+                        }
+                        break;
+                    case WifiP2pManager.INITIATOR_REPORT_NFC_HANDOVER:
+                        String handoverSelect = null;
+
+                        if (message.obj != null) {
+                            handoverSelect = ((Bundle) message.obj)
+                                    .getString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE);
+                        }
+
+                        if (handoverSelect != null
+                                && mWifiNative.initiatorReportNfcHandover(handoverSelect)) {
+                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_SUCCEEDED);
+                            transitionTo(mGroupCreatingState);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED);
+                        }
+                        break;
+                    case WifiP2pManager.RESPONDER_REPORT_NFC_HANDOVER:
+                        String handoverRequest = null;
+
+                        if (message.obj != null) {
+                            handoverRequest = ((Bundle) message.obj)
+                                    .getString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE);
+                        }
+
+                        if (handoverRequest != null
+                                && mWifiNative.responderReportNfcHandover(handoverRequest)) {
+                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_SUCCEEDED);
+                            transitionTo(mGroupCreatingState);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED);
+                        }
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class GroupCreatingState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                sendMessageDelayed(obtainMessage(GROUP_CREATING_TIMED_OUT,
+                        ++sGroupCreatingTimeoutIndex, 0), GROUP_CREATING_WAIT_TIME_MS);
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                boolean ret = HANDLED;
+                switch (message.what) {
+                    case GROUP_CREATING_TIMED_OUT:
+                        if (sGroupCreatingTimeoutIndex == message.arg1) {
+                            if (DBG) logd("Group negotiation timed out");
+                            handleGroupCreationFailure();
+                            transitionTo(mInactiveState);
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        WifiP2pDevice device = (WifiP2pDevice) message.obj;
+                        if (!mSavedPeerConfig.deviceAddress.equals(device.deviceAddress)) {
+                            if (DBG) {
+                                logd("mSavedPeerConfig " + mSavedPeerConfig.deviceAddress
+                                        + "device " + device.deviceAddress);
+                            }
+                            // Do the regular device lost handling
+                            ret = NOT_HANDLED;
+                            break;
+                        }
+                        // Do nothing
+                        if (DBG) logd("Add device to lost list " + device);
+                        mPeersLostDuringConnection.updateSupplicantDetails(device);
+                        break;
+                    case WifiP2pManager.DISCOVER_PEERS:
+                        // Discovery will break negotiation
+                        replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
+                                WifiP2pManager.BUSY);
+                        break;
+                    case WifiP2pManager.CANCEL_CONNECT:
+                        // Do a supplicant p2p_cancel which only cancels an ongoing
+                        // group negotiation. This will fail for a pending provision
+                        // discovery or for a pending user action, but at the framework
+                        // level, we always treat cancel as succeeded and enter
+                        // an inactive state
+                        mWifiNative.p2pCancelConnect();
                         handleGroupCreationFailure();
                         transitionTo(mInactiveState);
-                    }
-                    break;
-                default:
-                    return NOT_HANDLED;
+                        replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED);
+                        break;
+                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
+                        // We hit this scenario when NFC handover is invoked.
+                        mAutonomousGroup = false;
+                        transitionTo(mGroupNegotiationState);
+                        break;
+                    default:
+                        ret = NOT_HANDLED;
+                }
+                return ret;
             }
-            return HANDLED;
         }
-    }
 
-    class FrequencyConflictState extends State {
-        private AlertDialog mFrequencyConflictDialog;
+        class UserAuthorizingNegotiationRequestState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                notifyInvitationReceived();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                boolean ret = HANDLED;
+                switch (message.what) {
+                    case PEER_CONNECTION_USER_ACCEPT:
+                        mWifiNative.p2pStopFind();
+                        p2pConnectWithPinDisplay(mSavedPeerConfig);
+                        mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+                        sendPeersChangedBroadcast();
+                        transitionTo(mGroupNegotiationState);
+                        break;
+                    case PEER_CONNECTION_USER_REJECT:
+                        if (DBG) logd("User rejected negotiation " + mSavedPeerConfig);
+                        transitionTo(mInactiveState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return ret;
+            }
+
+            @Override
+            public void exit() {
+                // TODO: dismiss dialog if not already done
+            }
+        }
+
+        class UserAuthorizingInviteRequestState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                notifyInvitationReceived();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                boolean ret = HANDLED;
+                switch (message.what) {
+                    case PEER_CONNECTION_USER_ACCEPT:
+                        mWifiNative.p2pStopFind();
+                        if (!reinvokePersistentGroup(mSavedPeerConfig)) {
+                            // Do negotiation when persistence fails
+                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                        }
+                        mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
+                        sendPeersChangedBroadcast();
+                        transitionTo(mGroupNegotiationState);
+                        break;
+                    case PEER_CONNECTION_USER_REJECT:
+                        if (DBG) logd("User rejected invitation " + mSavedPeerConfig);
+                        transitionTo(mInactiveState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return ret;
+            }
+
+            @Override
+            public void exit() {
+                // TODO: dismiss dialog if not already done
+            }
+        }
+
+        class ProvisionDiscoveryState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig);
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                WifiP2pProvDiscEvent provDisc = null;
+                WifiP2pDevice device = null;
+                switch (message.what) {
+                    case WifiP2pMonitor.P2P_PROV_DISC_PBC_RSP_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Invalid argument(s)");
+                            break;
+                        }
+                        provDisc = (WifiP2pProvDiscEvent) message.obj;
+                        device = provDisc.device;
+                        if (device != null
+                                && !device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) {
+                            break;
+                        }
+                        if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
+                            if (DBG) logd("Found a match " + mSavedPeerConfig);
+                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                            transitionTo(mGroupNegotiationState);
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        provDisc = (WifiP2pProvDiscEvent) message.obj;
+                        device = provDisc.device;
+                        if (device != null
+                                && !device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) {
+                            break;
+                        }
+                        if (mSavedPeerConfig.wps.setup == WpsInfo.KEYPAD) {
+                            if (DBG) logd("Found a match " + mSavedPeerConfig);
+                            // we already have the pin
+                            if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
+                                p2pConnectWithPinDisplay(mSavedPeerConfig);
+                                transitionTo(mGroupNegotiationState);
+                            } else {
+                                mJoinExistingGroup = false;
+                                transitionTo(mUserAuthorizingNegotiationRequestState);
+                            }
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        provDisc = (WifiP2pProvDiscEvent) message.obj;
+                        device = provDisc.device;
+                        if (device == null) {
+                            Log.e(TAG, "Invalid device");
+                            break;
+                        }
+                        if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) {
+                            break;
+                        }
+                        if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) {
+                            if (DBG) logd("Found a match " + mSavedPeerConfig);
+                            mSavedPeerConfig.wps.pin = provDisc.pin;
+                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                            notifyInvitationSent(provDisc.pin, device.deviceAddress);
+                            transitionTo(mGroupNegotiationState);
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT:
+                        loge("provision discovery failed");
+                        handleGroupCreationFailure();
+                        transitionTo(mInactiveState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class GroupNegotiationState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    // We ignore these right now, since we get a GROUP_STARTED notification
+                    // afterwards
+                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
+                    case WifiP2pMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
+                        if (DBG) logd(getName() + " go success");
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        mGroup = (WifiP2pGroup) message.obj;
+                        if (DBG) logd(getName() + " group started");
+                        if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
+                             // update cache information and set network id to mGroup.
+                            updatePersistentNetworks(NO_RELOAD);
+                            String devAddr = mGroup.getOwner().deviceAddress;
+                            mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
+                                    mGroup.getNetworkName()));
+                        }
+
+                        if (mGroup.isGroupOwner()) {
+                            // Setting an idle time out on GO causes issues with certain scenarios
+                            // on clients where it can be off-channel for longer and with the power
+                            // save modes used.
+                            // TODO: Verify multi-channel scenarios and supplicant behavior are
+                            // better before adding a time out in future
+                            // Set group idle timeout of 10 sec, to avoid GO beaconing incase of any
+                            // failure during 4-way Handshake.
+                            if (!mAutonomousGroup) {
+                                mWifiNative.setP2pGroupIdle(mGroup.getInterface(),
+                                        GROUP_IDLE_TIME_S);
+                            }
+                            startDhcpServer(mGroup.getInterface());
+                        } else {
+                            mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
+                            startIpManager(mGroup.getInterface());
+                            WifiP2pDevice groupOwner = mGroup.getOwner();
+                            WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress);
+                            if (peer != null) {
+                                // update group owner details with peer details found at discovery
+                                groupOwner.updateSupplicantDetails(peer);
+                                mPeers.updateStatus(groupOwner.deviceAddress,
+                                        WifiP2pDevice.CONNECTED);
+                                sendPeersChangedBroadcast();
+                            } else {
+                                // A supplicant bug can lead to reporting an invalid
+                                // group owner address (all zeroes) at times. Avoid a
+                                // crash, but continue group creation since it is not
+                                // essential.
+                                logw("Unknown group owner " + groupOwner);
+                            }
+                        }
+                        transitionTo(mGroupCreatedState);
+                        break;
+                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
+                        P2pStatus status = (P2pStatus) message.obj;
+                        if (status == P2pStatus.NO_COMMON_CHANNEL) {
+                            transitionTo(mFrequencyConflictState);
+                            break;
+                        }
+                        // continue with group removal handling
+                    case WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
+                        if (DBG) logd(getName() + " go failure");
+                        handleGroupCreationFailure();
+                        transitionTo(mInactiveState);
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+                        // A group formation failure is always followed by
+                        // a group removed event. Flushing things at group formation
+                        // failure causes supplicant issues. Ignore right now.
+                        status = (P2pStatus) message.obj;
+                        if (status == P2pStatus.NO_COMMON_CHANNEL) {
+                            transitionTo(mFrequencyConflictState);
+                            break;
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_INVITATION_RESULT_EVENT:
+                        status = (P2pStatus) message.obj;
+                        if (status == P2pStatus.SUCCESS) {
+                            // invocation was succeeded.
+                            // wait P2P_GROUP_STARTED_EVENT.
+                            break;
+                        }
+                        loge("Invitation result " + status);
+                        if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+                            // target device has already removed the credential.
+                            // So, remove this credential accordingly.
+                            int netId = mSavedPeerConfig.netId;
+                            if (netId >= 0) {
+                                if (DBG) logd("Remove unknown client from the list");
+                                removeClientFromList(netId, mSavedPeerConfig.deviceAddress, true);
+                            }
+
+                            // Reinvocation has failed, try group negotiation
+                            mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
+                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                        } else if (status == P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE) {
+
+                            // Devices setting persistent_reconnect to 0 in wpa_supplicant
+                            // always defer the invocation request and return
+                            // "information is currently unavailable" error.
+                            // So, try another way to connect for interoperability.
+                            mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
+                            p2pConnectWithPinDisplay(mSavedPeerConfig);
+                        } else if (status == P2pStatus.NO_COMMON_CHANNEL) {
+                            transitionTo(mFrequencyConflictState);
+                        } else {
+                            handleGroupCreationFailure();
+                            transitionTo(mInactiveState);
+                        }
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class FrequencyConflictState extends State {
+            private AlertDialog mFrequencyConflictDialog;
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                notifyFrequencyConflict();
+            }
+
+            private void notifyFrequencyConflict() {
+                logd("Notify frequency conflict");
+                Resources r = Resources.getSystem();
+
+                AlertDialog dialog = new AlertDialog.Builder(mContext)
+                        .setMessage(r.getString(R.string.wifi_p2p_frequency_conflict_message,
+                            getDeviceName(mSavedPeerConfig.deviceAddress)))
+                        .setPositiveButton(r.getString(R.string.dlg_ok), new OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                sendMessage(DROP_WIFI_USER_ACCEPT);
+                            }
+                        })
+                        .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                sendMessage(DROP_WIFI_USER_REJECT);
+                            }
+                        })
+                        .setOnCancelListener(new DialogInterface.OnCancelListener() {
+                            @Override
+                            public void onCancel(DialogInterface arg0) {
+                                sendMessage(DROP_WIFI_USER_REJECT);
+                            }
+                        })
+                        .create();
+                dialog.setCanceledOnTouchOutside(false);
+
+                dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+                WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
+                attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+                dialog.getWindow().setAttributes(attrs);
+                dialog.show();
+                mFrequencyConflictDialog = dialog;
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
+                    case WifiP2pMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
+                        loge(getName() + "group sucess during freq conflict!");
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
+                        loge(getName() + "group started after freq conflict, handle anyway");
+                        deferMessage(message);
+                        transitionTo(mGroupNegotiationState);
+                        break;
+                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
+                    case WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
+                    case WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
+                        // Ignore failures since we retry again
+                        break;
+                    case DROP_WIFI_USER_REJECT:
+                        // User rejected dropping wifi in favour of p2p
+                        handleGroupCreationFailure();
+                        transitionTo(mInactiveState);
+                        break;
+                    case DROP_WIFI_USER_ACCEPT:
+                        // User accepted dropping wifi in favour of p2p
+                        if (mWifiChannel != null) {
+                            mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 1);
+                        } else {
+                            loge("DROP_WIFI_USER_ACCEPT message received when WifiChannel is null");
+                        }
+                        mTemporarilyDisconnectedWifi = true;
+                        break;
+                    case DISCONNECT_WIFI_RESPONSE:
+                        // Got a response from wifistatemachine, retry p2p
+                        if (DBG) logd(getName() + "Wifi disconnected, retry p2p");
+                        transitionTo(mInactiveState);
+                        sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            public void exit() {
+                if (mFrequencyConflictDialog != null) mFrequencyConflictDialog.dismiss();
+            }
+        }
+
+        class GroupCreatedState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                // Once connected, peer config details are invalid
+                mSavedPeerConfig.invalidate();
+                mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+
+                updateThisDevice(WifiP2pDevice.CONNECTED);
+
+                // DHCP server has already been started if I am a group owner
+                if (mGroup.isGroupOwner()) {
+                    setWifiP2pInfoOnGroupFormation(
+                            NetworkUtils.numericToInetAddress(SERVER_ADDRESS));
+                }
+
+                // In case of a negotiation group, connection changed is sent
+                // after a client joins. For autonomous, send now
+                if (mAutonomousGroup) {
+                    sendP2pConnectionChangedBroadcast();
+                }
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                WifiP2pDevice device = null;
+                String deviceAddress = null;
+                switch (message.what) {
+                    case WifiP2pMonitor.AP_STA_CONNECTED_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        device = (WifiP2pDevice) message.obj;
+                        deviceAddress = device.deviceAddress;
+                        // Clear timeout that was set when group was started.
+                        mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
+                        if (deviceAddress != null) {
+                            if (mPeers.get(deviceAddress) != null) {
+                                mGroup.addClient(mPeers.get(deviceAddress));
+                            } else {
+                                mGroup.addClient(deviceAddress);
+                            }
+                            mPeers.updateStatus(deviceAddress, WifiP2pDevice.CONNECTED);
+                            if (DBG) logd(getName() + " ap sta connected");
+                            sendPeersChangedBroadcast();
+                        } else {
+                            loge("Connect on null device address, ignore");
+                        }
+                        sendP2pConnectionChangedBroadcast();
+                        break;
+                    case WifiP2pMonitor.AP_STA_DISCONNECTED_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            break;
+                        }
+                        device = (WifiP2pDevice) message.obj;
+                        deviceAddress = device.deviceAddress;
+                        if (deviceAddress != null) {
+                            mPeers.updateStatus(deviceAddress, WifiP2pDevice.AVAILABLE);
+                            if (mGroup.removeClient(deviceAddress)) {
+                                if (DBG) logd("Removed client " + deviceAddress);
+                                if (!mAutonomousGroup && mGroup.isClientListEmpty()) {
+                                    logd("Client list empty, remove non-persistent p2p group");
+                                    mWifiNative.p2pGroupRemove(mGroup.getInterface());
+                                    // We end up sending connection changed broadcast
+                                    // when this happens at exit()
+                                } else {
+                                    // Notify when a client disconnects from group
+                                    sendP2pConnectionChangedBroadcast();
+                                }
+                            } else {
+                                if (DBG) logd("Failed to remove client " + deviceAddress);
+                                for (WifiP2pDevice c : mGroup.getClientList()) {
+                                    if (DBG) logd("client " + c.deviceAddress);
+                                }
+                            }
+                            sendPeersChangedBroadcast();
+                            if (DBG) logd(getName() + " ap sta disconnected");
+                        } else {
+                            loge("Disconnect on unknown device: " + device);
+                        }
+                        break;
+                    case IPM_PRE_DHCP_ACTION:
+                        mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
+                        mIpManager.completedPreDhcpAction();
+                        break;
+                    case IPM_POST_DHCP_ACTION:
+                        mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
+                        break;
+                    case IPM_DHCP_RESULTS:
+                        mDhcpResults = (DhcpResults) message.obj;
+                        break;
+                    case IPM_PROVISIONING_SUCCESS:
+                        if (DBG) logd("mDhcpResults: " + mDhcpResults);
+                        if (mDhcpResults != null) {
+                            setWifiP2pInfoOnGroupFormation(mDhcpResults.serverAddress);
+                        }
+                        sendP2pConnectionChangedBroadcast();
+                        try {
+                            final String ifname = mGroup.getInterface();
+                            if (mDhcpResults != null) {
+                                mNwService.addInterfaceToLocalNetwork(
+                                        ifname, mDhcpResults.getRoutes(ifname));
+                            }
+                        } catch (RemoteException e) {
+                            loge("Failed to add iface to local network " + e);
+                        }
+                        break;
+                    case IPM_PROVISIONING_FAILURE:
+                        loge("IP provisioning failed");
+                        mWifiNative.p2pGroupRemove(mGroup.getInterface());
+                        break;
+                    case WifiP2pManager.REMOVE_GROUP:
+                        if (DBG) logd(getName() + " remove group");
+                        if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) {
+                            transitionTo(mOngoingGroupRemovalState);
+                            replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
+                        } else {
+                            handleGroupRemoved();
+                            transitionTo(mInactiveState);
+                            replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
+                                    WifiP2pManager.ERROR);
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
+                        // We do not listen to NETWORK_DISCONNECTION_EVENT for group removal
+                        // handling since supplicant actually tries to reconnect after a temporary
+                        // disconnect until group idle time out. Eventually, a group removal event
+                        // will come when group has been removed.
+                        //
+                        // When there are connectivity issues during temporary disconnect,
+                        // the application will also just remove the group.
+                        //
+                        // Treating network disconnection as group removal causes race conditions
+                        // since supplicant would still maintain the group at that stage.
+                        if (DBG) logd(getName() + " group removed");
+                        handleGroupRemoved();
+                        transitionTo(mInactiveState);
+                        break;
+                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
+                        if (message.obj == null) {
+                            Log.e(TAG, "Illegal argument(s)");
+                            return NOT_HANDLED;
+                        }
+                        device = (WifiP2pDevice) message.obj;
+                        if (!mGroup.contains(device)) {
+                            // do the regular device lost handling
+                            return NOT_HANDLED;
+                        }
+                        // Device loss for a connected device indicates
+                        // it is not in discovery any more
+                        if (DBG) logd("Add device to lost list " + device);
+                        mPeersLostDuringConnection.updateSupplicantDetails(device);
+                        return HANDLED;
+                    case WifiStateMachine.CMD_DISABLE_P2P_REQ:
+                        sendMessage(WifiP2pManager.REMOVE_GROUP);
+                        deferMessage(message);
+                        break;
+                        // This allows any client to join the GO during the
+                        // WPS window
+                    case WifiP2pManager.START_WPS:
+                        WpsInfo wps = (WpsInfo) message.obj;
+                        if (wps == null) {
+                            replyToMessage(message, WifiP2pManager.START_WPS_FAILED);
+                            break;
+                        }
+                        boolean ret = true;
+                        if (wps.setup == WpsInfo.PBC) {
+                            ret = mWifiNative.startWpsPbc(mGroup.getInterface(), null);
+                        } else {
+                            if (wps.pin == null) {
+                                String pin = mWifiNative.startWpsPinDisplay(
+                                        mGroup.getInterface(), null);
+                                try {
+                                    Integer.parseInt(pin);
+                                    notifyInvitationSent(pin, "any");
+                                } catch (NumberFormatException ignore) {
+                                    ret = false;
+                                }
+                            } else {
+                                ret = mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
+                                        wps.pin);
+                            }
+                        }
+                        replyToMessage(message, ret ? WifiP2pManager.START_WPS_SUCCEEDED :
+                                WifiP2pManager.START_WPS_FAILED);
+                        break;
+                    case WifiP2pManager.CONNECT:
+                        WifiP2pConfig config = (WifiP2pConfig) message.obj;
+                        if (isConfigInvalid(config)) {
+                            loge("Dropping connect request " + config);
+                            replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
+                            break;
+                        }
+                        logd("Inviting device : " + config.deviceAddress);
+                        mSavedPeerConfig = config;
+                        if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) {
+                            mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED);
+                            sendPeersChangedBroadcast();
+                            replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
+                        } else {
+                            replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
+                                    WifiP2pManager.ERROR);
+                        }
+                        // TODO: figure out updating the status to declined
+                        // when invitation is rejected
+                        break;
+                    case WifiP2pMonitor.P2P_INVITATION_RESULT_EVENT:
+                        P2pStatus status = (P2pStatus) message.obj;
+                        if (status == P2pStatus.SUCCESS) {
+                            // invocation was succeeded.
+                            break;
+                        }
+                        loge("Invitation result " + status);
+                        if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
+                            // target device has already removed the credential.
+                            // So, remove this credential accordingly.
+                            int netId = mGroup.getNetworkId();
+                            if (netId >= 0) {
+                                if (DBG) logd("Remove unknown client from the list");
+                                if (!removeClientFromList(netId,
+                                        mSavedPeerConfig.deviceAddress, false)) {
+                                    // not found the client on the list
+                                    loge("Already removed the client, ignore");
+                                    break;
+                                }
+                                // try invitation.
+                                sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
+                            }
+                        }
+                        break;
+                    case WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+                    case WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+                    case WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+                        WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
+                        mSavedPeerConfig = new WifiP2pConfig();
+                        if (provDisc != null && provDisc.device != null) {
+                            mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress;
+                        }
+                        if (message.what == WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) {
+                            mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
+                        } else if (message.what == WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) {
+                            mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
+                            mSavedPeerConfig.wps.pin = provDisc.pin;
+                        } else {
+                            mSavedPeerConfig.wps.setup = WpsInfo.PBC;
+                        }
+                        transitionTo(mUserAuthorizingJoinState);
+                        break;
+                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
+                        loge("Duplicate group creation event notice, ignore");
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            public void exit() {
+                updateThisDevice(WifiP2pDevice.AVAILABLE);
+                resetWifiP2pInfo();
+                mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
+                sendP2pConnectionChangedBroadcast();
+            }
+        }
+
+        class UserAuthorizingJoinState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+                notifyInvitationReceived();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    case WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
+                    case WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
+                    case WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
+                        // Ignore more client requests
+                        break;
+                    case PEER_CONNECTION_USER_ACCEPT:
+                        // Stop discovery to avoid failure due to channel switch
+                        mWifiNative.p2pStopFind();
+                        if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
+                            mWifiNative.startWpsPbc(mGroup.getInterface(), null);
+                        } else {
+                            mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
+                                    mSavedPeerConfig.wps.pin);
+                        }
+                        transitionTo(mGroupCreatedState);
+                        break;
+                    case PEER_CONNECTION_USER_REJECT:
+                        if (DBG) logd("User rejected incoming request");
+                        transitionTo(mGroupCreatedState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+
+            @Override
+            public void exit() {
+                // TODO: dismiss dialog if not already done
+            }
+        }
+
+        class OngoingGroupRemovalState extends State {
+            @Override
+            public void enter() {
+                if (DBG) logd(getName());
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) logd(getName() + message.toString());
+                switch (message.what) {
+                    // Group removal ongoing. Multiple calls
+                    // end up removing persisted network. Do nothing.
+                    case WifiP2pManager.REMOVE_GROUP:
+                        replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
+                        break;
+                    // Parent state will transition out of this state
+                    // when removal is complete
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
         @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            notifyFrequencyConflict();
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            super.dump(fd, pw, args);
+            pw.println("mWifiP2pInfo " + mWifiP2pInfo);
+            pw.println("mGroup " + mGroup);
+            pw.println("mSavedPeerConfig " + mSavedPeerConfig);
+            pw.println("mGroups" + mGroups);
+            pw.println();
         }
 
-        private void notifyFrequencyConflict() {
-            logd("Notify frequency conflict");
+        private void sendP2pStateChangedBroadcast(boolean enabled) {
+            final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            if (enabled) {
+                intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+                        WifiP2pManager.WIFI_P2P_STATE_ENABLED);
+            } else {
+                intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+                        WifiP2pManager.WIFI_P2P_STATE_DISABLED);
+            }
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void sendP2pDiscoveryChangedBroadcast(boolean started) {
+            if (mDiscoveryStarted == started) return;
+            mDiscoveryStarted = started;
+
+            if (DBG) logd("discovery change broadcast " + started);
+
+            final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            intent.putExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, started
+                    ? WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED :
+                    WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void sendThisDeviceChangedBroadcast() {
+            final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE, new WifiP2pDevice(mThisDevice));
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void sendPeersChangedBroadcast() {
+            final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+            intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, new WifiP2pDeviceList(mPeers));
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void sendP2pConnectionChangedBroadcast() {
+            if (DBG) logd("sending p2p connection changed broadcast");
+            Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                    | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+            intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, new WifiP2pInfo(mWifiP2pInfo));
+            intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
+            intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, new WifiP2pGroup(mGroup));
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            if (mWifiChannel != null) {
+                mWifiChannel.sendMessage(WifiP2pServiceImpl.P2P_CONNECTION_CHANGED,
+                        new NetworkInfo(mNetworkInfo));
+            } else {
+                loge("sendP2pConnectionChangedBroadcast(): WifiChannel is null");
+            }
+        }
+
+        private void sendP2pPersistentGroupsChangedBroadcast() {
+            if (DBG) logd("sending p2p persistent groups changed broadcast");
+            Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void startDhcpServer(String intf) {
+            InterfaceConfiguration ifcg = null;
+            try {
+                ifcg = mNwService.getInterfaceConfig(intf);
+                ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(
+                            SERVER_ADDRESS), 24));
+                ifcg.setInterfaceUp();
+                mNwService.setInterfaceConfig(intf, ifcg);
+                // This starts the dnsmasq server
+                ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+                        Context.CONNECTIVITY_SERVICE);
+                String[] tetheringDhcpRanges = cm.getTetheredDhcpRanges();
+                if (mNwService.isTetheringStarted()) {
+                    if (DBG) logd("Stop existing tethering and restart it");
+                    mNwService.stopTethering();
+                }
+                mNwService.tetherInterface(intf);
+                mNwService.startTethering(tetheringDhcpRanges);
+            } catch (Exception e) {
+                loge("Error configuring interface " + intf + ", :" + e);
+                return;
+            }
+
+            logd("Started Dhcp server on " + intf);
+        }
+
+        private void stopDhcpServer(String intf) {
+            try {
+                mNwService.untetherInterface(intf);
+                for (String temp : mNwService.listTetheredInterfaces()) {
+                    logd("List all interfaces " + temp);
+                    if (temp.compareTo(intf) != 0) {
+                        logd("Found other tethering interfaces, so keep tethering alive");
+                        return;
+                    }
+                }
+                mNwService.stopTethering();
+            } catch (Exception e) {
+                loge("Error stopping Dhcp server" + e);
+                return;
+            } finally {
+                logd("Stopped Dhcp server");
+            }
+        }
+
+        private void notifyP2pEnableFailure() {
+            Resources r = Resources.getSystem();
+            AlertDialog dialog = new AlertDialog.Builder(mContext)
+                    .setTitle(r.getString(R.string.wifi_p2p_dialog_title))
+                    .setMessage(r.getString(R.string.wifi_p2p_failed_message))
+                    .setPositiveButton(r.getString(R.string.ok), null)
+                    .create();
+            dialog.setCanceledOnTouchOutside(false);
+            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
+            attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            dialog.getWindow().setAttributes(attrs);
+            dialog.show();
+        }
+
+        private void addRowToDialog(ViewGroup group, int stringId, String value) {
+            Resources r = Resources.getSystem();
+            View row = LayoutInflater.from(mContext).inflate(R.layout.wifi_p2p_dialog_row,
+                    group, false);
+            ((TextView) row.findViewById(R.id.name)).setText(r.getString(stringId));
+            ((TextView) row.findViewById(R.id.value)).setText(value);
+            group.addView(row);
+        }
+
+        private void notifyInvitationSent(String pin, String peerAddress) {
             Resources r = Resources.getSystem();
 
+            final View textEntryView = LayoutInflater.from(mContext)
+                    .inflate(R.layout.wifi_p2p_dialog, null);
+
+            ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
+            addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress));
+            addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin);
+
             AlertDialog dialog = new AlertDialog.Builder(mContext)
-                .setMessage(r.getString(R.string.wifi_p2p_frequency_conflict_message,
-                        getDeviceName(mSavedPeerConfig.deviceAddress)))
-                .setPositiveButton(r.getString(R.string.dlg_ok), new OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            sendMessage(DROP_WIFI_USER_ACCEPT);
+                    .setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title))
+                    .setView(textEntryView)
+                    .setPositiveButton(r.getString(R.string.ok), null)
+                    .create();
+            dialog.setCanceledOnTouchOutside(false);
+            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
+            attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            dialog.getWindow().setAttributes(attrs);
+            dialog.show();
+        }
+
+        private void notifyInvitationReceived() {
+            Resources r = Resources.getSystem();
+            final WpsInfo wps = mSavedPeerConfig.wps;
+            final View textEntryView = LayoutInflater.from(mContext)
+                    .inflate(R.layout.wifi_p2p_dialog, null);
+
+            ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
+            addRowToDialog(group, R.string.wifi_p2p_from_message, getDeviceName(
+                    mSavedPeerConfig.deviceAddress));
+
+            final EditText pin = (EditText) textEntryView.findViewById(R.id.wifi_p2p_wps_pin);
+
+            AlertDialog dialog = new AlertDialog.Builder(mContext)
+                    .setTitle(r.getString(R.string.wifi_p2p_invitation_to_connect_title))
+                    .setView(textEntryView)
+                    .setPositiveButton(r.getString(R.string.accept), new OnClickListener() {
+                            public void onClick(DialogInterface dialog, int which) {
+                                if (wps.setup == WpsInfo.KEYPAD) {
+                                    mSavedPeerConfig.wps.pin = pin.getText().toString();
+                                }
+                                if (DBG) logd(getName() + " accept invitation " + mSavedPeerConfig);
+                                sendMessage(PEER_CONNECTION_USER_ACCEPT);
+                            }
+                        })
+                    .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                if (DBG) logd(getName() + " ignore connect");
+                                sendMessage(PEER_CONNECTION_USER_REJECT);
+                            }
+                        })
+                    .setOnCancelListener(new DialogInterface.OnCancelListener() {
+                            @Override
+                            public void onCancel(DialogInterface arg0) {
+                                if (DBG) logd(getName() + " ignore connect");
+                                sendMessage(PEER_CONNECTION_USER_REJECT);
+                            }
+                        })
+                    .create();
+            dialog.setCanceledOnTouchOutside(false);
+
+            // make the enter pin area or the display pin area visible
+            switch (wps.setup) {
+                case WpsInfo.KEYPAD:
+                    if (DBG) logd("Enter pin section visible");
+                    textEntryView.findViewById(R.id.enter_pin_section).setVisibility(View.VISIBLE);
+                    break;
+                case WpsInfo.DISPLAY:
+                    if (DBG) logd("Shown pin section visible");
+                    addRowToDialog(group, R.string.wifi_p2p_show_pin_message, wps.pin);
+                    break;
+                default:
+                    break;
+            }
+
+            if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_APPLIANCE)
+                    == Configuration.UI_MODE_TYPE_APPLIANCE) {
+                // For appliance devices, add a key listener which accepts.
+                dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
+
+                    @Override
+                    public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+                        // TODO: make the actual key come from a config value.
+                        if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+                            sendMessage(PEER_CONNECTION_USER_ACCEPT);
+                            dialog.dismiss();
+                            return true;
                         }
-                    })
-                .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            sendMessage(DROP_WIFI_USER_REJECT);
-                        }
-                    })
-                .setOnCancelListener(new DialogInterface.OnCancelListener() {
-                        @Override
-                        public void onCancel(DialogInterface arg0) {
-                            sendMessage(DROP_WIFI_USER_REJECT);
-                        }
-                    })
-                .create();
+                        return false;
+                    }
+                });
+                // TODO: add timeout for this dialog.
+                // TODO: update UI in appliance mode to tell user what to do.
+            }
 
             dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
             WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
             attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
             dialog.getWindow().setAttributes(attrs);
             dialog.show();
-            mFrequencyConflictDialog = dialog;
         }
 
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
-                case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
-                    loge(getName() + "group sucess during freq conflict!");
-                    break;
-                case WifiMonitor.P2P_GROUP_STARTED_EVENT:
-                    loge(getName() + "group started after freq conflict, handle anyway");
-                    deferMessage(message);
-                    transitionTo(mGroupNegotiationState);
-                    break;
-                case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
-                case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
-                case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
-                    // Ignore failures since we retry again
-                    break;
-                case DROP_WIFI_USER_REJECT:
-                    // User rejected dropping wifi in favour of p2p
-                    handleGroupCreationFailure();
-                    transitionTo(mInactiveState);
-                    break;
-                case DROP_WIFI_USER_ACCEPT:
-                    // User accepted dropping wifi in favour of p2p
-                    mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 1);
-                    mTemporarilyDisconnectedWifi = true;
-                    break;
-                case DISCONNECT_WIFI_RESPONSE:
-                    // Got a response from wifistatemachine, retry p2p
-                    if (DBG) logd(getName() + "Wifi disconnected, retry p2p");
-                    transitionTo(mInactiveState);
-                    sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
+        /**
+         * This method unifies the persisent group list, cleans up unused
+         * networks and if required, updates corresponding broadcast receivers
+         * @param boolean if true, reload the group list from scratch
+         *                and send broadcast message with fresh list
+         */
+        private void updatePersistentNetworks(boolean reload) {
+            if (reload) mGroups.clear();
 
-        public void exit() {
-            if (mFrequencyConflictDialog != null) mFrequencyConflictDialog.dismiss();
-        }
-    }
-
-    class GroupCreatedState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            // Once connected, peer config details are invalid
-            mSavedPeerConfig.invalidate();
-            mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
-
-            updateThisDevice(WifiP2pDevice.CONNECTED);
-
-            //DHCP server has already been started if I am a group owner
-            if (mGroup.isGroupOwner()) {
-                setWifiP2pInfoOnGroupFormation(NetworkUtils.numericToInetAddress(SERVER_ADDRESS));
-            }
-
-            // In case of a negotiation group, connection changed is sent
-            // after a client joins. For autonomous, send now
-            if (mAutonomousGroup) {
-                sendP2pConnectionChangedBroadcast();
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiMonitor.AP_STA_CONNECTED_EVENT:
-                    WifiP2pDevice device = (WifiP2pDevice) message.obj;
-                    String deviceAddress = device.deviceAddress;
-                    // Clear timeout that was set when group was started.
-                    mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
-                    if (deviceAddress != null) {
-                        if (mPeers.get(deviceAddress) != null) {
-                            mGroup.addClient(mPeers.get(deviceAddress));
-                        } else {
-                            mGroup.addClient(deviceAddress);
-                        }
-                        mPeers.updateStatus(deviceAddress, WifiP2pDevice.CONNECTED);
-                        if (DBG) logd(getName() + " ap sta connected");
-                        sendPeersChangedBroadcast();
-                    } else {
-                        loge("Connect on null device address, ignore");
+            // Save in all cases, including when reload was requested, but
+            // no network has been found.
+            if (mWifiNative.p2pListNetworks(mGroups) || reload) {
+                for (WifiP2pGroup group : mGroups.getGroupList()) {
+                    if (mThisDevice.deviceAddress.equals(group.getOwner().deviceAddress)) {
+                        group.setOwner(mThisDevice);
                     }
-                    sendP2pConnectionChangedBroadcast();
-                    break;
-                case WifiMonitor.AP_STA_DISCONNECTED_EVENT:
-                    device = (WifiP2pDevice) message.obj;
-                    deviceAddress = device.deviceAddress;
-                    if (deviceAddress != null) {
-                        mPeers.updateStatus(deviceAddress, WifiP2pDevice.AVAILABLE);
-                        if (mGroup.removeClient(deviceAddress)) {
-                            if (DBG) logd("Removed client " + deviceAddress);
-                            if (!mAutonomousGroup && mGroup.isClientListEmpty()) {
-                                logd("Client list empty, remove non-persistent p2p group");
-                                mWifiNative.p2pGroupRemove(mGroup.getInterface());
-                                // We end up sending connection changed broadcast
-                                // when this happens at exit()
-                            } else {
-                                // Notify when a client disconnects from group
-                                sendP2pConnectionChangedBroadcast();
-                            }
-                        } else {
-                            if (DBG) logd("Failed to remove client " + deviceAddress);
-                            for (WifiP2pDevice c : mGroup.getClientList()) {
-                                if (DBG) logd("client " + c.deviceAddress);
-                            }
-                        }
-                        sendPeersChangedBroadcast();
-                        if (DBG) logd(getName() + " ap sta disconnected");
-                    } else {
-                        loge("Disconnect on unknown device: " + device);
-                    }
-                    break;
-                case IPM_PRE_DHCP_ACTION:
-                    mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
-                    mIpManager.completedPreDhcpAction();
-                    break;
-                case IPM_POST_DHCP_ACTION:
-                    mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
-                    break;
-                case IPM_DHCP_RESULTS:
-                    mDhcpResults = (DhcpResults) message.obj;
-                    break;
-                case IPM_PROVISIONING_SUCCESS:
-                    if (DBG) logd("mDhcpResults: " + mDhcpResults);
-                    setWifiP2pInfoOnGroupFormation(mDhcpResults.serverAddress);
-                    sendP2pConnectionChangedBroadcast();
-                    try {
-                        final String ifname = mGroup.getInterface();
-                        mNwService.addInterfaceToLocalNetwork(
-                                ifname, mDhcpResults.getRoutes(ifname));
-                    } catch (RemoteException e) {
-                        loge("Failed to add iface to local network " + e);
-                    }
-                    break;
-                case IPM_PROVISIONING_FAILURE:
-                    loge("IP provisioning failed");
-                    mWifiNative.p2pGroupRemove(mGroup.getInterface());
-                    break;
-                case WifiP2pManager.REMOVE_GROUP:
-                    if (DBG) logd(getName() + " remove group");
-                    if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) {
-                        transitionTo(mOngoingGroupRemovalState);
-                        replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
-                    } else {
-                        handleGroupRemoved();
-                        transitionTo(mInactiveState);
-                        replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    break;
-                /* We do not listen to NETWORK_DISCONNECTION_EVENT for group removal
-                 * handling since supplicant actually tries to reconnect after a temporary
-                 * disconnect until group idle time out. Eventually, a group removal event
-                 * will come when group has been removed.
-                 *
-                 * When there are connectivity issues during temporary disconnect, the application
-                 * will also just remove the group.
-                 *
-                 * Treating network disconnection as group removal causes race conditions since
-                 * supplicant would still maintain the group at that stage.
-                 */
-                case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
-                    if (DBG) logd(getName() + " group removed");
-                    handleGroupRemoved();
-                    transitionTo(mInactiveState);
-                    break;
-                case WifiMonitor.P2P_DEVICE_LOST_EVENT:
-                    device = (WifiP2pDevice) message.obj;
-                    //Device loss for a connected device indicates it is not in discovery any more
-                    if (mGroup.contains(device)) {
-                        if (DBG) logd("Add device to lost list " + device);
-                        mPeersLostDuringConnection.updateSupplicantDetails(device);
-                        return HANDLED;
-                    }
-                    // Do the regular device lost handling
-                    return NOT_HANDLED;
-                case WifiStateMachine.CMD_DISABLE_P2P_REQ:
-                    sendMessage(WifiP2pManager.REMOVE_GROUP);
-                    deferMessage(message);
-                    break;
-                    // This allows any client to join the GO during the
-                    // WPS window
-                case WifiP2pManager.START_WPS:
-                    WpsInfo wps = (WpsInfo) message.obj;
-                    if (wps == null) {
-                        replyToMessage(message, WifiP2pManager.START_WPS_FAILED);
-                        break;
-                    }
-                    boolean ret = true;
-                    if (wps.setup == WpsInfo.PBC) {
-                        ret = mWifiNative.startWpsPbc(mGroup.getInterface(), null);
-                    } else {
-                        if (wps.pin == null) {
-                            String pin = mWifiNative.startWpsPinDisplay(mGroup.getInterface());
-                            try {
-                                Integer.parseInt(pin);
-                                notifyInvitationSent(pin, "any");
-                            } catch (NumberFormatException ignore) {
-                                ret = false;
-                            }
-                        } else {
-                            ret = mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
-                                    wps.pin);
-                        }
-                    }
-                    replyToMessage(message, ret ? WifiP2pManager.START_WPS_SUCCEEDED :
-                            WifiP2pManager.START_WPS_FAILED);
-                    break;
-                case WifiP2pManager.CONNECT:
-                    WifiP2pConfig config = (WifiP2pConfig) message.obj;
-                    if (isConfigInvalid(config)) {
-                        loge("Dropping connect requeset " + config);
-                        replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
-                        break;
-                    }
-                    logd("Inviting device : " + config.deviceAddress);
-                    mSavedPeerConfig = config;
-                    if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) {
-                        mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED);
-                        sendPeersChangedBroadcast();
-                        replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
-                                WifiP2pManager.ERROR);
-                    }
-                    // TODO: figure out updating the status to declined when invitation is rejected
-                    break;
-                case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
-                    P2pStatus status = (P2pStatus)message.obj;
-                    if (status == P2pStatus.SUCCESS) {
-                        // invocation was succeeded.
-                        break;
-                    }
-                    loge("Invitation result " + status);
-                    if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
-                        // target device has already removed the credential.
-                        // So, remove this credential accordingly.
-                        int netId = mGroup.getNetworkId();
-                        if (netId >= 0) {
-                            if (DBG) logd("Remove unknown client from the list");
-                            if (!removeClientFromList(netId,
-                                    mSavedPeerConfig.deviceAddress, false)) {
-                                // not found the client on the list
-                                loge("Already removed the client, ignore");
-                                break;
-                            }
-                            // try invitation.
-                            sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
-                        }
-                    }
-                    break;
-                case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
-                case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
-                case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
-                    WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
-                    mSavedPeerConfig = new WifiP2pConfig();
-                    mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress;
-                    if (message.what == WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) {
-                        mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
-                    } else if (message.what == WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) {
-                        mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
-                        mSavedPeerConfig.wps.pin = provDisc.pin;
-                    } else {
-                        mSavedPeerConfig.wps.setup = WpsInfo.PBC;
-                    }
-                    transitionTo(mUserAuthorizingJoinState);
-                    break;
-                case WifiMonitor.P2P_GROUP_STARTED_EVENT:
-                    loge("Duplicate group creation event notice, ignore");
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-
-        public void exit() {
-            updateThisDevice(WifiP2pDevice.AVAILABLE);
-            resetWifiP2pInfo();
-            mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
-            sendP2pConnectionChangedBroadcast();
-        }
-    }
-
-    class UserAuthorizingJoinState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-            notifyInvitationReceived();
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
-                case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
-                case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
-                    //Ignore more client requests
-                    break;
-                case PEER_CONNECTION_USER_ACCEPT:
-                    //Stop discovery to avoid failure due to channel switch
-                    mWifiNative.p2pStopFind();
-                    if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
-                        mWifiNative.startWpsPbc(mGroup.getInterface(), null);
-                    } else {
-                        mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
-                                mSavedPeerConfig.wps.pin);
-                    }
-                    transitionTo(mGroupCreatedState);
-                    break;
-                case PEER_CONNECTION_USER_REJECT:
-                    if (DBG) logd("User rejected incoming request");
-                    transitionTo(mGroupCreatedState);
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-
-        @Override
-        public void exit() {
-            //TODO: dismiss dialog if not already done
-        }
-    }
-
-    class OngoingGroupRemovalState extends State {
-        @Override
-        public void enter() {
-            if (DBG) logd(getName());
-        }
-
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) logd(getName() + message.toString());
-            switch (message.what) {
-                // Group removal ongoing. Multiple calls
-                // end up removing persisted network. Do nothing.
-                case WifiP2pManager.REMOVE_GROUP:
-                    replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
-                    break;
-                // Parent state will transition out of this state
-                // when removal is complete
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
-        pw.println("mWifiP2pInfo " + mWifiP2pInfo);
-        pw.println("mGroup " + mGroup);
-        pw.println("mSavedPeerConfig " + mSavedPeerConfig);
-        pw.println();
-    }
-
-    private void sendP2pStateChangedBroadcast(boolean enabled) {
-        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        if (enabled) {
-            intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
-                    WifiP2pManager.WIFI_P2P_STATE_ENABLED);
-        } else {
-            intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
-                    WifiP2pManager.WIFI_P2P_STATE_DISABLED);
-        }
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    private void sendP2pDiscoveryChangedBroadcast(boolean started) {
-        if (mDiscoveryStarted == started) return;
-        mDiscoveryStarted = started;
-
-        if (DBG) logd("discovery change broadcast " + started);
-
-        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, started ?
-                WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED :
-                WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    private void sendThisDeviceChangedBroadcast() {
-        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE, new WifiP2pDevice(mThisDevice));
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    private void sendPeersChangedBroadcast() {
-        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
-        intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, new WifiP2pDeviceList(mPeers));
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    private void sendP2pConnectionChangedBroadcast() {
-        if (DBG) logd("sending p2p connection changed broadcast");
-        Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, new WifiP2pInfo(mWifiP2pInfo));
-        intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
-        intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, new WifiP2pGroup(mGroup));
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-        mWifiChannel.sendMessage(WifiP2pServiceImpl.P2P_CONNECTION_CHANGED,
-                new NetworkInfo(mNetworkInfo));
-    }
-
-    private void sendP2pPersistentGroupsChangedBroadcast() {
-        if (DBG) logd("sending p2p persistent groups changed broadcast");
-        Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    private void startDhcpServer(String intf) {
-        InterfaceConfiguration ifcg = null;
-        try {
-            ifcg = mNwService.getInterfaceConfig(intf);
-            ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(
-                        SERVER_ADDRESS), 24));
-            ifcg.setInterfaceUp();
-            mNwService.setInterfaceConfig(intf, ifcg);
-            /* This starts the dnsmasq server */
-            ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
-                    Context.CONNECTIVITY_SERVICE);
-            String[] tetheringDhcpRanges = cm.getTetheredDhcpRanges();
-            if (mNwService.isTetheringStarted()) {
-                if (DBG) logd("Stop existing tethering and restart it");
-                mNwService.stopTethering();
-            }
-            mNwService.tetherInterface(intf);
-            mNwService.startTethering(tetheringDhcpRanges);
-        } catch (Exception e) {
-            loge("Error configuring interface " + intf + ", :" + e);
-            return;
-        }
-
-        logd("Started Dhcp server on " + intf);
-   }
-
-    private void stopDhcpServer(String intf) {
-        try {
-            mNwService.untetherInterface(intf);
-            for (String temp : mNwService.listTetheredInterfaces()) {
-                logd("List all interfaces " + temp);
-                if (temp.compareTo(intf) != 0) {
-                    logd("Found other tethering interfaces, so keep tethering alive");
-                    return;
                 }
+                mWifiNative.saveConfig();
+                sendP2pPersistentGroupsChangedBroadcast();
             }
-            mNwService.stopTethering();
-        } catch (Exception e) {
-            loge("Error stopping Dhcp server" + e);
-            return;
-        } finally {
-            logd("Stopped Dhcp server");
-        }
-    }
-
-    private void notifyP2pEnableFailure() {
-        Resources r = Resources.getSystem();
-        AlertDialog dialog = new AlertDialog.Builder(mContext)
-            .setTitle(r.getString(R.string.wifi_p2p_dialog_title))
-            .setMessage(r.getString(R.string.wifi_p2p_failed_message))
-            .setPositiveButton(r.getString(R.string.ok), null)
-            .create();
-        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-        WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
-        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-        dialog.getWindow().setAttributes(attrs);
-        dialog.show();
-    }
-
-    private void addRowToDialog(ViewGroup group, int stringId, String value) {
-        Resources r = Resources.getSystem();
-        View row = LayoutInflater.from(mContext).inflate(R.layout.wifi_p2p_dialog_row,
-                group, false);
-        ((TextView) row.findViewById(R.id.name)).setText(r.getString(stringId));
-        ((TextView) row.findViewById(R.id.value)).setText(value);
-        group.addView(row);
-    }
-
-    private void notifyInvitationSent(String pin, String peerAddress) {
-        Resources r = Resources.getSystem();
-
-        final View textEntryView = LayoutInflater.from(mContext)
-                .inflate(R.layout.wifi_p2p_dialog, null);
-
-        ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
-        addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress));
-        addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin);
-
-        AlertDialog dialog = new AlertDialog.Builder(mContext)
-            .setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title))
-            .setView(textEntryView)
-            .setPositiveButton(r.getString(R.string.ok), null)
-            .create();
-        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-        WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
-        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-        dialog.getWindow().setAttributes(attrs);
-        dialog.show();
-    }
-
-    private void notifyInvitationReceived() {
-        Resources r = Resources.getSystem();
-        final WpsInfo wps = mSavedPeerConfig.wps;
-        final View textEntryView = LayoutInflater.from(mContext)
-                .inflate(R.layout.wifi_p2p_dialog, null);
-
-        ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
-        addRowToDialog(group, R.string.wifi_p2p_from_message, getDeviceName(
-                mSavedPeerConfig.deviceAddress));
-
-        final EditText pin = (EditText) textEntryView.findViewById(R.id.wifi_p2p_wps_pin);
-
-        AlertDialog dialog = new AlertDialog.Builder(mContext)
-            .setTitle(r.getString(R.string.wifi_p2p_invitation_to_connect_title))
-            .setView(textEntryView)
-            .setPositiveButton(r.getString(R.string.accept), new OnClickListener() {
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (wps.setup == WpsInfo.KEYPAD) {
-                                mSavedPeerConfig.wps.pin = pin.getText().toString();
-                            }
-                            if (DBG) logd(getName() + " accept invitation " + mSavedPeerConfig);
-                            sendMessage(PEER_CONNECTION_USER_ACCEPT);
-                        }
-                    })
-            .setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
-                        @Override
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (DBG) logd(getName() + " ignore connect");
-                            sendMessage(PEER_CONNECTION_USER_REJECT);
-                        }
-                    })
-            .setOnCancelListener(new DialogInterface.OnCancelListener() {
-                        @Override
-                        public void onCancel(DialogInterface arg0) {
-                            if (DBG) logd(getName() + " ignore connect");
-                            sendMessage(PEER_CONNECTION_USER_REJECT);
-                        }
-                    })
-            .create();
-
-        //make the enter pin area or the display pin area visible
-        switch (wps.setup) {
-            case WpsInfo.KEYPAD:
-                if (DBG) logd("Enter pin section visible");
-                textEntryView.findViewById(R.id.enter_pin_section).setVisibility(View.VISIBLE);
-                break;
-            case WpsInfo.DISPLAY:
-                if (DBG) logd("Shown pin section visible");
-                addRowToDialog(group, R.string.wifi_p2p_show_pin_message, wps.pin);
-                break;
-            default:
-                break;
         }
 
-        if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_APPLIANCE) ==
-                Configuration.UI_MODE_TYPE_APPLIANCE) {
-            // For appliance devices, add a key listener which accepts.
-            dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
-
-                @Override
-                public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
-                    // TODO: make the actual key come from a config value.
-                    if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
-                        sendMessage(PEER_CONNECTION_USER_ACCEPT);
-                        dialog.dismiss();
-                        return true;
-                    }
-                    return false;
-                }
-            });
-            // TODO: add timeout for this dialog.
-            // TODO: update UI in appliance mode to tell user what to do.
+        /**
+         * A config is valid if it has a peer address that has already been
+         * discovered
+         * @param WifiP2pConfig config to be validated
+         * @return true if it is invalid, false otherwise
+         */
+        private boolean isConfigInvalid(WifiP2pConfig config) {
+            if (config == null) return true;
+            if (TextUtils.isEmpty(config.deviceAddress)) return true;
+            if (mPeers.get(config.deviceAddress) == null) return true;
+            return false;
         }
 
-        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-        WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
-        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-        dialog.getWindow().setAttributes(attrs);
-        dialog.show();
-    }
+        private WifiP2pDevice fetchCurrentDeviceDetails(WifiP2pConfig config) {
+            if (config == null) return null;
+            // Fetch & update group capability from supplicant on the device
+            int gc = mWifiNative.getGroupCapability(config.deviceAddress);
+            // TODO: The supplicant does not provide group capability changes as an event.
+            // Having it pushed as an event would avoid polling for this information right
+            // before a connection
+            mPeers.updateGroupCapability(config.deviceAddress, gc);
+            return mPeers.get(config.deviceAddress);
+        }
 
-    /**
-     * Synchronize the persistent group list between
-     * wpa_supplicant and mGroups.
-     */
-    private void updatePersistentNetworks(boolean reload) {
-        String listStr = mWifiNative.listNetworks();
-        if (listStr == null) return;
-
-        boolean isSaveRequired = false;
-        String[] lines = listStr.split("\n");
-        if (lines == null) return;
-
-        if (reload) mGroups.clear();
-
-        // Skip the first line, which is a header
-        for (int i = 1; i < lines.length; i++) {
-            String[] result = lines[i].split("\t");
-            if (result == null || result.length < 4) {
-                continue;
+        /**
+         * Start a p2p group negotiation and display pin if necessary
+         * @param config for the peer
+         */
+        private void p2pConnectWithPinDisplay(WifiP2pConfig config) {
+            if (config == null) {
+                Log.e(TAG, "Illegal argument(s)");
+                return;
             }
-            // network-id | ssid | bssid | flags
-            int netId = -1;
-            String ssid = result[1];
-            String bssid = result[2];
-            String flags = result[3];
+            WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
+            if (dev == null) {
+                Log.e(TAG, "Invalid device");
+                return;
+            }
+            String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner());
             try {
-                netId = Integer.parseInt(result[0]);
-            } catch(NumberFormatException e) {
-                e.printStackTrace();
-                continue;
+                Integer.parseInt(pin);
+                notifyInvitationSent(pin, config.deviceAddress);
+            } catch (NumberFormatException ignore) {
+                // do nothing if p2pConnect did not return a pin
             }
-
-            if (flags.indexOf("[CURRENT]") != -1) {
-                continue;
-            }
-            if (flags.indexOf("[P2P-PERSISTENT]") == -1) {
-                /*
-                 * The unused profile is sometimes remained when the p2p group formation is failed.
-                 * So, we clean up the p2p group here.
-                 */
-                if (DBG) logd("clean up the unused persistent group. netId=" + netId);
-                mWifiNative.removeNetwork(netId);
-                isSaveRequired = true;
-                continue;
-            }
-
-            if (mGroups.contains(netId)) {
-                continue;
-            }
-
-            WifiP2pGroup group = new WifiP2pGroup();
-            group.setNetworkId(netId);
-            group.setNetworkName(ssid);
-            String mode = mWifiNative.getNetworkVariable(netId, "mode");
-            if (mode != null && mode.equals("3")) {
-                group.setIsGroupOwner(true);
-            }
-            if (bssid.equalsIgnoreCase(mThisDevice.deviceAddress)) {
-                group.setOwner(mThisDevice);
-            } else {
-                WifiP2pDevice device = new WifiP2pDevice();
-                device.deviceAddress = bssid;
-                group.setOwner(device);
-            }
-            mGroups.add(group);
-            isSaveRequired = true;
         }
 
-        if (reload || isSaveRequired) {
-            mWifiNative.saveConfig();
-            sendP2pPersistentGroupsChangedBroadcast();
-        }
-    }
+        /**
+         * Reinvoke a persistent group.
+         *
+         * @param config for the peer
+         * @return true on success, false on failure
+         */
+        private boolean reinvokePersistentGroup(WifiP2pConfig config) {
+            if (config == null) {
+                Log.e(TAG, "Illegal argument(s)");
+                return false;
+            }
+            WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
+            if (dev == null) {
+                Log.e(TAG, "Invalid device");
+                return false;
+            }
+            boolean join = dev.isGroupOwner();
+            String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
+            if (DBG) logd("target ssid is " + ssid + " join:" + join);
 
-    /**
-     * A config is valid if it has a peer address that has already been
-     * discovered
-     * @return true if it is invalid, false otherwise
-     */
-    private boolean isConfigInvalid(WifiP2pConfig config) {
-        if (config == null) return true;
-        if (TextUtils.isEmpty(config.deviceAddress)) return true;
-        if (mPeers.get(config.deviceAddress) == null) return true;
-        return false;
-    }
+            if (join && dev.isGroupLimit()) {
+                if (DBG) logd("target device reaches group limit.");
 
-    /* TODO: The supplicant does not provide group capability changes as an event.
-     * Having it pushed as an event would avoid polling for this information right
-     * before a connection
-     */
-    private WifiP2pDevice fetchCurrentDeviceDetails(WifiP2pConfig config) {
-        /* Fetch & update group capability from supplicant on the device */
-        int gc = mWifiNative.getGroupCapability(config.deviceAddress);
-        mPeers.updateGroupCapability(config.deviceAddress, gc);
-        return mPeers.get(config.deviceAddress);
-    }
-
-    /**
-     * Start a p2p group negotiation and display pin if necessary
-     * @param config for the peer
-     */
-    private void p2pConnectWithPinDisplay(WifiP2pConfig config) {
-        WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
-
-        String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner());
-        try {
-            Integer.parseInt(pin);
-            notifyInvitationSent(pin, config.deviceAddress);
-        } catch (NumberFormatException ignore) {
-            // do nothing if p2pConnect did not return a pin
-        }
-    }
-
-    /**
-     * Reinvoke a persistent group.
-     *
-     * @param config for the peer
-     * @return true on success, false on failure
-     */
-    private boolean reinvokePersistentGroup(WifiP2pConfig config) {
-        WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
-
-        boolean join = dev.isGroupOwner();
-        String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
-        if (DBG) logd("target ssid is " + ssid + " join:" + join);
-
-        if (join && dev.isGroupLimit()) {
-            if (DBG) logd("target device reaches group limit.");
-
-            // if the target group has reached the limit,
-            // try group formation.
-            join = false;
-        } else if (join) {
-            int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
-            if (netId >= 0) {
-                // Skip WPS and start 4way handshake immediately.
-                if (!mWifiNative.p2pGroupAdd(netId)) {
-                    return false;
+                // if the target group has reached the limit,
+                // try group formation.
+                join = false;
+            } else if (join) {
+                int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
+                if (netId >= 0) {
+                    // Skip WPS and start 4way handshake immediately.
+                    if (!mWifiNative.p2pGroupAdd(netId)) {
+                        return false;
+                    }
+                    return true;
                 }
+            }
+
+            if (!join && dev.isDeviceLimit()) {
+                loge("target device reaches the device limit.");
+                return false;
+            }
+
+            if (!join && dev.isInvitationCapable()) {
+                int netId = WifiP2pGroup.PERSISTENT_NET_ID;
+                if (config.netId >= 0) {
+                    if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId))) {
+                        netId = config.netId;
+                    }
+                } else {
+                    netId = mGroups.getNetworkId(dev.deviceAddress);
+                }
+                if (netId < 0) {
+                    netId = getNetworkIdFromClientList(dev.deviceAddress);
+                }
+                if (DBG) logd("netId related with " + dev.deviceAddress + " = " + netId);
+                if (netId >= 0) {
+                    // Invoke the persistent group.
+                    if (mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) {
+                        // Save network id. It'll be used when an invitation
+                        // result event is received.
+                        config.netId = netId;
+                        return true;
+                    } else {
+                        loge("p2pReinvoke() failed, update networks");
+                        updatePersistentNetworks(RELOAD);
+                        return false;
+                    }
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Return the network id of the group owner profile which has the p2p client with
+         * the specified device address in it's client list.
+         * If more than one persistent group of the same address is present in its client
+         * lists, return the first one.
+         *
+         * @param deviceAddress p2p device address.
+         * @return the network id. if not found, return -1.
+         */
+        private int getNetworkIdFromClientList(String deviceAddress) {
+            if (deviceAddress == null) return -1;
+
+            Collection<WifiP2pGroup> groups = mGroups.getGroupList();
+            for (WifiP2pGroup group : groups) {
+                int netId = group.getNetworkId();
+                String[] p2pClientList = getClientList(netId);
+                if (p2pClientList == null) continue;
+                for (String client : p2pClientList) {
+                    if (deviceAddress.equalsIgnoreCase(client)) {
+                        return netId;
+                    }
+                }
+            }
+            return -1;
+        }
+
+        /**
+         * Return p2p client list associated with the specified network id.
+         * @param netId network id.
+         * @return p2p client list. if not found, return null.
+         */
+        private String[] getClientList(int netId) {
+            String p2pClients = mWifiNative.getP2pClientList(netId);
+            if (p2pClients == null) {
+                return null;
+            }
+            return p2pClients.split(" ");
+        }
+
+        /**
+         * Remove the specified p2p client from the specified profile.
+         * @param netId network id of the profile.
+         * @param addr p2p client address to be removed.
+         * @param isRemovable if true, remove the specified profile if its client
+         *             list becomes empty.
+         * @return whether removing the specified p2p client is successful or not.
+         */
+        private boolean removeClientFromList(int netId, String addr, boolean isRemovable) {
+            StringBuilder modifiedClientList =  new StringBuilder();
+            String[] currentClientList = getClientList(netId);
+            boolean isClientRemoved = false;
+            if (currentClientList != null) {
+                for (String client : currentClientList) {
+                    if (!client.equalsIgnoreCase(addr)) {
+                        modifiedClientList.append(" ");
+                        modifiedClientList.append(client);
+                    } else {
+                        isClientRemoved = true;
+                    }
+                }
+            }
+            if (modifiedClientList.length() == 0 && isRemovable) {
+                // the client list is empty. so remove it.
+                if (DBG) logd("Remove unknown network");
+                mGroups.remove(netId);
                 return true;
             }
-        }
 
-        if (!join && dev.isDeviceLimit()) {
-            loge("target device reaches the device limit.");
-            return false;
-        }
-
-        if (!join && dev.isInvitationCapable()) {
-            int netId = WifiP2pGroup.PERSISTENT_NET_ID;
-            if (config.netId >= 0) {
-                if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId))) {
-                    netId = config.netId;
-                }
-            } else {
-                netId = mGroups.getNetworkId(dev.deviceAddress);
+            if (!isClientRemoved) {
+                // specified p2p client is not found. already removed.
+                return false;
             }
-            if (netId < 0) {
-                netId = getNetworkIdFromClientList(dev.deviceAddress);
+
+            if (DBG) logd("Modified client list: " + modifiedClientList);
+            if (modifiedClientList.length() == 0) {
+                modifiedClientList.append("\"\"");
             }
-            if (DBG) logd("netId related with " + dev.deviceAddress + " = " + netId);
-            if (netId >= 0) {
-                // Invoke the persistent group.
-                if (mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) {
-                    // Save network id. It'll be used when an invitation result event is received.
-                    config.netId = netId;
-                    return true;
-                } else {
-                    loge("p2pReinvoke() failed, update networks");
-                    updatePersistentNetworks(RELOAD);
-                    return false;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Return the network id of the group owner profile which has the p2p client with
-     * the specified device address in it's client list.
-     * If more than one persistent group of the same address is present in its client
-     * lists, return the first one.
-     *
-     * @param deviceAddress p2p device address.
-     * @return the network id. if not found, return -1.
-     */
-    private int getNetworkIdFromClientList(String deviceAddress) {
-        if (deviceAddress == null) return -1;
-
-        Collection<WifiP2pGroup> groups = mGroups.getGroupList();
-        for (WifiP2pGroup group : groups) {
-            int netId = group.getNetworkId();
-            String[] p2pClientList = getClientList(netId);
-            if (p2pClientList == null) continue;
-            for (String client : p2pClientList) {
-                if (deviceAddress.equalsIgnoreCase(client)) {
-                    return netId;
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Return p2p client list associated with the specified network id.
-     * @param netId network id.
-     * @return p2p client list. if not found, return null.
-     */
-    private String[] getClientList(int netId) {
-        String p2pClients = mWifiNative.getNetworkVariable(netId, "p2p_client_list");
-        if (p2pClients == null) {
-            return null;
-        }
-        return p2pClients.split(" ");
-    }
-
-    /**
-     * Remove the specified p2p client from the specified profile.
-     * @param netId network id of the profile.
-     * @param addr p2p client address to be removed.
-     * @param isRemovable if true, remove the specified profile if its client list becomes empty.
-     * @return whether removing the specified p2p client is successful or not.
-     */
-    private boolean removeClientFromList(int netId, String addr, boolean isRemovable) {
-        StringBuilder modifiedClientList =  new StringBuilder();
-        String[] currentClientList = getClientList(netId);
-        boolean isClientRemoved = false;
-        if (currentClientList != null) {
-            for (String client : currentClientList) {
-                if (!client.equalsIgnoreCase(addr)) {
-                    modifiedClientList.append(" ");
-                    modifiedClientList.append(client);
-                } else {
-                    isClientRemoved = true;
-                }
-            }
-        }
-        if (modifiedClientList.length() == 0 && isRemovable) {
-            // the client list is empty. so remove it.
-            if (DBG) logd("Remove unknown network");
-            mGroups.remove(netId);
+            mWifiNative.setP2pClientList(netId, modifiedClientList.toString());
+            mWifiNative.saveConfig();
             return true;
         }
 
-        if (!isClientRemoved) {
-            // specified p2p client is not found. already removed.
-            return false;
+        private void setWifiP2pInfoOnGroupFormation(InetAddress serverInetAddress) {
+            mWifiP2pInfo.groupFormed = true;
+            mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
+            mWifiP2pInfo.groupOwnerAddress = serverInetAddress;
         }
 
-        if (DBG) logd("Modified client list: " + modifiedClientList);
-        if (modifiedClientList.length() == 0) {
-            modifiedClientList.append("\"\"");
+        private void resetWifiP2pInfo() {
+            mWifiP2pInfo.groupFormed = false;
+            mWifiP2pInfo.isGroupOwner = false;
+            mWifiP2pInfo.groupOwnerAddress = null;
         }
-        mWifiNative.setNetworkVariable(netId,
-                "p2p_client_list", modifiedClientList.toString());
-        mWifiNative.saveConfig();
-        return true;
-    }
 
-    private void setWifiP2pInfoOnGroupFormation(InetAddress serverInetAddress) {
-        mWifiP2pInfo.groupFormed = true;
-        mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
-        mWifiP2pInfo.groupOwnerAddress = serverInetAddress;
-    }
-
-    private void resetWifiP2pInfo() {
-        mWifiP2pInfo.groupFormed = false;
-        mWifiP2pInfo.isGroupOwner = false;
-        mWifiP2pInfo.groupOwnerAddress = null;
-    }
-
-    private String getDeviceName(String deviceAddress) {
-        WifiP2pDevice d = mPeers.get(deviceAddress);
-        if (d != null) {
+        private String getDeviceName(String deviceAddress) {
+            WifiP2pDevice d = mPeers.get(deviceAddress);
+            if (d != null) {
                 return d.deviceName;
-        }
-        //Treat the address as name if there is no match
-        return deviceAddress;
-    }
-
-    private String getPersistedDeviceName() {
-        String deviceName = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.WIFI_P2P_DEVICE_NAME);
-        if (deviceName == null) {
-            /* We use the 4 digits of the ANDROID_ID to have a friendly
-             * default that has low likelihood of collision with a peer */
-            String id = Settings.Secure.getString(mContext.getContentResolver(),
-                    Settings.Secure.ANDROID_ID);
-            return "Android_" + id.substring(0,4);
-        }
-        return deviceName;
-    }
-
-    private boolean setAndPersistDeviceName(String devName) {
-        if (devName == null) return false;
-
-        if (!mWifiNative.setDeviceName(devName)) {
-            loge("Failed to set device name " + devName);
-            return false;
+            }
+            //Treat the address as name if there is no match
+            return deviceAddress;
         }
 
-        mThisDevice.deviceName = devName;
-        mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
-
-        Settings.Global.putString(mContext.getContentResolver(),
-                Settings.Global.WIFI_P2P_DEVICE_NAME, devName);
-        sendThisDeviceChangedBroadcast();
-        return true;
-    }
-
-    private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) {
-        boolean success;
-
-        if (!wfdInfo.isWfdEnabled()) {
-            success = mWifiNative.setWfdEnable(false);
-        } else {
-            success =
-                mWifiNative.setWfdEnable(true)
-                && mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex());
+        private String getPersistedDeviceName() {
+            String deviceName = Settings.Global.getString(mContext.getContentResolver(),
+                    Settings.Global.WIFI_P2P_DEVICE_NAME);
+            if (deviceName == null) {
+                // We use the 4 digits of the ANDROID_ID to have a friendly
+                // default that has low likelihood of collision with a peer
+                String id = Settings.Secure.getString(mContext.getContentResolver(),
+                        Settings.Secure.ANDROID_ID);
+                return "Android_" + id.substring(0, 4);
+            }
+            return deviceName;
         }
 
-        if (!success) {
-            loge("Failed to set wfd properties");
-            return false;
+        private boolean setAndPersistDeviceName(String devName) {
+            if (devName == null) return false;
+
+            if (!mWifiNative.setDeviceName(devName)) {
+                loge("Failed to set device name " + devName);
+                return false;
+            }
+
+            mThisDevice.deviceName = devName;
+            mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
+
+            Settings.Global.putString(mContext.getContentResolver(),
+                    Settings.Global.WIFI_P2P_DEVICE_NAME, devName);
+            sendThisDeviceChangedBroadcast();
+            return true;
         }
 
-        mThisDevice.wfdInfo = wfdInfo;
-        sendThisDeviceChangedBroadcast();
-        return true;
-    }
+        private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) {
+            boolean success;
 
-    private void initializeP2pSettings() {
-        mWifiNative.setPersistentReconnect(true);
-        mThisDevice.deviceName = getPersistedDeviceName();
-        mWifiNative.setDeviceName(mThisDevice.deviceName);
-        // DIRECT-XY-DEVICENAME (XY is randomly generated)
-        mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
-        mWifiNative.setDeviceType(mThisDevice.primaryDeviceType);
-        // Supplicant defaults to using virtual display with display
-        // which refers to a remote display. Use physical_display
-        mWifiNative.setConfigMethods("virtual_push_button physical_display keypad");
-        // STA has higher priority over P2P
-        mWifiNative.setConcurrencyPriority("sta");
+            if (!wfdInfo.isWfdEnabled()) {
+                success = mWifiNative.setWfdEnable(false);
+            } else {
+                success =
+                    mWifiNative.setWfdEnable(true)
+                    && mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex());
+            }
 
-        mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
-        updateThisDevice(WifiP2pDevice.AVAILABLE);
-        if (DBG) logd("DeviceAddress: " + mThisDevice.deviceAddress);
+            if (!success) {
+                loge("Failed to set wfd properties");
+                return false;
+            }
 
-        mClientInfoList.clear();
-        mWifiNative.p2pFlush();
-        mWifiNative.p2pServiceFlush();
-        mServiceTransactionId = 0;
-        mServiceDiscReqId = null;
-
-        updatePersistentNetworks(RELOAD);
-    }
-
-    private void updateThisDevice(int status) {
-        mThisDevice.status = status;
-        sendThisDeviceChangedBroadcast();
-    }
-
-    private void handleGroupCreationFailure() {
-        resetWifiP2pInfo();
-        mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.FAILED, null, null);
-        sendP2pConnectionChangedBroadcast();
-
-        // Remove only the peer we failed to connect to so that other devices discovered
-        // that have not timed out still remain in list for connection
-        boolean peersChanged = mPeers.remove(mPeersLostDuringConnection);
-        if (TextUtils.isEmpty(mSavedPeerConfig.deviceAddress) == false &&
-                mPeers.remove(mSavedPeerConfig.deviceAddress) != null) {
-            peersChanged = true;
-        }
-        if (peersChanged) {
-            sendPeersChangedBroadcast();
+            mThisDevice.wfdInfo = wfdInfo;
+            sendThisDeviceChangedBroadcast();
+            return true;
         }
 
-        mPeersLostDuringConnection.clear();
-        mServiceDiscReqId = null;
-        sendMessage(WifiP2pManager.DISCOVER_PEERS);
-    }
+        private void initializeP2pSettings() {
+            mThisDevice.deviceName = getPersistedDeviceName();
+            mWifiNative.setP2pDeviceName(mThisDevice.deviceName);
+            // DIRECT-XY-DEVICENAME (XY is randomly generated)
+            mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
+            mWifiNative.setP2pDeviceType(mThisDevice.primaryDeviceType);
+            // Supplicant defaults to using virtual display with display
+            // which refers to a remote display. Use physical_display
+            mWifiNative.setConfigMethods("virtual_push_button physical_display keypad");
 
-    private void handleGroupRemoved() {
-        if (mGroup.isGroupOwner()) {
-            stopDhcpServer(mGroup.getInterface());
-        } else {
-            if (DBG) logd("stop IpManager");
-            stopIpManager();
+            mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
+            updateThisDevice(WifiP2pDevice.AVAILABLE);
+            if (DBG) logd("DeviceAddress: " + mThisDevice.deviceAddress);
+
+            mClientInfoList.clear();
+            mWifiNative.p2pFlush();
+            mWifiNative.p2pServiceFlush();
+            mServiceTransactionId = 0;
+            mServiceDiscReqId = null;
+
+            updatePersistentNetworks(RELOAD);
+        }
+
+        private void updateThisDevice(int status) {
+            mThisDevice.status = status;
+            sendThisDeviceChangedBroadcast();
+        }
+
+        private void handleGroupCreationFailure() {
+            resetWifiP2pInfo();
+            mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.FAILED, null, null);
+            sendP2pConnectionChangedBroadcast();
+
+            // Remove only the peer we failed to connect to so that other devices discovered
+            // that have not timed out still remain in list for connection
+            boolean peersChanged = mPeers.remove(mPeersLostDuringConnection);
+            if (!TextUtils.isEmpty(mSavedPeerConfig.deviceAddress)
+                    && mPeers.remove(mSavedPeerConfig.deviceAddress) != null) {
+                peersChanged = true;
+            }
+            if (peersChanged) {
+                sendPeersChangedBroadcast();
+            }
+
+            mPeersLostDuringConnection.clear();
+            mServiceDiscReqId = null;
+            sendMessage(WifiP2pManager.DISCOVER_PEERS);
+        }
+
+        private void handleGroupRemoved() {
+            if (mGroup.isGroupOwner()) {
+                stopDhcpServer(mGroup.getInterface());
+            } else {
+                if (DBG) logd("stop IpManager");
+                stopIpManager();
+                try {
+                    mNwService.removeInterfaceFromLocalNetwork(mGroup.getInterface());
+                } catch (RemoteException e) {
+                    loge("Failed to remove iface from local network " + e);
+                }
+            }
+
             try {
-                mNwService.removeInterfaceFromLocalNetwork(mGroup.getInterface());
-            } catch (RemoteException e) {
-                loge("Failed to remove iface from local network " + e);
+                mNwService.clearInterfaceAddresses(mGroup.getInterface());
+            } catch (Exception e) {
+                loge("Failed to clear addresses " + e);
+            }
+
+            // Clear any timeout that was set. This is essential for devices
+            // that reuse the main p2p interface for a created group.
+            mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
+
+            boolean peersChanged = false;
+            // Remove only peers part of the group, so that other devices discovered
+            // that have not timed out still remain in list for connection
+            for (WifiP2pDevice d : mGroup.getClientList()) {
+                if (mPeers.remove(d)) peersChanged = true;
+            }
+            if (mPeers.remove(mGroup.getOwner())) peersChanged = true;
+            if (mPeers.remove(mPeersLostDuringConnection)) peersChanged = true;
+            if (peersChanged) {
+                sendPeersChangedBroadcast();
+            }
+
+            mGroup = null;
+            mPeersLostDuringConnection.clear();
+            mServiceDiscReqId = null;
+
+            if (mTemporarilyDisconnectedWifi) {
+                if (mWifiChannel != null) {
+                    mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 0);
+                } else {
+                    loge("handleGroupRemoved(): WifiChannel is null");
+                }
+                mTemporarilyDisconnectedWifi = false;
             }
         }
 
-        try {
-            mNwService.clearInterfaceAddresses(mGroup.getInterface());
-        } catch (Exception e) {
-            loge("Failed to clear addresses " + e);
+        private void replyToMessage(Message msg, int what) {
+            // State machine initiated requests can have replyTo set to null
+            // indicating there are no recipients, we ignore those reply actions
+            if (msg.replyTo == null) return;
+            Message dstMsg = obtainMessage(msg);
+            dstMsg.what = what;
+            mReplyChannel.replyToMessage(msg, dstMsg);
         }
 
-        // Clear any timeout that was set. This is essential for devices
-        // that reuse the main p2p interface for a created group.
-        mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
-
-        boolean peersChanged = false;
-        // Remove only peers part of the group, so that other devices discovered
-        // that have not timed out still remain in list for connection
-        for (WifiP2pDevice d : mGroup.getClientList()) {
-            if (mPeers.remove(d)) peersChanged = true;
-        }
-        if (mPeers.remove(mGroup.getOwner())) peersChanged = true;
-        if (mPeers.remove(mPeersLostDuringConnection)) peersChanged = true;
-        if (peersChanged) {
-            sendPeersChangedBroadcast();
+        private void replyToMessage(Message msg, int what, int arg1) {
+            if (msg.replyTo == null) return;
+            Message dstMsg = obtainMessage(msg);
+            dstMsg.what = what;
+            dstMsg.arg1 = arg1;
+            mReplyChannel.replyToMessage(msg, dstMsg);
         }
 
-        mGroup = null;
-        mPeersLostDuringConnection.clear();
-        mServiceDiscReqId = null;
-
-        if (mTemporarilyDisconnectedWifi) {
-            mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 0);
-            mTemporarilyDisconnectedWifi = false;
+        private void replyToMessage(Message msg, int what, Object obj) {
+            if (msg.replyTo == null) return;
+            Message dstMsg = obtainMessage(msg);
+            dstMsg.what = what;
+            dstMsg.obj = obj;
+            mReplyChannel.replyToMessage(msg, dstMsg);
         }
-    }
 
-    //State machine initiated requests can have replyTo set to null indicating
-    //there are no recipients, we ignore those reply actions
-    private void replyToMessage(Message msg, int what) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessage(msg);
-        dstMsg.what = what;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
+        private Message obtainMessage(Message srcMsg) {
+            // arg2 on the source message has a hash code that needs to
+            // be retained in replies see WifiP2pManager for details
+            Message msg = Message.obtain();
+            msg.arg2 = srcMsg.arg2;
+            return msg;
+        }
 
-    private void replyToMessage(Message msg, int what, int arg1) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessage(msg);
-        dstMsg.what = what;
-        dstMsg.arg1 = arg1;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
+        @Override
+        protected void logd(String s) {
+            Slog.d(TAG, s);
+        }
 
-    private void replyToMessage(Message msg, int what, Object obj) {
-        if (msg.replyTo == null) return;
-        Message dstMsg = obtainMessage(msg);
-        dstMsg.what = what;
-        dstMsg.obj = obj;
-        mReplyChannel.replyToMessage(msg, dstMsg);
-    }
+        @Override
+        protected void loge(String s) {
+            Slog.e(TAG, s);
+        }
 
-    /* arg2 on the source message has a hash code that needs to be retained in replies
-     * see WifiP2pManager for details */
-    private Message obtainMessage(Message srcMsg) {
-        Message msg = Message.obtain();
-        msg.arg2 = srcMsg.arg2;
-        return msg;
-    }
+        /**
+         * Update service discovery request to wpa_supplicant.
+         */
+        private boolean updateSupplicantServiceRequest() {
+            clearSupplicantServiceRequest();
 
-    @Override
-    protected void logd(String s) {
-        Slog.d(TAG, s);
-    }
+            StringBuffer sb = new StringBuffer();
+            for (ClientInfo c: mClientInfoList.values()) {
+                int key;
+                WifiP2pServiceRequest req;
+                for (int i = 0; i < c.mReqList.size(); i++) {
+                    req = c.mReqList.valueAt(i);
+                    if (req != null) {
+                        sb.append(req.getSupplicantQuery());
+                    }
+                }
+            }
 
-    @Override
-    protected void loge(String s) {
-        Slog.e(TAG, s);
-    }
+            if (sb.length() == 0) {
+                return false;
+            }
 
-    /**
-     * Update service discovery request to wpa_supplicant.
-     */
-    private boolean updateSupplicantServiceRequest() {
-        clearSupplicantServiceRequest();
+            mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString());
+            if (mServiceDiscReqId == null) {
+                return false;
+            }
+            return true;
+        }
 
-        StringBuffer sb = new StringBuffer();
-        for (ClientInfo c: mClientInfoList.values()) {
-            int key;
-            WifiP2pServiceRequest req;
-            for (int i=0; i < c.mReqList.size(); i++) {
-                req = c.mReqList.valueAt(i);
+        /**
+         * Clear service discovery request in wpa_supplicant
+         */
+        private void clearSupplicantServiceRequest() {
+            if (mServiceDiscReqId == null) return;
+
+            mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId);
+            mServiceDiscReqId = null;
+        }
+
+        private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+            if (m == null || req == null) {
+                Log.e(TAG, "Illegal argument(s)");
+                return false;
+            }
+            // TODO: We could track individual service adds separately and avoid
+            // having to do update all service requests on every new request
+            clearClientDeadChannels();
+
+            ClientInfo clientInfo = getClientInfo(m, true);
+            if (clientInfo == null) {
+                return false;
+            }
+
+            ++mServiceTransactionId;
+            //The Wi-Fi p2p spec says transaction id should be non-zero
+            if (mServiceTransactionId == 0) ++mServiceTransactionId;
+            req.setTransactionId(mServiceTransactionId);
+            clientInfo.mReqList.put(mServiceTransactionId, req);
+
+            if (mServiceDiscReqId == null) {
+                return true;
+            }
+
+            return updateSupplicantServiceRequest();
+        }
+
+        private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+            if (m == null || req == null) {
+                Log.e(TAG, "Illegal argument(s)");
+            }
+
+            ClientInfo clientInfo = getClientInfo(m, false);
+            if (clientInfo == null) {
+                return;
+            }
+
+            // Application does not have transaction id information
+            // go through stored requests to remove
+            boolean removed = false;
+            for (int i = 0; i < clientInfo.mReqList.size(); i++) {
+                if (req.equals(clientInfo.mReqList.valueAt(i))) {
+                    removed = true;
+                    clientInfo.mReqList.removeAt(i);
+                    break;
+                }
+            }
+
+            if (!removed) return;
+
+            if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+                if (DBG) logd("remove client information from framework");
+                mClientInfoList.remove(clientInfo.mMessenger);
+            }
+
+            if (mServiceDiscReqId == null) {
+                return;
+            }
+
+            updateSupplicantServiceRequest();
+        }
+
+        private void clearServiceRequests(Messenger m) {
+            if (m == null) {
+                Log.e(TAG, "Illegal argument(s)");
+                return;
+            }
+
+            ClientInfo clientInfo = getClientInfo(m, false);
+            if (clientInfo == null) {
+                return;
+            }
+
+            if (clientInfo.mReqList.size() == 0) {
+                return;
+            }
+
+            clientInfo.mReqList.clear();
+
+            if (clientInfo.mServList.size() == 0) {
+                if (DBG) logd("remove channel information from framework");
+                mClientInfoList.remove(clientInfo.mMessenger);
+            }
+
+            if (mServiceDiscReqId == null) {
+                return;
+            }
+
+            updateSupplicantServiceRequest();
+        }
+
+        private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+            if (m == null || servInfo == null) {
+                Log.e(TAG, "Illegal arguments");
+                return false;
+            }
+
+            clearClientDeadChannels();
+
+            ClientInfo clientInfo = getClientInfo(m, true);
+
+            if (clientInfo == null) {
+                return false;
+            }
+
+            if (!clientInfo.mServList.add(servInfo)) {
+                return false;
+            }
+
+            if (!mWifiNative.p2pServiceAdd(servInfo)) {
+                clientInfo.mServList.remove(servInfo);
+                return false;
+            }
+
+            return true;
+        }
+
+        private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+            if (m == null || servInfo == null) {
+                Log.e(TAG, "Illegal arguments");
+                return;
+            }
+
+            ClientInfo clientInfo = getClientInfo(m, false);
+            if (clientInfo == null) {
+                return;
+            }
+
+            mWifiNative.p2pServiceDel(servInfo);
+            clientInfo.mServList.remove(servInfo);
+
+            if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+                if (DBG) logd("remove client information from framework");
+                mClientInfoList.remove(clientInfo.mMessenger);
+            }
+        }
+
+        private void clearLocalServices(Messenger m) {
+            if (m == null) {
+                Log.e(TAG, "Illegal argument(s)");
+                return;
+            }
+
+            ClientInfo clientInfo = getClientInfo(m, false);
+            if (clientInfo == null) {
+                return;
+            }
+
+            for (WifiP2pServiceInfo servInfo: clientInfo.mServList) {
+                mWifiNative.p2pServiceDel(servInfo);
+            }
+
+            clientInfo.mServList.clear();
+            if (clientInfo.mReqList.size() == 0) {
+                if (DBG) logd("remove client information from framework");
+                mClientInfoList.remove(clientInfo.mMessenger);
+            }
+        }
+
+        private void clearClientInfo(Messenger m) {
+            clearLocalServices(m);
+            clearServiceRequests(m);
+        }
+
+        /**
+         * Send the service response to the WifiP2pManager.Channel.
+         * @param WifiP2pServiceResponse response to service discovery
+         */
+        private void sendServiceResponse(WifiP2pServiceResponse resp) {
+            if (resp == null) {
+                Log.e(TAG, "sendServiceResponse with null response");
+                return;
+            }
+            for (ClientInfo c : mClientInfoList.values()) {
+                WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId());
                 if (req != null) {
-                    sb.append(req.getSupplicantQuery());
+                    Message msg = Message.obtain();
+                    msg.what = WifiP2pManager.RESPONSE_SERVICE;
+                    msg.arg1 = 0;
+                    msg.arg2 = 0;
+                    msg.obj = resp;
+                    if (c.mMessenger == null) {
+                        continue;
+                    }
+                    try {
+                        c.mMessenger.send(msg);
+                    } catch (RemoteException e) {
+                        if (DBG) logd("detect dead channel");
+                        clearClientInfo(c.mMessenger);
+                        return;
+                    }
                 }
             }
         }
 
-        if (sb.length() == 0) {
-            return false;
-        }
+        /**
+         * We don't get notifications of clients that have gone away.
+         * We detect this actively when services are added and throw
+         * them away.
+         *
+         * TODO: This can be done better with full async channels.
+         */
+        private void clearClientDeadChannels() {
+            ArrayList<Messenger> deadClients = new ArrayList<Messenger>();
 
-        mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString());
-        if (mServiceDiscReqId == null) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Clear service discovery request in wpa_supplicant
-     */
-    private void clearSupplicantServiceRequest() {
-        if (mServiceDiscReqId == null) return;
-
-        mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId);
-        mServiceDiscReqId = null;
-    }
-
-    /* TODO: We could track individual service adds separately and avoid
-     * having to do update all service requests on every new request
-     */
-    private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
-        clearClientDeadChannels();
-        ClientInfo clientInfo = getClientInfo(m, true);
-        if (clientInfo == null) {
-            return false;
-        }
-
-        ++mServiceTransactionId;
-        //The Wi-Fi p2p spec says transaction id should be non-zero
-        if (mServiceTransactionId == 0) ++mServiceTransactionId;
-        req.setTransactionId(mServiceTransactionId);
-        clientInfo.mReqList.put(mServiceTransactionId, req);
-
-        if (mServiceDiscReqId == null) {
-            return true;
-        }
-
-        return updateSupplicantServiceRequest();
-    }
-
-    private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
-        ClientInfo clientInfo = getClientInfo(m, false);
-        if (clientInfo == null) {
-            return;
-        }
-
-        //Application does not have transaction id information
-        //go through stored requests to remove
-        boolean removed = false;
-        for (int i=0; i<clientInfo.mReqList.size(); i++) {
-            if (req.equals(clientInfo.mReqList.valueAt(i))) {
-                removed = true;
-                clientInfo.mReqList.removeAt(i);
-                break;
-            }
-        }
-
-        if (!removed) return;
-
-        if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
-            if (DBG) logd("remove client information from framework");
-            mClientInfoList.remove(clientInfo.mMessenger);
-        }
-
-        if (mServiceDiscReqId == null) {
-            return;
-        }
-
-        updateSupplicantServiceRequest();
-    }
-
-    private void clearServiceRequests(Messenger m) {
-
-        ClientInfo clientInfo = getClientInfo(m, false);
-        if (clientInfo == null) {
-            return;
-        }
-
-        if (clientInfo.mReqList.size() == 0) {
-            return;
-        }
-
-        clientInfo.mReqList.clear();
-
-        if (clientInfo.mServList.size() == 0) {
-            if (DBG) logd("remove channel information from framework");
-            mClientInfoList.remove(clientInfo.mMessenger);
-        }
-
-        if (mServiceDiscReqId == null) {
-            return;
-        }
-
-        updateSupplicantServiceRequest();
-    }
-
-    private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
-        clearClientDeadChannels();
-        ClientInfo clientInfo = getClientInfo(m, true);
-        if (clientInfo == null) {
-            return false;
-        }
-
-        if (!clientInfo.mServList.add(servInfo)) {
-            return false;
-        }
-
-        if (!mWifiNative.p2pServiceAdd(servInfo)) {
-            clientInfo.mServList.remove(servInfo);
-            return false;
-        }
-
-        return true;
-    }
-
-    private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
-        ClientInfo clientInfo = getClientInfo(m, false);
-        if (clientInfo == null) {
-            return;
-        }
-
-        mWifiNative.p2pServiceDel(servInfo);
-
-        clientInfo.mServList.remove(servInfo);
-        if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
-            if (DBG) logd("remove client information from framework");
-            mClientInfoList.remove(clientInfo.mMessenger);
-        }
-    }
-
-    private void clearLocalServices(Messenger m) {
-        ClientInfo clientInfo = getClientInfo(m, false);
-        if (clientInfo == null) {
-            return;
-        }
-
-        for (WifiP2pServiceInfo servInfo: clientInfo.mServList) {
-            mWifiNative.p2pServiceDel(servInfo);
-        }
-
-        clientInfo.mServList.clear();
-        if (clientInfo.mReqList.size() == 0) {
-            if (DBG) logd("remove client information from framework");
-            mClientInfoList.remove(clientInfo.mMessenger);
-        }
-    }
-
-    private void clearClientInfo(Messenger m) {
-        clearLocalServices(m);
-        clearServiceRequests(m);
-    }
-
-    /**
-     * Send the service response to the WifiP2pManager.Channel.
-     *
-     * @param resp
-     */
-    private void sendServiceResponse(WifiP2pServiceResponse resp) {
-        for (ClientInfo c : mClientInfoList.values()) {
-            WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId());
-            if (req != null) {
+            for (ClientInfo c : mClientInfoList.values()) {
                 Message msg = Message.obtain();
-                msg.what = WifiP2pManager.RESPONSE_SERVICE;
+                msg.what = WifiP2pManager.PING;
                 msg.arg1 = 0;
                 msg.arg2 = 0;
-                msg.obj = resp;
+                msg.obj = null;
+                if (c.mMessenger == null) {
+                    continue;
+                }
                 try {
                     c.mMessenger.send(msg);
                 } catch (RemoteException e) {
                     if (DBG) logd("detect dead channel");
-                    clearClientInfo(c.mMessenger);
-                    return;
+                    deadClients.add(c.mMessenger);
                 }
             }
-        }
-    }
 
-    /**
-     * We dont get notifications of clients that have gone away.
-     * We detect this actively when services are added and throw
-     * them away.
-     *
-     * TODO: This can be done better with full async channels.
-     */
-    private void clearClientDeadChannels() {
-        ArrayList<Messenger> deadClients = new ArrayList<Messenger>();
-
-        for (ClientInfo c : mClientInfoList.values()) {
-            Message msg = Message.obtain();
-            msg.what = WifiP2pManager.PING;
-            msg.arg1 = 0;
-            msg.arg2 = 0;
-            msg.obj = null;
-            try {
-                c.mMessenger.send(msg);
-            } catch (RemoteException e) {
-                if (DBG) logd("detect dead channel");
-                deadClients.add(c.mMessenger);
+            for (Messenger m : deadClients) {
+                clearClientInfo(m);
             }
         }
 
-        for (Messenger m : deadClients) {
-            clearClientInfo(m);
-        }
-    }
+        /**
+         * Return the specified ClientInfo.
+         * @param m Messenger
+         * @param createIfNotExist if true and the specified channel info does not exist,
+         * create new client info.
+         * @return the specified ClientInfo.
+         */
+        private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) {
+            ClientInfo clientInfo = mClientInfoList.get(m);
 
-    /**
-     * Return the specified ClientInfo.
-     * @param m Messenger
-     * @param createIfNotExist if true and the specified channel info does not exist,
-     * create new client info.
-     * @return the specified ClientInfo.
-     */
-    private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) {
-        ClientInfo clientInfo = mClientInfoList.get(m);
+            if (clientInfo == null && createIfNotExist) {
+                if (DBG) logd("add a new client");
+                clientInfo = new ClientInfo(m);
+                mClientInfoList.put(m, clientInfo);
+            }
 
-        if (clientInfo == null && createIfNotExist) {
-            if (DBG) logd("add a new client");
-            clientInfo = new ClientInfo(m);
-            mClientInfoList.put(m, clientInfo);
+            return clientInfo;
         }
 
-        return clientInfo;
-    }
-
+        /**
+         * Enforces permissions on the caller who is requesting for P2p Peers
+         * @param pkg Bundle containing the calling package string
+         * @param uid of the caller
+         * @return WifiP2pDeviceList the peer list
+         */
+        private WifiP2pDeviceList getPeers(Bundle pkg, int uid) {
+            String pkgName = pkg.getString(WifiP2pManager.CALLING_PACKAGE);
+            boolean scanPermission = false;
+            WifiPermissionsUtil wifiPermissionsUtil;
+            // getPeers() is guaranteed to be invoked after Wifi Service is up
+            // This ensures getInstance() will return a non-null object now
+            if (mWifiInjector == null) {
+                mWifiInjector = WifiInjector.getInstance();
+            }
+            wifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
+            // Minimum Version to enforce location permission is O or later
+            try {
+                scanPermission = wifiPermissionsUtil.canAccessScanResults(pkgName, uid,
+                        Build.VERSION_CODES.O);
+            } catch (SecurityException e) {
+                Log.e(TAG, "Security Exception, cannot access peer list");
+            }
+            if (scanPermission) {
+                return new WifiP2pDeviceList(mPeers);
+            } else {
+                return new WifiP2pDeviceList();
+            }
+        }
     }
 
     /**
@@ -3215,20 +3365,14 @@
      */
     private class ClientInfo {
 
-        /*
-         * A reference to WifiP2pManager.Channel handler.
-         * The response of this request is notified to WifiP2pManager.Channel handler
-         */
+        // A reference to WifiP2pManager.Channel handler.
+        // The response of this request is notified to WifiP2pManager.Channel handler
         private Messenger mMessenger;
 
-        /*
-         * A service discovery request list.
-         */
+        // A service discovery request list.
         private SparseArray<WifiP2pServiceRequest> mReqList;
 
-        /*
-         * A local service information list.
-         */
+        // A local service information list.
         private List<WifiP2pServiceInfo> mServList;
 
         private ClientInfo(Messenger m) {
diff --git a/service/java/com/android/server/wifi/scanner/BackgroundScanScheduler.java b/service/java/com/android/server/wifi/scanner/BackgroundScanScheduler.java
index 0f92431..01d64e9 100644
--- a/service/java/com/android/server/wifi/scanner/BackgroundScanScheduler.java
+++ b/service/java/com/android/server/wifi/scanner/BackgroundScanScheduler.java
@@ -36,7 +36,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
@@ -441,7 +440,6 @@
 
         schedule.max_ap_per_scan = 0;
         schedule.report_threshold_num_scans = getMaxBatch();
-        HashSet<Integer> hiddenNetworkIdSet = new HashSet<>();
 
         // set all buckets in schedule
         int bucketId = 0;
@@ -458,12 +456,6 @@
                         && settings.maxScansToCache < schedule.report_threshold_num_scans) {
                     schedule.report_threshold_num_scans = settings.maxScansToCache;
                 }
-                // note hidden networks
-                if (settings.hiddenNetworkIds != null) {
-                    for (int j = 0; j < settings.hiddenNetworkIds.length; j++) {
-                        hiddenNetworkIdSet.add(settings.hiddenNetworkIds[j]);
-                    }
-                }
             }
             bucketId++;
         }
@@ -473,13 +465,6 @@
         if (schedule.max_ap_per_scan == 0 || schedule.max_ap_per_scan > getMaxApPerScan()) {
             schedule.max_ap_per_scan = getMaxApPerScan();
         }
-        if (hiddenNetworkIdSet.size() > 0) {
-            schedule.hiddenNetworkIds = new int[hiddenNetworkIdSet.size()];
-            int numHiddenNetworks = 0;
-            for (Integer hiddenNetworkId : hiddenNetworkIdSet) {
-                schedule.hiddenNetworkIds[numHiddenNetworks++] = hiddenNetworkId;
-            }
-        }
 
         // update base period as gcd of periods
         if (schedule.num_buckets > 0) {
@@ -569,7 +554,6 @@
         ScanSettings settings = new ScanSettings();
         settings.band = originalSettings.band;
         settings.channels = originalSettings.channels;
-        settings.hiddenNetworkIds = originalSettings.hiddenNetworkIds;
         settings.periodInMs = originalSettings.periodInMs;
         settings.reportEvents = originalSettings.reportEvents;
         settings.numBssidsPerScan = originalSettings.numBssidsPerScan;
diff --git a/service/java/com/android/server/wifi/scanner/ChannelHelper.java b/service/java/com/android/server/wifi/scanner/ChannelHelper.java
index acb0ac8..d87df07 100644
--- a/service/java/com/android/server/wifi/scanner/ChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/ChannelHelper.java
@@ -222,10 +222,10 @@
         public abstract void fillBucketSettings(WifiNative.BucketSettings bucket, int maxChannels);
 
         /**
-         * Gets the list of channels that should be supplied to supplicant for a scan. Will either
-         * be a collection of all channels or null if all channels should be scanned.
+         * Gets the list of channels scan. Will either be a collection of all channels or null
+         * if all channels should be scanned.
          */
-        public abstract Set<Integer> getSupplicantScanFreqs();
+        public abstract Set<Integer> getScanFreqs();
     }
 
 
diff --git a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index d85c818..211e62a 100644
--- a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -24,11 +24,12 @@
 import android.util.Log;
 
 import com.android.server.wifi.Clock;
+import com.android.server.wifi.WifiMonitor;
 import com.android.server.wifi.WifiNative;
 
 /**
  * WifiScanner implementation that takes advantage of the gscan HAL API
- * The gscan API is used to perform background scans and wpa_supplicant is used for onehot scans.
+ * The gscan API is used to perform background scans and wificond is used for oneshot scans.
  * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method.
  */
 public class HalWifiScannerImpl extends WifiScannerImpl implements Handler.Callback {
@@ -37,14 +38,16 @@
 
     private final WifiNative mWifiNative;
     private final ChannelHelper mChannelHelper;
-    private final SupplicantWifiScannerImpl mSupplicantScannerDelegate;
+    private final WificondScannerImpl mWificondScannerDelegate;
     private final boolean mHalBasedPnoSupported;
 
-    public HalWifiScannerImpl(Context context, WifiNative wifiNative, Looper looper, Clock clock) {
+    public HalWifiScannerImpl(Context context, WifiNative wifiNative, WifiMonitor wifiMonitor,
+                              Looper looper, Clock clock) {
         mWifiNative = wifiNative;
         mChannelHelper = new HalChannelHelper(wifiNative);
-        mSupplicantScannerDelegate =
-                new SupplicantWifiScannerImpl(context, wifiNative, mChannelHelper, looper, clock);
+        mWificondScannerDelegate =
+                new WificondScannerImpl(context, wifiNative, wifiMonitor, mChannelHelper,
+                        looper, clock);
 
         // We are not going to support HAL ePNO currently.
         mHalBasedPnoSupported = false;
@@ -58,12 +61,12 @@
 
     @Override
     public void cleanup() {
-        mSupplicantScannerDelegate.cleanup();
+        mWificondScannerDelegate.cleanup();
     }
 
     @Override
     public boolean getScanCapabilities(WifiNative.ScanCapabilities capabilities) {
-        return mWifiNative.getScanCapabilities(capabilities);
+        return mWifiNative.getBgScanCapabilities(capabilities);
     }
 
     @Override
@@ -73,12 +76,12 @@
 
     public boolean startSingleScan(WifiNative.ScanSettings settings,
             WifiNative.ScanEventHandler eventHandler) {
-        return mSupplicantScannerDelegate.startSingleScan(settings, eventHandler);
+        return mWificondScannerDelegate.startSingleScan(settings, eventHandler);
     }
 
     @Override
     public WifiScanner.ScanData getLatestSingleScanResults() {
-        return mSupplicantScannerDelegate.getLatestSingleScanResults();
+        return mWificondScannerDelegate.getLatestSingleScanResults();
     }
 
     @Override
@@ -89,27 +92,27 @@
                     + ",eventHandler=" + eventHandler);
             return false;
         }
-        return mWifiNative.startScan(settings, eventHandler);
+        return mWifiNative.startBgScan(settings, eventHandler);
     }
 
     @Override
     public void stopBatchedScan() {
-        mWifiNative.stopScan();
+        mWifiNative.stopBgScan();
     }
 
     @Override
     public void pauseBatchedScan() {
-        mWifiNative.pauseScan();
+        mWifiNative.pauseBgScan();
     }
 
     @Override
     public void restartBatchedScan() {
-        mWifiNative.restartScan();
+        mWifiNative.restartBgScan();
     }
 
     @Override
     public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
-        return mWifiNative.getScanResults(flush);
+        return mWifiNative.getBgScanResults();
     }
 
     @Override
@@ -118,7 +121,7 @@
         if (mHalBasedPnoSupported) {
             return mWifiNative.setPnoList(settings, eventHandler);
         } else {
-            return mSupplicantScannerDelegate.setHwPnoList(settings, eventHandler);
+            return mWificondScannerDelegate.setHwPnoList(settings, eventHandler);
         }
     }
 
@@ -127,7 +130,7 @@
         if (mHalBasedPnoSupported) {
             return mWifiNative.resetPnoList();
         } else {
-            return mSupplicantScannerDelegate.resetHwPnoList();
+            return mWificondScannerDelegate.resetHwPnoList();
         }
     }
 
@@ -136,7 +139,7 @@
         if (mHalBasedPnoSupported) {
             return true;
         } else {
-            return mSupplicantScannerDelegate.isHwPnoSupported(isConnectedPno);
+            return mWificondScannerDelegate.isHwPnoSupported(isConnectedPno);
         }
     }
 
@@ -145,29 +148,7 @@
         if (mHalBasedPnoSupported) {
             return true;
         } else {
-            return mSupplicantScannerDelegate.shouldScheduleBackgroundScanForHwPno();
+            return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
         }
     }
-
-    @Override
-    public boolean setHotlist(WifiScanner.HotlistSettings settings,
-            WifiNative.HotlistEventHandler eventHandler) {
-        return mWifiNative.setHotlist(settings, eventHandler);
-    }
-
-    @Override
-    public void resetHotlist() {
-        mWifiNative.resetHotlist();
-    }
-
-    @Override
-    public boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings,
-            WifiNative.SignificantWifiChangeEventHandler handler) {
-        return mWifiNative.trackSignificantWifiChange(settings, handler);
-    }
-
-    @Override
-    public void untrackSignificantWifiChange() {
-        mWifiNative.untrackSignificantWifiChange();
-    }
 }
diff --git a/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java b/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java
index acddc26..33cce1c 100644
--- a/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/KnownBandsChannelHelper.java
@@ -263,7 +263,7 @@
         }
 
         @Override
-        public Set<Integer> getSupplicantScanFreqs() {
+        public Set<Integer> getScanFreqs() {
             if (mExactBands == WifiScanner.WIFI_BAND_BOTH_WITH_DFS) {
                 return null;
             } else {
diff --git a/service/java/com/android/server/wifi/scanner/NoBandChannelHelper.java b/service/java/com/android/server/wifi/scanner/NoBandChannelHelper.java
index 4f8373b..b2eeada 100644
--- a/service/java/com/android/server/wifi/scanner/NoBandChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/NoBandChannelHelper.java
@@ -100,7 +100,7 @@
 
         @Override
         public boolean partiallyContainsBand(int band) {
-            // We don't need to partially collapse settings in supplicant scanner because we
+            // We don't need to partially collapse settings in wificond scanner because we
             // don't have any limitation on the number of channels that can be scanned. We also
             // don't currently keep track of bands very well in NoBandChannelHelper.
             return false;
@@ -124,7 +124,7 @@
 
         @Override
         public Set<Integer> getMissingChannelsFromBand(int band) {
-            // We don't need to partially collapse settings in supplicant scanner because we
+            // We don't need to partially collapse settings in wificond scanner because we
             // don't have any limitation on the number of channels that can be scanned. We also
             // don't currently keep track of bands very well in NoBandChannelHelper.
             return new ArraySet<Integer>();
@@ -132,7 +132,7 @@
 
         @Override
         public Set<Integer> getContainingChannelsFromBand(int band) {
-            // We don't need to partially collapse settings in supplicant scanner because we
+            // We don't need to partially collapse settings in wificond scanner because we
             // don't have any limitation on the number of channels that can be scanned. We also
             // don't currently keep track of bands very well in NoBandChannelHelper.
             return new ArraySet<Integer>();
@@ -166,7 +166,7 @@
         }
 
         @Override
-        public Set<Integer> getSupplicantScanFreqs() {
+        public Set<Integer> getScanFreqs() {
             if (mAllChannels) {
                 return null;
             } else {
diff --git a/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
index 9012fc6..e0fb535 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -22,6 +22,8 @@
 import android.os.Looper;
 
 import com.android.server.wifi.Clock;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiMonitor;
 import com.android.server.wifi.WifiNative;
 
 import java.util.Comparator;
@@ -44,11 +46,13 @@
      */
     public static final WifiScannerImplFactory DEFAULT_FACTORY = new WifiScannerImplFactory() {
             public WifiScannerImpl create(Context context, Looper looper, Clock clock) {
-                WifiNative wifiNative = WifiNative.getWlanNativeInterface();
-                if (wifiNative.getScanCapabilities(new WifiNative.ScanCapabilities())) {
-                    return new HalWifiScannerImpl(context, wifiNative, looper, clock);
+                WifiNative wifiNative = WifiInjector.getInstance().getWifiNative();
+                WifiMonitor wifiMonitor = WifiInjector.getInstance().getWifiMonitor();
+                if (wifiNative.getBgScanCapabilities(new WifiNative.ScanCapabilities())) {
+                    return new HalWifiScannerImpl(context, wifiNative, wifiMonitor, looper, clock);
                 } else {
-                    return new SupplicantWifiScannerImpl(context, wifiNative, looper, clock);
+                    return new WificondScannerImpl(context, wifiNative, wifiMonitor, looper,
+                            clock);
                 }
             }
         };
@@ -154,26 +158,4 @@
      * @return true if background scan needs to be started, false otherwise.
      */
     public abstract boolean shouldScheduleBackgroundScanForHwPno();
-
-    /**
-     * Set a new hotlist
-     */
-    public abstract boolean setHotlist(WifiScanner.HotlistSettings settings,
-            WifiNative.HotlistEventHandler eventHandler);
-
-    /**
-     * Reset any active hotlist
-     */
-    public abstract void resetHotlist();
-
-    /**
-     * Start tracking significant wifi changes
-     */
-    public abstract boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings,
-            WifiNative.SignificantWifiChangeEventHandler handler);
-
-    /**
-     * Stop tracking significant wifi changes
-     */
-    public abstract void untrackSignificantWifiChange();
 }
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 9f8fb2f..af874b9 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -18,7 +18,6 @@
 
 import android.Manifest;
 import android.app.AlarmManager;
-import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,45 +27,48 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
-import android.net.wifi.WifiScanner.BssidInfo;
 import android.net.wifi.WifiScanner.ChannelSpec;
 import android.net.wifi.WifiScanner.PnoSettings;
 import android.net.wifi.WifiScanner.ScanData;
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.ArrayMap;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.wifi.Clock;
+import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiLog;
 import com.android.server.wifi.WifiMetrics;
-import com.android.server.wifi.WifiMetricsProto;
 import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.WifiStateMachine;
+import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
+import com.android.server.wifi.util.WifiHandler;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Set;
+import java.util.List;
 
 public class WifiScanningServiceImpl extends IWifiScanner.Stub {
 
@@ -78,6 +80,8 @@
 
     private final LocalLog mLocalLog = new LocalLog(512);
 
+    private WifiLog mLog;
+
     private void localLog(String message) {
         mLocalLog.log(message);
     }
@@ -97,11 +101,11 @@
     @Override
     public Messenger getMessenger() {
         if (mClientHandler != null) {
+            mLog.trace("getMessenger() uid=%").c(Binder.getCallingUid()).flush();
             return new Messenger(mClientHandler);
-        } else {
-            loge("WifiScanningServiceImpl trying to get messenger w/o initialization");
-            return null;
         }
+        loge("WifiScanningServiceImpl trying to get messenger w/o initialization");
+        return null;
     }
 
     @Override
@@ -114,6 +118,7 @@
         }
         Bundle b = new Bundle();
         b.putIntegerArrayList(WifiScanner.GET_AVAILABLE_CHANNELS_EXTRA, list);
+        mLog.trace("getAvailableChannels uid=%").c(Binder.getCallingUid()).flush();
         return b;
     }
 
@@ -124,14 +129,15 @@
                 "LocationHardware");
     }
 
-    private class ClientHandler extends Handler {
+    private class ClientHandler extends WifiHandler {
 
-        ClientHandler(Looper looper) {
-            super(looper);
+        ClientHandler(String tag, Looper looper) {
+            super(tag, looper);
         }
 
         @Override
         public void handleMessage(Message msg) {
+            super.handleMessage(msg);
             switch (msg.what) {
                 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
                     ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo);
@@ -143,7 +149,7 @@
                         return;
                     }
 
-                    AsyncChannel ac = new AsyncChannel();
+                    AsyncChannel ac = mFrameworkFacade.makeWifiAsyncChannel(TAG);
                     ac.connected(mContext, this, msg.replyTo);
 
                     client = new ExternalClientInfo(msg.sendingUid, msg.replyTo, ac);
@@ -151,7 +157,6 @@
 
                     ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
                             AsyncChannel.STATUS_SUCCESSFUL);
-
                     localLog("client connected: " + client);
                     return;
                 }
@@ -182,16 +187,22 @@
                 return;
             }
 
-            // Since this message is sent from WifiScanner using |sendMessageSynchronously| which
-            // doesn't set the correct |msg.replyTo| field.
+            // Since the CMD_GET_SCAN_RESULTS and CMD_GET_SINGLE_SCAN_RESULTS messages are
+            // sent from WifiScanner using |sendMessageSynchronously|, handle separately since
+            // the |msg.replyTo| field does not actually correspond to the Messenger that is
+            // registered for that client.
             if (msg.what == WifiScanner.CMD_GET_SCAN_RESULTS) {
                 mBackgroundScanStateMachine.sendMessage(Message.obtain(msg));
                 return;
             }
+            if (msg.what == WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS) {
+                mSingleScanStateMachine.sendMessage(Message.obtain(msg));
+                return;
+            }
 
             ClientInfo ci = mClients.get(msg.replyTo);
             if (ci == null) {
-                loge("Could not find client info for message " + msg.replyTo);
+                loge("Could not find client info for message " + msg.replyTo + ", msg=" + msg);
                 replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener");
                 return;
             }
@@ -199,8 +210,6 @@
             switch (msg.what) {
                 case WifiScanner.CMD_START_BACKGROUND_SCAN:
                 case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
-                case WifiScanner.CMD_SET_HOTLIST:
-                case WifiScanner.CMD_RESET_HOTLIST:
                     mBackgroundScanStateMachine.sendMessage(Message.obtain(msg));
                     break;
                 case WifiScanner.CMD_START_PNO_SCAN:
@@ -211,11 +220,6 @@
                 case WifiScanner.CMD_STOP_SINGLE_SCAN:
                     mSingleScanStateMachine.sendMessage(Message.obtain(msg));
                     break;
-                case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
-                case WifiScanner.CMD_START_TRACKING_CHANGE:
-                case WifiScanner.CMD_STOP_TRACKING_CHANGE:
-                    mWifiChangeStateMachine.sendMessage(Message.obtain(msg));
-                    break;
                 case WifiScanner.CMD_REGISTER_SCAN_LISTENER:
                     logScanRequest("registerScanListener", ci, msg.arg2, null, null, null);
                     mSingleScanListeners.addRequest(ci, msg.arg2, null, null);
@@ -261,13 +265,13 @@
 
     private WifiBackgroundScanStateMachine mBackgroundScanStateMachine;
     private WifiSingleScanStateMachine mSingleScanStateMachine;
-    private WifiChangeStateMachine mWifiChangeStateMachine;
     private WifiPnoScanStateMachine mPnoScanStateMachine;
     private ClientHandler mClientHandler;
     private final IBatteryStats mBatteryStats;
     private final AlarmManager mAlarmManager;
     private final WifiMetrics mWifiMetrics;
     private final Clock mClock;
+    private final FrameworkFacade mFrameworkFacade;
 
     WifiScanningServiceImpl(Context context, Looper looper,
             WifiScannerImpl.WifiScannerImplFactory scannerImplFactory, IBatteryStats batteryStats,
@@ -280,14 +284,14 @@
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         mWifiMetrics = wifiInjector.getWifiMetrics();
         mClock = wifiInjector.getClock();
-
+        mLog = wifiInjector.makeLog(TAG);
+        mFrameworkFacade = wifiInjector.getFrameworkFacade();
         mPreviousSchedule = null;
     }
 
     public void startService() {
-        mClientHandler = new ClientHandler(mLooper);
+        mClientHandler = new ClientHandler(TAG, mLooper);
         mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper);
-        mWifiChangeStateMachine = new WifiChangeStateMachine(mLooper);
         mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper);
         mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper);
 
@@ -311,11 +315,19 @@
                 }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE));
 
         mBackgroundScanStateMachine.start();
-        mWifiChangeStateMachine.start();
         mSingleScanStateMachine.start();
         mPnoScanStateMachine.start();
     }
 
+    /**
+     * Provide a way for unit tests to set valid log object in the WifiHandler
+     * @param log WifiLog object to assign to the clientHandler
+     */
+    @VisibleForTesting
+    public void setWifiHandlerLogForTest(WifiLog log) {
+        mClientHandler.setWifiLog(log);
+    }
+
     private static boolean isWorkSourceValid(WorkSource workSource) {
         return workSource != null && workSource.size() > 0 && workSource.get(0) >= 0;
     }
@@ -426,6 +438,15 @@
      * executed after transitioning back to IdleState.
      */
     class WifiSingleScanStateMachine extends StateMachine implements WifiNative.ScanEventHandler {
+        /**
+         * Maximum age of results that we return from our cache via
+         * {@link WifiScanner#getScanResults()}.
+         * This is currently set to 3 minutes to restore parity with the wpa_supplicant's scan
+         * result cache expiration policy. (See b/62253332 for details)
+         */
+        @VisibleForTesting
+        public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 180 * 1000;
+
         private final DefaultState mDefaultState = new DefaultState();
         private final DriverStartedState mDriverStartedState = new DriverStartedState();
         private final IdleState  mIdleState  = new IdleState();
@@ -435,6 +456,9 @@
         private RequestList<ScanSettings> mActiveScans = new RequestList<>();
         private RequestList<ScanSettings> mPendingScans = new RequestList<>();
 
+        // Scan results cached from the last full single scan request.
+        private final List<ScanResult> mCachedScanResults = new ArrayList<>();
+
         WifiSingleScanStateMachine(Looper looper) {
             super("WifiSingleScanStateMachine", looper);
 
@@ -519,10 +543,31 @@
                     case CMD_FULL_SCAN_RESULTS:
                         if (DBG) localLog("ignored full scan result event");
                         return HANDLED;
+                    case WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS:
+                        msg.obj = new WifiScanner.ParcelableScanResults(
+                            filterCachedScanResultsByAge());
+                        replySucceeded(msg);
+                        return HANDLED;
                     default:
                         return NOT_HANDLED;
                 }
+            }
 
+            /**
+             * Filter out  any scan results that are older than
+             * {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}.
+             *
+             * @return Filtered list of scan results.
+             */
+            private ScanResult[] filterCachedScanResultsByAge() {
+                // Using ScanResult.timestamp here to ensure that we use the same fields as
+                // WificondScannerImpl for filtering stale results.
+                long currentTimeInMillis = mClock.getElapsedSinceBootMillis();
+                return mCachedScanResults.stream()
+                        .filter(scanResult
+                                -> ((currentTimeInMillis - (scanResult.timestamp / 1000))
+                                        < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS))
+                        .toArray(ScanResult[]::new);
             }
         }
 
@@ -534,6 +579,9 @@
         class DriverStartedState extends State {
             @Override
             public void exit() {
+                // clear scan results when scan mode is not active
+                mCachedScanResults.clear();
+
                 mWifiMetrics.incrementScanReturnEntry(
                         WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED,
                         mPendingScans.size());
@@ -655,6 +703,7 @@
                     case CMD_SCAN_FAILED:
                         mWifiMetrics.incrementScanReturnEntry(
                                 WifiMetricsProto.WifiLog.SCAN_UNKNOWN, mActiveScans.size());
+                        sendScanResultBroadcast(false);
                         sendOpFailedToAllAndClear(mActiveScans, WifiScanner.REASON_UNSPECIFIED,
                                 "Scan failed");
                         transitionTo(mIdleState);
@@ -702,16 +751,18 @@
                 return false;
             }
 
-            if (settings.hiddenNetworkIds != null) {
-                if (mActiveScanSettings.hiddenNetworkIds == null) {
+            if (!ArrayUtils.isEmpty(settings.hiddenNetworks)) {
+                if (ArrayUtils.isEmpty(mActiveScanSettings.hiddenNetworks)) {
                     return false;
                 }
-                Set<Integer> activeHiddenNetworkIds = new HashSet<>();
-                for (int id : mActiveScanSettings.hiddenNetworkIds) {
-                    activeHiddenNetworkIds.add(id);
+                List<WifiNative.HiddenNetwork> activeHiddenNetworks = new ArrayList<>();
+                for (WifiNative.HiddenNetwork hiddenNetwork : mActiveScanSettings.hiddenNetworks) {
+                    activeHiddenNetworks.add(hiddenNetwork);
                 }
-                for (int id : settings.hiddenNetworkIds) {
-                    if (!activeHiddenNetworkIds.contains(id)) {
+                for (ScanSettings.HiddenNetwork hiddenNetwork : settings.hiddenNetworks) {
+                    WifiNative.HiddenNetwork nativeHiddenNetwork = new WifiNative.HiddenNetwork();
+                    nativeHiddenNetwork.ssid = hiddenNetwork.ssid;
+                    if (!activeHiddenNetworks.contains(nativeHiddenNetwork)) {
                         return false;
                     }
                 }
@@ -750,12 +801,14 @@
             bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
 
             ChannelCollection channels = mChannelHelper.createChannelCollection();
-            HashSet<Integer> hiddenNetworkIdSet = new HashSet<>();
+            List<WifiNative.HiddenNetwork> hiddenNetworkList = new ArrayList<>();
             for (RequestInfo<ScanSettings> entry : mPendingScans) {
                 channels.addChannels(entry.settings);
-                if (entry.settings.hiddenNetworkIds != null) {
-                    for (int i = 0; i < entry.settings.hiddenNetworkIds.length; i++) {
-                        hiddenNetworkIdSet.add(entry.settings.hiddenNetworkIds[i]);
+                if (entry.settings.hiddenNetworks != null) {
+                    for (int i = 0; i < entry.settings.hiddenNetworks.length; i++) {
+                        WifiNative.HiddenNetwork hiddenNetwork = new WifiNative.HiddenNetwork();
+                        hiddenNetwork.ssid = entry.settings.hiddenNetworks[i].ssid;
+                        hiddenNetworkList.add(hiddenNetwork);
                     }
                 }
                 if ((entry.settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
@@ -763,11 +816,11 @@
                     bucketSettings.report_events |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
                 }
             }
-            if (hiddenNetworkIdSet.size() > 0) {
-                settings.hiddenNetworkIds = new int[hiddenNetworkIdSet.size()];
+            if (hiddenNetworkList.size() > 0) {
+                settings.hiddenNetworks = new WifiNative.HiddenNetwork[hiddenNetworkList.size()];
                 int numHiddenNetworks = 0;
-                for (Integer hiddenNetworkId : hiddenNetworkIdSet) {
-                    settings.hiddenNetworkIds[numHiddenNetworks++] = hiddenNetworkId;
+                for (WifiNative.HiddenNetwork hiddenNetwork : hiddenNetworkList) {
+                    settings.hiddenNetworks[numHiddenNetworks++] = hiddenNetwork;
                 }
             }
 
@@ -817,6 +870,13 @@
             }
         }
 
+        private void sendScanResultBroadcast(boolean scanSucceeded) {
+            Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            intent.putExtra(WifiManager.EXTRA_RESULTS_UPDATED, scanSucceeded);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
         void reportScanResults(ScanData results) {
             if (results != null && results.getResults() != null) {
                 if (results.getResults().length > 0) {
@@ -845,19 +905,27 @@
                         describeForLog(allResults));
                 entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults);
             }
+
+            if (results.isAllChannelsScanned()) {
+                mCachedScanResults.clear();
+                mCachedScanResults.addAll(Arrays.asList(results.getResults()));
+                sendScanResultBroadcast(true);
+            }
+        }
+
+        List<ScanResult> getCachedScanResultsAsList() {
+            return mCachedScanResults;
         }
     }
 
     class WifiBackgroundScanStateMachine extends StateMachine
-            implements WifiNative.ScanEventHandler, WifiNative.HotlistEventHandler {
+            implements WifiNative.ScanEventHandler {
 
         private final DefaultState mDefaultState = new DefaultState();
         private final StartedState mStartedState = new StartedState();
         private final PausedState  mPausedState  = new PausedState();
 
         private final RequestList<ScanSettings> mActiveBackgroundScans = new RequestList<>();
-        private final RequestList<WifiScanner.HotlistSettings> mActiveHotlistSettings =
-                new RequestList<>();
 
         WifiBackgroundScanStateMachine(Looper looper) {
             super("WifiBackgroundScanStateMachine", looper);
@@ -883,11 +951,6 @@
             updateSchedule();
         }
 
-        public void removeHotlistSettings(ClientInfo ci) {
-            mActiveHotlistSettings.removeAllForClient(ci);
-            resetHotlist();
-        }
-
         @Override
         public void onScanStatus(int event) {
             if (DBG) localLog("onScanStatus event received, event=" + event);
@@ -924,24 +987,11 @@
             sendMessage(CMD_SCAN_RESTARTED);
         }
 
-        @Override
-        public void onHotlistApFound(ScanResult[] results) {
-            if (DBG) localLog("onHotlistApFound event received");
-            sendMessage(CMD_HOTLIST_AP_FOUND, 0, 0, results);
-        }
-
-        @Override
-        public void onHotlistApLost(ScanResult[] results) {
-            if (DBG) localLog("onHotlistApLost event received");
-            sendMessage(CMD_HOTLIST_AP_LOST, 0, 0, results);
-        }
-
         class DefaultState extends State {
             @Override
             public void enter() {
                 if (DBG) localLog("DefaultState");
                 mActiveBackgroundScans.clear();
-                mActiveHotlistSettings.clear();
             }
 
             @Override
@@ -964,6 +1014,11 @@
                             loge("could not get scan capabilities");
                             return HANDLED;
                         }
+                        if (capabilities.max_scan_buckets <= 0) {
+                            loge("invalid max buckets in scan capabilities "
+                                    + capabilities.max_scan_buckets);
+                            return HANDLED;
+                        }
                         mBackgroundScheduler.setMaxBuckets(capabilities.max_scan_buckets);
                         mBackgroundScheduler.setMaxApPerScan(capabilities.max_ap_cache_per_scan);
 
@@ -980,8 +1035,6 @@
                     case WifiScanner.CMD_STOP_BACKGROUND_SCAN:
                     case WifiScanner.CMD_START_SINGLE_SCAN:
                     case WifiScanner.CMD_STOP_SINGLE_SCAN:
-                    case WifiScanner.CMD_SET_HOTLIST:
-                    case WifiScanner.CMD_RESET_HOTLIST:
                     case WifiScanner.CMD_GET_SCAN_RESULTS:
                         replyFailed(msg, WifiScanner.REASON_UNSPECIFIED, "not available");
                         break;
@@ -1013,8 +1066,6 @@
             public void exit() {
                 sendBackgroundScanFailedToAllAndClear(
                         WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted");
-                sendHotlistFailedToAllAndClear(
-                        WifiScanner.REASON_UNSPECIFIED, "Scan was interrupted");
                 mScannerImpl.cleanup();
             }
 
@@ -1053,28 +1104,12 @@
                         reportScanResults(mScannerImpl.getLatestBatchedScanResults(true));
                         replySucceeded(msg);
                         break;
-                    case WifiScanner.CMD_SET_HOTLIST:
-                        if (addHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj)) {
-                            replySucceeded(msg);
-                        } else {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
-                        }
-                        break;
-                    case WifiScanner.CMD_RESET_HOTLIST:
-                        removeHotlist(ci, msg.arg2);
-                        break;
                     case CMD_SCAN_RESULTS_AVAILABLE:
                         reportScanResults(mScannerImpl.getLatestBatchedScanResults(true));
                         break;
                     case CMD_FULL_SCAN_RESULTS:
                         reportFullScanResult((ScanResult) msg.obj, /* bucketsScanned */ msg.arg2);
                         break;
-                    case CMD_HOTLIST_AP_FOUND:
-                        reportHotlistResults(WifiScanner.CMD_AP_FOUND, (ScanResult[]) msg.obj);
-                        break;
-                    case CMD_HOTLIST_AP_LOST:
-                        reportHotlistResults(WifiScanner.CMD_AP_LOST, (ScanResult[]) msg.obj);
-                        break;
                     case CMD_SCAN_PAUSED:
                         reportScanResults((ScanData[]) msg.obj);
                         transitionTo(mPausedState);
@@ -1260,6 +1295,10 @@
         }
 
         private void reportScanResults(ScanData[] results) {
+            if (results == null) {
+                Log.d(TAG,"The results is null, nothing to report.");
+                return;
+            }
             for (ScanData result : results) {
                 if (result != null && result.getResults() != null) {
                     if (result.getResults().length > 0) {
@@ -1294,106 +1333,6 @@
             }
             mActiveBackgroundScans.clear();
         }
-
-        private boolean addHotlist(ClientInfo ci, int handler,
-                WifiScanner.HotlistSettings settings) {
-            if (ci == null) {
-                Log.d(TAG, "Failing hotlist request ClientInfo not found " + handler);
-                return false;
-            }
-            mActiveHotlistSettings.addRequest(ci, handler, null, settings);
-            resetHotlist();
-            return true;
-        }
-
-        private void removeHotlist(ClientInfo ci, int handler) {
-            if (ci != null) {
-                mActiveHotlistSettings.removeRequest(ci, handler);
-                resetHotlist();
-            }
-        }
-
-        private void resetHotlist() {
-            if (mScannerImpl == null) {
-                loge("Failed to update hotlist because WifiScanningService is not initialized");
-                return;
-            }
-
-            Collection<WifiScanner.HotlistSettings> settings =
-                    mActiveHotlistSettings.getAllSettings();
-            int num_hotlist_ap = 0;
-
-            for (WifiScanner.HotlistSettings s : settings) {
-                num_hotlist_ap +=  s.bssidInfos.length;
-            }
-
-            if (num_hotlist_ap == 0) {
-                mScannerImpl.resetHotlist();
-            } else {
-                BssidInfo[] bssidInfos = new BssidInfo[num_hotlist_ap];
-                int apLostThreshold = Integer.MAX_VALUE;
-                int index = 0;
-                for (WifiScanner.HotlistSettings s : settings) {
-                    for (int i = 0; i < s.bssidInfos.length; i++, index++) {
-                        bssidInfos[index] = s.bssidInfos[i];
-                    }
-                    if (s.apLostThreshold < apLostThreshold) {
-                        apLostThreshold = s.apLostThreshold;
-                    }
-                }
-
-                WifiScanner.HotlistSettings mergedSettings = new WifiScanner.HotlistSettings();
-                mergedSettings.bssidInfos = bssidInfos;
-                mergedSettings.apLostThreshold = apLostThreshold;
-                mScannerImpl.setHotlist(mergedSettings, this);
-            }
-        }
-
-        private void reportHotlistResults(int what, ScanResult[] results) {
-            if (DBG) localLog("reportHotlistResults " + what + " results " + results.length);
-            for (RequestInfo<WifiScanner.HotlistSettings> entry : mActiveHotlistSettings) {
-                ClientInfo ci = entry.clientInfo;
-                int handler = entry.handlerId;
-                WifiScanner.HotlistSettings settings = entry.settings;
-                int num_results = 0;
-                for (ScanResult result : results) {
-                    for (BssidInfo BssidInfo : settings.bssidInfos) {
-                        if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) {
-                            num_results++;
-                            break;
-                        }
-                    }
-                }
-                if (num_results == 0) {
-                    // nothing to report
-                    return;
-                }
-                ScanResult[] results2 = new ScanResult[num_results];
-                int index = 0;
-                for (ScanResult result : results) {
-                    for (BssidInfo BssidInfo : settings.bssidInfos) {
-                        if (result.BSSID.equalsIgnoreCase(BssidInfo.bssid)) {
-                            results2[index] = result;
-                            index++;
-                        }
-                    }
-                }
-                WifiScanner.ParcelableScanResults parcelableScanResults =
-                        new WifiScanner.ParcelableScanResults(results2);
-
-                ci.reportEvent(what, 0, handler, parcelableScanResults);
-            }
-        }
-
-        private void sendHotlistFailedToAllAndClear(int reason, String description) {
-            for (RequestInfo<WifiScanner.HotlistSettings> entry : mActiveHotlistSettings) {
-                ClientInfo ci = entry.clientInfo;
-                int handler = entry.handlerId;
-                ci.reportEvent(WifiScanner.CMD_OP_FAILED, 0, handler,
-                        new WifiScanner.OperationResult(reason, description));
-            }
-            mActiveHotlistSettings.clear();
-        }
     }
 
     /**
@@ -1409,14 +1348,14 @@
      * 2. Move to |Started State| when we get the |WIFI_SCAN_AVAILABLE| broadcast from WifiManager.
      * 3. When a new PNO scan request comes in:
      *   a.1. Switch to |Hw Pno Scan state| when the device supports HW PNO
-     *        (This could either be HAL based ePNO or supplicant based PNO).
+     *        (This could either be HAL based ePNO or wificond based PNO).
      *   a.2. In |Hw Pno Scan state| when PNO scan results are received, check if the result
      *        contains IE (information elements). If yes, send the results to the client, else
      *        switch to |Single Scan state| and send the result to the client when the scan result
      *        is obtained.
      *   b.1. Switch to |Sw Pno Scan state| when the device does not supports HW PNO
      *        (This is for older devices which do not support HW PNO and for connected PNO on
-     *         devices which support supplicant based PNO)
+     *         devices which support wificond based PNO)
      *   b.2. In |Sw Pno Scan state| send the result to the client when the background scan result
      *        is obtained
      *
@@ -1439,7 +1378,7 @@
         WifiPnoScanStateMachine(Looper looper) {
             super("WifiPnoScanStateMachine", looper);
 
-            setLogRecSize(512);
+            setLogRecSize(256);
             setLogOnlyTransitions(false);
 
             // CHECKSTYLE:OFF IndentationCheck
@@ -1705,8 +1644,10 @@
             }
         }
 
-        private WifiNative.PnoSettings convertPnoSettingsToNative(PnoSettings pnoSettings) {
+        private WifiNative.PnoSettings convertSettingsToPnoNative(ScanSettings scanSettings,
+                                                                  PnoSettings pnoSettings) {
             WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings();
+            nativePnoSetting.periodInMs = scanSettings.periodInMs;
             nativePnoSetting.min5GHzRssi = pnoSettings.min5GHzRssi;
             nativePnoSetting.min24GHzRssi = pnoSettings.min24GHzRssi;
             nativePnoSetting.initialScoreMax = pnoSettings.initialScoreMax;
@@ -1720,8 +1661,6 @@
             for (int i = 0; i < pnoSettings.networkList.length; i++) {
                 nativePnoSetting.networkList[i] = new WifiNative.PnoNetwork();
                 nativePnoSetting.networkList[i].ssid = pnoSettings.networkList[i].ssid;
-                nativePnoSetting.networkList[i].networkId = pnoSettings.networkList[i].networkId;
-                nativePnoSetting.networkList[i].priority = pnoSettings.networkList[i].priority;
                 nativePnoSetting.networkList[i].flags = pnoSettings.networkList[i].flags;
                 nativePnoSetting.networkList[i].auth_bit_field =
                         pnoSettings.networkList[i].authBitField;
@@ -1778,7 +1717,8 @@
                 loge("Failing scan request because there is already an active scan");
                 return false;
             }
-            WifiNative.PnoSettings nativePnoSettings = convertPnoSettingsToNative(pnoSettings);
+            WifiNative.PnoSettings nativePnoSettings =
+                    convertSettingsToPnoNative(scanSettings, pnoSettings);
             if (!mScannerImpl.setHwPnoList(nativePnoSettings, mPnoScanStateMachine)) {
                 return false;
             }
@@ -1912,7 +1852,6 @@
             mSingleScanListeners.removeAllForClient(this);
             mSingleScanStateMachine.removeSingleScanRequests(this);
             mBackgroundScanStateMachine.removeBackgroundScanSettings(this);
-            mBackgroundScanStateMachine.removeHotlistSettings(this);
             unregister();
             localLog("Successfully stopped all requests for client " + this);
         }
@@ -2012,10 +1951,6 @@
         @Override
         public void cleanup() {
             mDisconnected = true;
-            // Internal clients should not have any wifi change requests. So, keeping this cleanup
-            // only for external client because this will otherwise cause an infinite recursion
-            // when the internal client in WifiChangeStateMachine is cleaned up.
-            mWifiChangeStateMachine.removeWifiChangeHandler(this);
             mPnoScanStateMachine.removePnoSettings(this);
             super.cleanup();
         }
@@ -2090,8 +2025,12 @@
             Message reply = Message.obtain();
             reply.what = WifiScanner.CMD_OP_SUCCEEDED;
             reply.arg2 = msg.arg2;
+            if (msg.obj != null) {
+                reply.obj = msg.obj;
+            }
             try {
                 msg.replyTo.send(reply);
+                mLog.trace("replySucceeded recvdMessage=%").c(msg.what).flush();
             } catch (RemoteException e) {
                 // There's not much we can do if reply can't be sent!
             }
@@ -2108,6 +2047,10 @@
             reply.obj = new WifiScanner.OperationResult(reason, description);
             try {
                 msg.replyTo.send(reply);
+                mLog.trace("replyFailed recvdMessage=% reason=%")
+                            .c(msg.what)
+                            .c(reason)
+                            .flush();
             } catch (RemoteException e) {
                 // There's not much we can do if reply can't be sent!
             }
@@ -2116,451 +2059,6 @@
         }
     }
 
-    /**
-     * Wifi Change state machine is used to handle any wifi change tracking requests.
-     * TODO: This state machine doesn't handle driver loading/unloading yet.
-     */
-    class WifiChangeStateMachine extends StateMachine
-            implements WifiNative.SignificantWifiChangeEventHandler {
-
-        private static final int MAX_APS_TO_TRACK = 3;
-        private static final int MOVING_SCAN_PERIOD_MS      = 10000;
-        private static final int STATIONARY_SCAN_PERIOD_MS  =  5000;
-        private static final int MOVING_STATE_TIMEOUT_MS    = 30000;
-
-        State mDefaultState = new DefaultState();
-        State mStationaryState = new StationaryState();
-        State mMovingState = new MovingState();
-
-        private static final String ACTION_TIMEOUT =
-                "com.android.server.WifiScanningServiceImpl.action.TIMEOUT";
-        private PendingIntent mTimeoutIntent;
-        private ScanResult[] mCurrentBssids;
-        private InternalClientInfo mInternalClientInfo;
-
-        private final Set<Pair<ClientInfo, Integer>> mActiveWifiChangeHandlers = new HashSet<>();
-
-        WifiChangeStateMachine(Looper looper) {
-            super("SignificantChangeStateMachine", looper);
-
-            // CHECKSTYLE:OFF IndentationCheck
-            addState(mDefaultState);
-                addState(mStationaryState, mDefaultState);
-                addState(mMovingState, mDefaultState);
-            // CHECKSTYLE:ON IndentationCheck
-
-            setInitialState(mDefaultState);
-        }
-
-        public void removeWifiChangeHandler(ClientInfo ci) {
-            Iterator<Pair<ClientInfo, Integer>> iter = mActiveWifiChangeHandlers.iterator();
-            while (iter.hasNext()) {
-                Pair<ClientInfo, Integer> entry = iter.next();
-                if (entry.first == ci) {
-                    iter.remove();
-                }
-            }
-            untrackSignificantWifiChangeOnEmpty();
-        }
-
-        class DefaultState extends State {
-            @Override
-            public void enter() {
-                if (DBG) localLog("Entering IdleState");
-            }
-
-            @Override
-            public boolean processMessage(Message msg) {
-                if (DBG) localLog("DefaultState state got " + msg);
-                ClientInfo ci = mClients.get(msg.replyTo);
-                switch (msg.what) {
-                    case WifiScanner.CMD_START_TRACKING_CHANGE:
-                        if (addWifiChangeHandler(ci, msg.arg2)) {
-                            replySucceeded(msg);
-                            transitionTo(mMovingState);
-                        } else {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
-                        }
-                        break;
-                    case WifiScanner.CMD_STOP_TRACKING_CHANGE:
-                        // nothing to do
-                        break;
-                    case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
-                        /* save configuration till we transition to moving state */
-                        deferMessage(msg);
-                        break;
-                    case WifiScanner.CMD_SCAN_RESULT:
-                        // nothing to do
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-        }
-
-        class StationaryState extends State {
-            @Override
-            public void enter() {
-                if (DBG) localLog("Entering StationaryState");
-                reportWifiStabilized(mCurrentBssids);
-            }
-
-            @Override
-            public boolean processMessage(Message msg) {
-                if (DBG) localLog("Stationary state got " + msg);
-                ClientInfo ci = mClients.get(msg.replyTo);
-                switch (msg.what) {
-                    case WifiScanner.CMD_START_TRACKING_CHANGE:
-                        if (addWifiChangeHandler(ci, msg.arg2)) {
-                            replySucceeded(msg);
-                        } else {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
-                        }
-                        break;
-                    case WifiScanner.CMD_STOP_TRACKING_CHANGE:
-                        removeWifiChangeHandler(ci, msg.arg2);
-                        break;
-                    case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
-                        /* save configuration till we transition to moving state */
-                        deferMessage(msg);
-                        break;
-                    case CMD_WIFI_CHANGE_DETECTED:
-                        if (DBG) localLog("Got wifi change detected");
-                        reportWifiChanged((ScanResult[]) msg.obj);
-                        transitionTo(mMovingState);
-                        break;
-                    case WifiScanner.CMD_SCAN_RESULT:
-                        // nothing to do
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-        }
-
-        class MovingState extends State {
-            boolean mWifiChangeDetected = false;
-            boolean mScanResultsPending = false;
-
-            @Override
-            public void enter() {
-                if (DBG) localLog("Entering MovingState");
-                if (mTimeoutIntent == null) {
-                    Intent intent = new Intent(ACTION_TIMEOUT, null);
-                    mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
-
-                    mContext.registerReceiver(
-                            new BroadcastReceiver() {
-                                @Override
-                                public void onReceive(Context context, Intent intent) {
-                                    sendMessage(CMD_WIFI_CHANGE_TIMEOUT);
-                                }
-                            }, new IntentFilter(ACTION_TIMEOUT));
-                }
-                issueFullScan();
-            }
-
-            @Override
-            public boolean processMessage(Message msg) {
-                if (DBG) localLog("MovingState state got " + msg);
-                ClientInfo ci = mClients.get(msg.replyTo);
-                switch (msg.what) {
-                    case WifiScanner.CMD_START_TRACKING_CHANGE:
-                        if (addWifiChangeHandler(ci, msg.arg2)) {
-                            replySucceeded(msg);
-                        } else {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
-                        }
-                        break;
-                    case WifiScanner.CMD_STOP_TRACKING_CHANGE:
-                        removeWifiChangeHandler(ci, msg.arg2);
-                        break;
-                    case WifiScanner.CMD_CONFIGURE_WIFI_CHANGE:
-                        if (DBG) localLog("Got configuration from app");
-                        WifiScanner.WifiChangeSettings settings =
-                                (WifiScanner.WifiChangeSettings) msg.obj;
-                        reconfigureScan(settings);
-                        mWifiChangeDetected = false;
-                        long unchangedDelay = settings.unchangedSampleSize * settings.periodInMs;
-                        mAlarmManager.cancel(mTimeoutIntent);
-                        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                                mClock.elapsedRealtime() + unchangedDelay,
-                                mTimeoutIntent);
-                        break;
-                    case WifiScanner.CMD_SCAN_RESULT:
-                        if (DBG) localLog("Got scan results");
-                        if (mScanResultsPending) {
-                            if (DBG) localLog("reconfiguring scan");
-                            reconfigureScan((ScanData[])msg.obj,
-                                    STATIONARY_SCAN_PERIOD_MS);
-                            mWifiChangeDetected = false;
-                            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                                    mClock.elapsedRealtime() + MOVING_STATE_TIMEOUT_MS,
-                                    mTimeoutIntent);
-                            mScanResultsPending = false;
-                        }
-                        break;
-                    case CMD_WIFI_CHANGE_DETECTED:
-                        if (DBG) localLog("Change detected");
-                        mAlarmManager.cancel(mTimeoutIntent);
-                        reportWifiChanged((ScanResult[])msg.obj);
-                        mWifiChangeDetected = true;
-                        issueFullScan();
-                        break;
-                    case CMD_WIFI_CHANGE_TIMEOUT:
-                        if (DBG) localLog("Got timeout event");
-                        if (mWifiChangeDetected == false) {
-                            transitionTo(mStationaryState);
-                        }
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-
-            @Override
-            public void exit() {
-                mAlarmManager.cancel(mTimeoutIntent);
-            }
-
-            void issueFullScan() {
-                if (DBG) localLog("Issuing full scan");
-                ScanSettings settings = new ScanSettings();
-                settings.band = WifiScanner.WIFI_BAND_BOTH;
-                settings.periodInMs = MOVING_SCAN_PERIOD_MS;
-                settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
-                addScanRequest(settings);
-                mScanResultsPending = true;
-            }
-
-        }
-
-        private void reconfigureScan(ScanData[] results, int period) {
-            // find brightest APs and set them as sentinels
-            if (results.length < MAX_APS_TO_TRACK) {
-                localLog("too few APs (" + results.length + ") available to track wifi change");
-                return;
-            }
-
-            removeScanRequest();
-
-            // remove duplicate BSSIDs
-            HashMap<String, ScanResult> bssidToScanResult = new HashMap<String, ScanResult>();
-            for (ScanResult result : results[0].getResults()) {
-                ScanResult saved = bssidToScanResult.get(result.BSSID);
-                if (saved == null) {
-                    bssidToScanResult.put(result.BSSID, result);
-                } else if (saved.level > result.level) {
-                    bssidToScanResult.put(result.BSSID, result);
-                }
-            }
-
-            // find brightest BSSIDs
-            ScanResult brightest[] = new ScanResult[MAX_APS_TO_TRACK];
-            Collection<ScanResult> results2 = bssidToScanResult.values();
-            for (ScanResult result : results2) {
-                for (int j = 0; j < brightest.length; j++) {
-                    if (brightest[j] == null
-                            || (brightest[j].level < result.level)) {
-                        for (int k = brightest.length; k > (j + 1); k--) {
-                            brightest[k - 1] = brightest[k - 2];
-                        }
-                        brightest[j] = result;
-                        break;
-                    }
-                }
-            }
-
-            // Get channels to scan for
-            ArrayList<Integer> channels = new ArrayList<Integer>();
-            for (int i = 0; i < brightest.length; i++) {
-                boolean found = false;
-                for (int j = i + 1; j < brightest.length; j++) {
-                    if (brightest[j].frequency == brightest[i].frequency) {
-                        found = true;
-                    }
-                }
-                if (!found) {
-                    channels.add(brightest[i].frequency);
-                }
-            }
-
-            if (DBG) localLog("Found " + channels.size() + " channels");
-
-            // set scanning schedule
-            ScanSettings settings = new ScanSettings();
-            settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
-            settings.channels = new ChannelSpec[channels.size()];
-            for (int i = 0; i < channels.size(); i++) {
-                settings.channels[i] = new ChannelSpec(channels.get(i));
-            }
-
-            settings.periodInMs = period;
-            addScanRequest(settings);
-
-            WifiScanner.WifiChangeSettings settings2 = new WifiScanner.WifiChangeSettings();
-            settings2.rssiSampleSize = 3;
-            settings2.lostApSampleSize = 3;
-            settings2.unchangedSampleSize = 3;
-            settings2.minApsBreachingThreshold = 2;
-            settings2.bssidInfos = new BssidInfo[brightest.length];
-
-            for (int i = 0; i < brightest.length; i++) {
-                BssidInfo BssidInfo = new BssidInfo();
-                BssidInfo.bssid = brightest[i].BSSID;
-                int threshold = (100 + brightest[i].level) / 32 + 2;
-                BssidInfo.low = brightest[i].level - threshold;
-                BssidInfo.high = brightest[i].level + threshold;
-                settings2.bssidInfos[i] = BssidInfo;
-
-                if (DBG) localLog("Setting bssid=" + BssidInfo.bssid + ", " +
-                        "low=" + BssidInfo.low + ", high=" + BssidInfo.high);
-            }
-
-            trackSignificantWifiChange(settings2);
-            mCurrentBssids = brightest;
-        }
-
-        private void reconfigureScan(WifiScanner.WifiChangeSettings settings) {
-
-            if (settings.bssidInfos.length < MAX_APS_TO_TRACK) {
-                localLog("too few APs (" + settings.bssidInfos.length
-                        + ") available to track wifi change");
-                return;
-            }
-
-            if (DBG) localLog("Setting configuration specified by app");
-
-            mCurrentBssids = new ScanResult[settings.bssidInfos.length];
-            HashSet<Integer> channels = new HashSet<Integer>();
-
-            for (int i = 0; i < settings.bssidInfos.length; i++) {
-                ScanResult result = new ScanResult();
-                result.BSSID = settings.bssidInfos[i].bssid;
-                mCurrentBssids[i] = result;
-                channels.add(settings.bssidInfos[i].frequencyHint);
-            }
-
-            // cancel previous scan
-            removeScanRequest();
-
-            // set new scanning schedule
-            ScanSettings settings2 = new ScanSettings();
-            settings2.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
-            settings2.channels = new ChannelSpec[channels.size()];
-            int i = 0;
-            for (Integer channel : channels) {
-                settings2.channels[i++] = new ChannelSpec(channel);
-            }
-
-            settings2.periodInMs = settings.periodInMs;
-            addScanRequest(settings2);
-
-            // start tracking new APs
-            trackSignificantWifiChange(settings);
-        }
-
-
-        @Override
-        public void onChangesFound(ScanResult results[]) {
-            sendMessage(CMD_WIFI_CHANGE_DETECTED, 0, 0, results);
-        }
-
-        private void addScanRequest(ScanSettings settings) {
-            if (DBG) localLog("Starting scans");
-            if (mInternalClientInfo != null) {
-                mInternalClientInfo.sendRequestToClientHandler(
-                        WifiScanner.CMD_START_BACKGROUND_SCAN, settings, null);
-            }
-        }
-
-        private void removeScanRequest() {
-            if (DBG) localLog("Stopping scans");
-            if (mInternalClientInfo != null) {
-                mInternalClientInfo.sendRequestToClientHandler(
-                        WifiScanner.CMD_STOP_BACKGROUND_SCAN);
-            }
-        }
-
-        private void trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings) {
-            if (mScannerImpl != null) {
-                mScannerImpl.untrackSignificantWifiChange();
-                mScannerImpl.trackSignificantWifiChange(settings, this);
-            }
-        }
-
-        private void untrackSignificantWifiChange() {
-            if (mScannerImpl != null) {
-                mScannerImpl.untrackSignificantWifiChange();
-            }
-        }
-
-        private boolean addWifiChangeHandler(ClientInfo ci, int handler) {
-            if (ci == null) {
-                Log.d(TAG, "Failing wifi change request ClientInfo not found " + handler);
-                return false;
-            }
-            mActiveWifiChangeHandlers.add(Pair.create(ci, handler));
-            // Add an internal client to make background scan requests.
-            if (mInternalClientInfo == null) {
-                mInternalClientInfo =
-                        new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler()));
-                mInternalClientInfo.register();
-            }
-            return true;
-        }
-
-        private void removeWifiChangeHandler(ClientInfo ci, int handler) {
-            if (ci != null) {
-                mActiveWifiChangeHandlers.remove(Pair.create(ci, handler));
-                untrackSignificantWifiChangeOnEmpty();
-            }
-        }
-
-        private void untrackSignificantWifiChangeOnEmpty() {
-            if (mActiveWifiChangeHandlers.isEmpty()) {
-                if (DBG) localLog("Got Disable Wifi Change");
-                mCurrentBssids = null;
-                untrackSignificantWifiChange();
-                // Remove the internal client when there are no more external clients.
-                if (mInternalClientInfo != null) {
-                    mInternalClientInfo.cleanup();
-                    mInternalClientInfo = null;
-                }
-                transitionTo(mDefaultState);
-            }
-        }
-
-        private void reportWifiChanged(ScanResult[] results) {
-            WifiScanner.ParcelableScanResults parcelableScanResults =
-                    new WifiScanner.ParcelableScanResults(results);
-            Iterator<Pair<ClientInfo, Integer>> it = mActiveWifiChangeHandlers.iterator();
-            while (it.hasNext()) {
-                Pair<ClientInfo, Integer> entry = it.next();
-                ClientInfo ci = entry.first;
-                int handler = entry.second;
-                ci.reportEvent(WifiScanner.CMD_WIFI_CHANGE_DETECTED, 0, handler,
-                        parcelableScanResults);
-            }
-        }
-
-        private void reportWifiStabilized(ScanResult[] results) {
-            WifiScanner.ParcelableScanResults parcelableScanResults =
-                    new WifiScanner.ParcelableScanResults(results);
-            Iterator<Pair<ClientInfo, Integer>> it = mActiveWifiChangeHandlers.iterator();
-            while (it.hasNext()) {
-                Pair<ClientInfo, Integer> entry = it.next();
-                ClientInfo ci = entry.first;
-                int handler = entry.second;
-                ci.reportEvent(WifiScanner.CMD_WIFI_CHANGES_STABILIZED, 0, handler,
-                        parcelableScanResults);
-            }
-        }
-    }
-
     private static String toString(int uid, ScanSettings settings) {
         StringBuilder sb = new StringBuilder();
         sb.append("ScanSettings[uid=").append(uid);
@@ -2624,6 +2122,40 @@
         if (mPnoScanStateMachine != null) {
             mPnoScanStateMachine.dump(fd, pw, args);
         }
+        pw.println();
+
+        if (mSingleScanStateMachine != null) {
+            mSingleScanStateMachine.dump(fd, pw, args);
+            pw.println();
+            pw.println("Latest scan results:");
+            List<ScanResult> scanResults = mSingleScanStateMachine.getCachedScanResultsAsList();
+            long nowMs = System.currentTimeMillis();
+            if (scanResults != null && scanResults.size() != 0) {
+                pw.println("    BSSID              Frequency  RSSI  Age(sec)   SSID "
+                        + "                                Flags");
+                for (ScanResult r : scanResults) {
+                    String age;
+                    if (r.seen <= 0) {
+                        age = "___?___";
+                    } else if (nowMs < r.seen) {
+                        age = "  0.000";
+                    } else if (r.seen < nowMs - 1000000) {
+                        age = ">1000.0";
+                    } else {
+                        age = String.format("%3.3f", (nowMs - r.seen) / 1000.0);
+                    }
+                    String ssid = r.SSID == null ? "" : r.SSID;
+                    pw.printf("  %17s  %9d  %5d   %7s    %-32s  %s\n",
+                              r.BSSID,
+                              r.frequency,
+                              r.level,
+                              age,
+                              String.format("%1.32s", ssid),
+                              r.capabilities);
+                }
+            }
+            pw.println();
+        }
     }
 
     void logScanRequest(String request, ClientInfo ci, int id, WorkSource workSource,
@@ -2707,10 +2239,7 @@
           .append(" networks:[ ");
         if (pnoSettings.networkList != null) {
             for (int i = 0; i < pnoSettings.networkList.length; i++) {
-                sb.append(pnoSettings.networkList[i].ssid)
-                  .append(",")
-                  .append(pnoSettings.networkList[i].networkId)
-                  .append(" ");
+                sb.append(pnoSettings.networkList[i].ssid).append(",");
             }
         }
         sb.append(" ] ")
diff --git a/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
similarity index 74%
rename from service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
rename to service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
index f0cac0f..fd7fddb 100644
--- a/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -19,7 +19,6 @@
 import android.app.AlarmManager;
 import android.content.Context;
 import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
 import android.os.Handler;
 import android.os.Looper;
@@ -42,25 +41,22 @@
 import java.util.Set;
 
 /**
- * Implementation of the WifiScanner HAL API that uses wpa_supplicant to perform all scans
+ * Implementation of the WifiScanner HAL API that uses wificond to perform all scans
  * @see com.android.server.wifi.scanner.WifiScannerImpl for more details on each method.
  */
-public class SupplicantWifiScannerImpl extends WifiScannerImpl implements Handler.Callback {
-    private static final String TAG = "SupplicantWifiScannerImpl";
+public class WificondScannerImpl extends WifiScannerImpl implements Handler.Callback {
+    private static final String TAG = "WificondScannerImpl";
     private static final boolean DBG = false;
 
     public static final String BACKGROUND_PERIOD_ALARM_TAG = TAG + " Background Scan Period";
     public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
-    // Max number of networks that can be specified to wpa_supplicant per scan request
+    // Max number of networks that can be specified to wificond per scan request
     public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
 
     private static final int SCAN_BUFFER_CAPACITY = 10;
     private static final int MAX_APS_PER_SCAN = 32;
     private static final int MAX_SCAN_BUCKETS = 16;
 
-    private static final String ACTION_SCAN_PERIOD =
-            "com.android.server.util.SupplicantWifiScannerImpl.action.SCAN_PERIOD";
-
     private final Context mContext;
     private final WifiNative mWifiNative;
     private final AlarmManager mAlarmManager;
@@ -68,7 +64,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;
@@ -91,10 +87,6 @@
     // Settings for the currently running scan, null if no scan active
     private LastScanSettings mLastScanSettings = null;
 
-    // Active hotlist settings
-    private WifiNative.HotlistEventHandler mHotlistHandler = null;
-    private ChangeBuffer mHotlistChangeBuffer = new ChangeBuffer();
-
     // Pno related info.
     private WifiNative.PnoSettings mPnoSettings = null;
     private WifiNative.PnoEventHandler mPnoEventHandler;
@@ -131,8 +123,9 @@
             }
         };
 
-    public SupplicantWifiScannerImpl(Context context, WifiNative wifiNative,
-            ChannelHelper channelHelper, Looper looper, Clock clock) {
+    public WificondScannerImpl(Context context, WifiNative wifiNative,
+                                     WifiMonitor wifiMonitor, ChannelHelper channelHelper,
+                                     Looper looper, Clock clock) {
         mContext = context;
         mWifiNative = wifiNative;
         mChannelHelper = channelHelper;
@@ -145,16 +138,18 @@
         mHwPnoScanSupported = mContext.getResources().getBoolean(
                 R.bool.config_wifi_background_scan_support);
 
-        WifiMonitor.getInstance().registerHandler(mWifiNative.getInterfaceName(),
+        wifiMonitor.registerHandler(mWifiNative.getInterfaceName(),
                 WifiMonitor.SCAN_FAILED_EVENT, mEventHandler);
-        WifiMonitor.getInstance().registerHandler(mWifiNative.getInterfaceName(),
+        wifiMonitor.registerHandler(mWifiNative.getInterfaceName(),
+                WifiMonitor.PNO_SCAN_RESULTS_EVENT, mEventHandler);
+        wifiMonitor.registerHandler(mWifiNative.getInterfaceName(),
                 WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
     }
 
-    public SupplicantWifiScannerImpl(Context context, WifiNative wifiNative, Looper looper,
-            Clock clock) {
-        // TODO figure out how to get channel information from supplicant
-        this(context, wifiNative, new NoBandChannelHelper(), looper, clock);
+    public WificondScannerImpl(Context context, WifiNative wifiNative,
+                                     WifiMonitor wifiMonitor, Looper looper, Clock clock) {
+        // TODO get channel information from wificond.
+        this(context, wifiNative, wifiMonitor, new NoBandChannelHelper(), looper, clock);
     }
 
     @Override
@@ -164,8 +159,6 @@
             mPendingSingleScanEventHandler = null;
             stopHwPnoScan();
             stopBatchedScan();
-            resetHotlist();
-            untrackSignificantWifiChange();
             mLastScanSettings = null; // finally clear any active scan
         }
     }
@@ -177,8 +170,6 @@
         capabilities.max_ap_cache_per_scan = MAX_APS_PER_SCAN;
         capabilities.max_rssi_sample_size = 8;
         capabilities.max_scan_reporting_threshold = SCAN_BUFFER_CAPACITY;
-        capabilities.max_hotlist_bssids = 0;
-        capabilities.max_significant_wifi_change_aps = 0;
         return true;
     }
 
@@ -249,7 +240,7 @@
             stopBatchedScan();
             if (DBG) {
                 Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period="
-                    + settings.base_period_ms + " ms");
+                        + settings.base_period_ms + " ms");
             }
             mPendingBackgroundScanSettings = settings;
             mPendingBackgroundScanEventHandler = eventHandler;
@@ -324,7 +315,7 @@
     }
 
     private void handleScanTimeout() {
-        Log.e(TAG, "Timed out waiting for scan result from supplicant");
+        Log.e(TAG, "Timed out waiting for scan result from wificond");
         reportScanFailure();
         processPendingScans();
     }
@@ -344,9 +335,9 @@
             }
 
             ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
-            Set<Integer> hiddenNetworkIdSet = new HashSet<Integer>();
+            Set<String> hiddenNetworkSSIDSet = new HashSet<>();
             final LastScanSettings newScanSettings =
-                    new LastScanSettings(mClock.elapsedRealtime());
+                    new LastScanSettings(mClock.getElapsedSinceBootMillis());
 
             // Update scan settings if there is a pending scan
             if (!mBackgroundScanPaused) {
@@ -389,20 +380,11 @@
                                 mBackgroundScanSettings.report_threshold_num_scans,
                                 mBackgroundScanSettings.report_threshold_percent);
                     }
-
-                    int[] hiddenNetworkIds = mBackgroundScanSettings.hiddenNetworkIds;
-                    if (hiddenNetworkIds != null) {
-                        int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length,
-                                MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
-                        for (int i = 0; i < numHiddenNetworkIds; i++) {
-                            hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
-                        }
-                    }
-
                     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);
                 }
             }
@@ -422,14 +404,17 @@
                 }
                 newScanSettings.setSingleScan(reportFullResults, singleScanFreqs,
                         mPendingSingleScanEventHandler);
-                int[] hiddenNetworkIds = mPendingSingleScanSettings.hiddenNetworkIds;
-                if (hiddenNetworkIds != null) {
-                    int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length,
-                            MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
-                    for (int i = 0; i < numHiddenNetworkIds; i++) {
-                        hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
+
+                WifiNative.HiddenNetwork[] hiddenNetworks =
+                        mPendingSingleScanSettings.hiddenNetworks;
+                if (hiddenNetworks != null) {
+                    int numHiddenNetworks =
+                            Math.min(hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
+                    for (int i = 0; i < numHiddenNetworks; i++) {
+                        hiddenNetworkSSIDSet.add(hiddenNetworks[i].ssid);
                     }
                 }
+
                 mPendingSingleScanSettings = null;
                 mPendingSingleScanEventHandler = null;
             }
@@ -437,8 +422,8 @@
             if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
                     && !allFreqs.isEmpty()) {
                 pauseHwPnoScan();
-                Set<Integer> freqs = allFreqs.getSupplicantScanFreqs();
-                boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet);
+                Set<Integer> freqs = allFreqs.getScanFreqs();
+                boolean success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
                 if (success) {
                     // TODO handle scan timeout
                     if (DBG) {
@@ -448,7 +433,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);
@@ -469,9 +454,9 @@
                 // If the PNO network list has changed from the previous request, ensure that
                 // we bypass the debounce logic and restart PNO scan.
                 if (isDifferentPnoScanSettings(newScanSettings)) {
-                    status = restartHwPnoScan();
+                    status = restartHwPnoScan(mPnoSettings);
                 } else {
-                    status = startHwPnoScan();
+                    status = startHwPnoScan(mPnoSettings);
                 }
                 if (status) {
                     mLastScanSettings = newScanSettings;
@@ -502,6 +487,10 @@
                 reportScanFailure();
                 processPendingScans();
                 break;
+            case WifiMonitor.PNO_SCAN_RESULTS_EVENT:
+                pollLatestScanDataForPno();
+                processPendingScans();
+                break;
             case WifiMonitor.SCAN_RESULTS_EVENT:
                 mAlarmManager.cancel(mScanTimeoutListener);
                 pollLatestScanData();
@@ -540,6 +529,67 @@
         }
     }
 
+    private void pollLatestScanDataForPno() {
+        synchronized (mSettingsLock) {
+            if (mLastScanSettings == null) {
+                 // got a scan before we started scanning or after scan was canceled
+                return;
+            }
+            ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
+            List<ScanResult> hwPnoScanResults = new ArrayList<>();
+            int numFilteredScanResults = 0;
+            for (int i = 0; i < nativeResults.size(); ++i) {
+                ScanResult result = nativeResults.get(i).getScanResult();
+                long timestamp_ms = result.timestamp / 1000; // convert us -> ms
+                if (timestamp_ms > mLastScanSettings.startTime) {
+                    if (mLastScanSettings.hwPnoScanActive) {
+                        hwPnoScanResults.add(result);
+                    }
+                } else {
+                    numFilteredScanResults++;
+                }
+            }
+
+            if (numFilteredScanResults != 0) {
+                Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results.");
+            }
+
+            if (mLastScanSettings.hwPnoScanActive
+                    && mLastScanSettings.pnoScanEventHandler != null) {
+                ScanResult[] pnoScanResultsArray = new ScanResult[hwPnoScanResults.size()];
+                for (int i = 0; i < pnoScanResultsArray.length; ++i) {
+                    ScanResult result = nativeResults.get(i).getScanResult();
+                    pnoScanResultsArray[i] = hwPnoScanResults.get(i);
+                }
+                mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
+            }
+            // On pno scan result event, we are expecting a mLastScanSettings for pno scan.
+            // However, if unlikey mLastScanSettings is for single scan, we need this part
+            // to protect from leaving WifiSingleScanStateMachine in a forever wait state.
+            if (mLastScanSettings.singleScanActive
+                    && mLastScanSettings.singleScanEventHandler != null) {
+                Log.w(TAG, "Polling pno scan result when single scan is active, reporting"
+                        + " single scan failure");
+                mLastScanSettings.singleScanEventHandler
+                        .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
+            }
+            // mLastScanSettings is for either single/batched scan or pno scan.
+            // We can safely set it to null when pno scan finishes.
+            mLastScanSettings = null;
+        }
+    }
+
+    /**
+     * Check if the provided channel collection contains all the channels.
+     */
+    private static boolean isAllChannelsScanned(ChannelCollection channelCollection) {
+        // TODO(b/62253332): Get rid of this hack.
+        // We're treating 2g + 5g and 2g + 5g + dfs as all channels scanned to work around
+        // the lack of a proper cache.
+        return (channelCollection.containsBand(WifiScanner.WIFI_BAND_24_GHZ)
+                && channelCollection.containsBand(WifiScanner.WIFI_BAND_5_GHZ));
+    }
+
     private void pollLatestScanData() {
         synchronized (mSettingsLock) {
             if (mLastScanSettings == null) {
@@ -551,7 +601,7 @@
             ArrayList<ScanDetail> nativeResults = mWifiNative.getScanResults();
             List<ScanResult> singleScanResults = new ArrayList<>();
             List<ScanResult> backgroundScanResults = new ArrayList<>();
-            List<ScanResult> hwPnoScanResults = new ArrayList<>();
+            int numFilteredScanResults = 0;
             for (int i = 0; i < nativeResults.size(); ++i) {
                 ScanResult result = nativeResults.get(i).getScanResult();
                 long timestamp_ms = result.timestamp / 1000; // convert us -> ms
@@ -564,13 +614,13 @@
                                     result.frequency)) {
                         singleScanResults.add(result);
                     }
-                    if (mLastScanSettings.hwPnoScanActive) {
-                        hwPnoScanResults.add(result);
-                    }
                 } else {
-                    // was a cached result in wpa_supplicant
+                    numFilteredScanResults++;
                 }
             }
+            if (numFilteredScanResults != 0) {
+                Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
+            }
 
             if (mLastScanSettings.backgroundScanActive) {
                 if (mBackgroundScanEventHandler != null) {
@@ -613,18 +663,6 @@
                                 .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
                     }
                 }
-
-                if (mHotlistHandler != null) {
-                    int event = mHotlistChangeBuffer.processScan(backgroundScanResults);
-                    if ((event & ChangeBuffer.EVENT_FOUND) != 0) {
-                        mHotlistHandler.onHotlistApFound(
-                                mHotlistChangeBuffer.getLastResults(ChangeBuffer.EVENT_FOUND));
-                    }
-                    if ((event & ChangeBuffer.EVENT_LOST) != 0) {
-                        mHotlistHandler.onHotlistApLost(
-                                mHotlistChangeBuffer.getLastResults(ChangeBuffer.EVENT_LOST));
-                    }
-                }
             }
 
             if (mLastScanSettings.singleScanActive
@@ -638,21 +676,12 @@
                 }
                 Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
                 mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
-                        mLastScanSettings.singleScanFreqs.isAllChannels(),
+                        isAllChannelsScanned(mLastScanSettings.singleScanFreqs),
                         singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
                 mLastScanSettings.singleScanEventHandler
                         .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
             }
 
-            if (mLastScanSettings.hwPnoScanActive
-                    && mLastScanSettings.pnoScanEventHandler != null) {
-                ScanResult[] pnoScanResultsArray = new ScanResult[hwPnoScanResults.size()];
-                for (int i = 0; i < pnoScanResultsArray.length; ++i) {
-                    pnoScanResultsArray[i] = hwPnoScanResults.get(i);
-                }
-                mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
-            }
-
             mLastScanSettings = null;
         }
     }
@@ -669,27 +698,8 @@
         }
     }
 
-    private boolean setNetworkPriorities(WifiNative.PnoNetwork[] networkList) {
-        if (networkList != null) {
-            if (DBG) Log.i(TAG, "Enable network and Set priorities for PNO.");
-            for (WifiNative.PnoNetwork network : networkList) {
-                if (!mWifiNative.setNetworkVariable(network.networkId,
-                        WifiConfiguration.priorityVarName,
-                        Integer.toString(network.priority))) {
-                    Log.e(TAG, "Set priority failed for: " + network.networkId);
-                    return false;
-                }
-                if (!mWifiNative.enableNetworkWithoutConnect(network.networkId)) {
-                    Log.e(TAG, "Enable network failed for: " + network.networkId);
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    private boolean startHwPnoScan() {
-        return mHwPnoDebouncer.startPnoScan(mHwPnoDebouncerListener);
+    private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
+        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
     }
 
     private void stopHwPnoScan() {
@@ -700,9 +710,9 @@
         mHwPnoDebouncer.forceStopPnoScan();
     }
 
-    private boolean restartHwPnoScan() {
+    private boolean restartHwPnoScan(WifiNative.PnoSettings pnoSettings) {
         mHwPnoDebouncer.forceStopPnoScan();
-        return mHwPnoDebouncer.startPnoScan(mHwPnoDebouncerListener);
+        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
     }
 
     /**
@@ -729,8 +739,8 @@
             }
             mPnoEventHandler = eventHandler;
             mPnoSettings = settings;
-            if (!setNetworkPriorities(settings.networkList)) return false;
-            // For supplicant based PNO, we start the scan immediately when we set pno list.
+
+            // For wificond based PNO, we start the scan immediately when we set pno list.
             processPendingScans();
             return true;
         }
@@ -745,7 +755,7 @@
             }
             mPnoEventHandler = null;
             mPnoSettings = null;
-            // For supplicant based PNO, we stop the scan immediately when we reset pno list.
+            // For wificond based PNO, we stop the scan immediately when we reset pno list.
             stopHwPnoScan();
             return true;
         }
@@ -762,43 +772,10 @@
         return false;
     }
 
-    @Override
-    public boolean setHotlist(WifiScanner.HotlistSettings settings,
-            WifiNative.HotlistEventHandler eventHandler) {
-        if (settings == null || eventHandler == null) {
-            return false;
-        }
-        synchronized (mSettingsLock) {
-            mHotlistHandler = eventHandler;
-            mHotlistChangeBuffer.setSettings(settings.bssidInfos, settings.apLostThreshold, 1);
-            return true;
-        }
-    }
-
-    @Override
-    public void resetHotlist() {
-        synchronized (mSettingsLock) {
-            mHotlistChangeBuffer.clearSettings();
-            mHotlistHandler = null;
-        }
-    }
-
-    /*
-     * Significant Wifi Change API is not implemented
-     */
-    @Override
-    public boolean trackSignificantWifiChange(WifiScanner.WifiChangeSettings settings,
-            WifiNative.SignificantWifiChangeEventHandler handler) {
-        return false;
-    }
-    @Override
-    public void untrackSignificantWifiChange() {}
-
-
     private static class LastScanSettings {
         public long startTime;
 
-        public LastScanSettings(long startTime) {
+        LastScanSettings(long startTime) {
             this.startTime = startTime;
         }
 
@@ -853,7 +830,7 @@
         private final ArrayDeque<WifiScanner.ScanData> mBuffer;
         private int mCapacity;
 
-        public ScanBuffer(int capacity) {
+        ScanBuffer(int capacity) {
             mCapacity = capacity;
             mBuffer = new ArrayDeque<>(mCapacity);
         }
@@ -886,138 +863,6 @@
         }
     }
 
-    private static class ChangeBuffer {
-        public static int EVENT_NONE = 0;
-        public static int EVENT_LOST = 1;
-        public static int EVENT_FOUND = 2;
-
-        public static int STATE_FOUND = 0;
-
-        private WifiScanner.BssidInfo[] mBssidInfos = null;
-        private int mApLostThreshold;
-        private int mMinEvents;
-        private int[] mLostCount = null;
-        private ScanResult[] mMostRecentResult = null;
-        private int[] mPendingEvent = null;
-        private boolean mFiredEvents = false;
-
-        private static ScanResult findResult(List<ScanResult> results, String bssid) {
-            for (int i = 0; i < results.size(); ++i) {
-                if (bssid.equalsIgnoreCase(results.get(i).BSSID)) {
-                    return results.get(i);
-                }
-            }
-            return null;
-        }
-
-        public void setSettings(WifiScanner.BssidInfo[] bssidInfos, int apLostThreshold,
-                                int minEvents) {
-            mBssidInfos = bssidInfos;
-            if (apLostThreshold <= 0) {
-                mApLostThreshold = 1;
-            } else {
-                mApLostThreshold = apLostThreshold;
-            }
-            mMinEvents = minEvents;
-            if (bssidInfos != null) {
-                mLostCount = new int[bssidInfos.length];
-                Arrays.fill(mLostCount, mApLostThreshold); // default to lost
-                mMostRecentResult = new ScanResult[bssidInfos.length];
-                mPendingEvent = new int[bssidInfos.length];
-                mFiredEvents = false;
-            } else {
-                mLostCount = null;
-                mMostRecentResult = null;
-                mPendingEvent = null;
-            }
-        }
-
-        public void clearSettings() {
-            setSettings(null, 0, 0);
-        }
-
-        /**
-         * Get the most recent scan results for APs that triggered the given event on the last call
-         * to {@link #processScan}.
-         */
-        public ScanResult[] getLastResults(int event) {
-            ArrayList<ScanResult> results = new ArrayList<>();
-            for (int i = 0; i < mLostCount.length; ++i) {
-                if (mPendingEvent[i] == event) {
-                    results.add(mMostRecentResult[i]);
-                }
-            }
-            return results.toArray(new ScanResult[results.size()]);
-        }
-
-        /**
-         * Process the supplied scan results and determine if any events should be generated based
-         * on the configured settings
-         * @return The events that occurred
-         */
-        public int processScan(List<ScanResult> scanResults) {
-            if (mBssidInfos == null) {
-                return EVENT_NONE;
-            }
-
-            // clear events from last time
-            if (mFiredEvents) {
-                mFiredEvents = false;
-                for (int i = 0; i < mLostCount.length; ++i) {
-                    mPendingEvent[i] = EVENT_NONE;
-                }
-            }
-
-            int eventCount = 0;
-            int eventType = EVENT_NONE;
-            for (int i = 0; i < mLostCount.length; ++i) {
-                ScanResult result = findResult(scanResults, mBssidInfos[i].bssid);
-                int rssi = Integer.MIN_VALUE;
-                if (result != null) {
-                    mMostRecentResult[i] = result;
-                    rssi = result.level;
-                }
-
-                if (rssi < mBssidInfos[i].low) {
-                    if (mLostCount[i] < mApLostThreshold) {
-                        mLostCount[i]++;
-
-                        if (mLostCount[i] >= mApLostThreshold) {
-                            if (mPendingEvent[i] == EVENT_FOUND) {
-                                mPendingEvent[i] = EVENT_NONE;
-                            } else {
-                                mPendingEvent[i] = EVENT_LOST;
-                            }
-                        }
-                    }
-                } else {
-                    if (mLostCount[i] >= mApLostThreshold) {
-                        if (mPendingEvent[i] == EVENT_LOST) {
-                            mPendingEvent[i] = EVENT_NONE;
-                        } else {
-                            mPendingEvent[i] = EVENT_FOUND;
-                        }
-                    }
-                    mLostCount[i] = STATE_FOUND;
-                }
-                if (DBG) {
-                    Log.d(TAG, "ChangeBuffer BSSID: " + mBssidInfos[i].bssid + "=" + mLostCount[i]
-                            + ", " + mPendingEvent[i] + ", rssi=" + rssi);
-                }
-                if (mPendingEvent[i] != EVENT_NONE) {
-                    ++eventCount;
-                    eventType |= mPendingEvent[i];
-                }
-            }
-            if (DBG) Log.d(TAG, "ChangeBuffer events count=" + eventCount + ": " + eventType);
-            if (eventCount >= mMinEvents) {
-                mFiredEvents = true;
-                return eventType;
-            }
-            return EVENT_NONE;
-        }
-    }
-
     /**
      * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
      * state too often which is not handled very well by some drivers.
@@ -1036,6 +881,7 @@
         private boolean mCurrentPnoState = false;;
         private boolean mWaitForTimer = false;
         private Listener mListener;
+        private WifiNative.PnoSettings mPnoSettings;
 
         /**
          * Interface used to indicate PNO scan notifications.
@@ -1056,34 +902,63 @@
         }
 
         /**
-         * Enable/Disable PNO state in wpa_supplicant
-         * @param enable boolean indicating whether PNO is being enabled or disabled.
+         * Enable PNO state in wificond
          */
-        private boolean updatePnoState(boolean enable) {
-            if (mCurrentPnoState == enable) {
-                if (DBG) Log.d(TAG, "PNO state is already " + enable);
+        private boolean startPnoScanInternal() {
+            if (mCurrentPnoState) {
+                if (DBG) Log.d(TAG, "PNO state is already enable");
                 return true;
             }
-            mLastPnoChangeTimeStamp = mClock.elapsedRealtime();
-            if (mWifiNative.setPnoScan(enable)) {
-                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to " + enable);
-                mCurrentPnoState = enable;
-                return true;
-            } else {
-                Log.e(TAG, "PNO state change to " + enable + " failed");
-                mCurrentPnoState = false;
+            if (mPnoSettings == null) {
+                Log.e(TAG, "PNO state change to enable failed, no available Pno settings");
                 return false;
             }
+            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
+            Log.d(TAG, "Remove all networks from supplicant before starting PNO scan");
+            mWifiNative.removeAllNetworks();
+            if (mWifiNative.startPnoScan(mPnoSettings)) {
+                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to enable");
+                mCurrentPnoState = true;
+                return true;
+            } else {
+                Log.e(TAG, "PNO state change to enable failed");
+                mCurrentPnoState = false;
+            }
+            return false;
+        }
+
+        /**
+         * Disable PNO state in wificond
+         */
+        private boolean stopPnoScanInternal() {
+            if (!mCurrentPnoState) {
+                if (DBG) Log.d(TAG, "PNO state is already disable");
+                return true;
+            }
+            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
+            if (mWifiNative.stopPnoScan()) {
+                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to disable");
+                mCurrentPnoState = false;
+                return true;
+            } else {
+                Log.e(TAG, "PNO state change to disable failed");
+                mCurrentPnoState = false;
+            }
+            return false;
         }
 
         private final AlarmManager.OnAlarmListener mAlarmListener =
                 new AlarmManager.OnAlarmListener() {
             public void onAlarm() {
                 if (DBG) Log.d(TAG, "PNO timer expired, expected state " + mExpectedPnoState);
-                if (!updatePnoState(mExpectedPnoState)) {
-                    if (mListener != null) {
-                        mListener.onPnoScanFailed();
+                if (mExpectedPnoState) {
+                    if (!startPnoScanInternal()) {
+                        if (mListener != null) {
+                            mListener.onPnoScanFailed();
+                        }
                     }
+                } else {
+                    stopPnoScanInternal();
                 }
                 mWaitForTimer = false;
             }
@@ -1097,14 +972,19 @@
             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);
+                    if (enable) {
+                        isSuccess = startPnoScanInternal();
+                    } else {
+                        isSuccess = stopPnoScanInternal();
+                    }
                 } 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;
                 }
@@ -1115,9 +995,10 @@
         /**
          * Start PNO scan
          */
-        public boolean startPnoScan(Listener listener) {
+        public boolean startPnoScan(WifiNative.PnoSettings pnoSettings, Listener listener) {
             if (DBG) Log.d(TAG, "Starting PNO scan");
             mListener = listener;
+            mPnoSettings = pnoSettings;
             if (!setPnoState(true)) {
                 mListener = null;
                 return false;
@@ -1145,7 +1026,7 @@
                 mAlarmManager.cancel(mAlarmListener);
                 mWaitForTimer = false;
             }
-            updatePnoState(false);
+            stopPnoScanInternal();
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/util/BitMask.java b/service/java/com/android/server/wifi/util/BitMask.java
new file mode 100644
index 0000000..a6a82fd
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/BitMask.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+/**
+ * Helper for translating bit-flags packed into an int
+ */
+public class BitMask {
+    public int value;
+
+    public BitMask(int value) {
+        this.value = value;
+    }
+
+    /**
+     * Clears the specifed bit, returning true if it was set
+     *
+     * @param maskBit to test and clear
+     * @return true if and only if the mask was originally set in value
+     */
+    public boolean testAndClear(int maskBit) {
+        boolean ans = (value & maskBit) != 0;
+        value &= ~maskBit;
+        return ans;
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/InformationElementUtil.java b/service/java/com/android/server/wifi/util/InformationElementUtil.java
index d3e2fea..c8f9ca3 100644
--- a/service/java/com/android/server/wifi/util/InformationElementUtil.java
+++ b/service/java/com/android/server/wifi/util/InformationElementUtil.java
@@ -15,16 +15,14 @@
  */
 package com.android.server.wifi.util;
 
-import static com.android.server.wifi.anqp.Constants.getInteger;
-
+import android.net.wifi.ScanResult;
 import android.net.wifi.ScanResult.InformationElement;
 import android.util.Log;
 
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.anqp.VenueNameElement;
+import com.android.server.wifi.ByteBufferReader;
 import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.anqp.Constants;
 
-import java.net.ProtocolException;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
@@ -32,6 +30,7 @@
 import java.util.BitSet;
 
 public class InformationElementUtil {
+    private static final String TAG = "InformationElementUtil";
 
     public static InformationElement[] parseInformationElements(byte[] bytes) {
         if (bytes == null) {
@@ -65,6 +64,71 @@
         return infoElements.toArray(new InformationElement[infoElements.size()]);
     }
 
+    /**
+     * Parse and retrieve the Roaming Consortium Information Element from the list of IEs.
+     *
+     * @param ies List of IEs to retrieve from
+     * @return {@link RoamingConsortium}
+     */
+    public static RoamingConsortium getRoamingConsortiumIE(InformationElement[] ies) {
+        RoamingConsortium roamingConsortium = new RoamingConsortium();
+        if (ies != null) {
+            for (InformationElement ie : ies) {
+                if (ie.id == InformationElement.EID_ROAMING_CONSORTIUM) {
+                    try {
+                        roamingConsortium.from(ie);
+                    } catch (RuntimeException e) {
+                        Log.e(TAG, "Failed to parse Roaming Consortium IE: " + e.getMessage());
+                    }
+                }
+            }
+        }
+        return roamingConsortium;
+    }
+
+    /**
+     * Parse and retrieve the Hotspot 2.0 Vendor Specific Information Element from the list of IEs.
+     *
+     * @param ies List of IEs to retrieve from
+     * @return {@link Vsa}
+     */
+    public static Vsa getHS2VendorSpecificIE(InformationElement[] ies) {
+        Vsa vsa = new Vsa();
+        if (ies != null) {
+            for (InformationElement ie : ies) {
+                if (ie.id == InformationElement.EID_VSA) {
+                    try {
+                        vsa.from(ie);
+                    } catch (RuntimeException e) {
+                        Log.e(TAG, "Failed to parse Vendor Specific IE: " + e.getMessage());
+                    }
+                }
+            }
+        }
+        return vsa;
+    }
+
+    /**
+     * Parse and retrieve the Interworking information element from the list of IEs.
+     *
+     * @param ies List of IEs to retrieve from
+     * @return {@link Interworking}
+     */
+    public static Interworking getInterworkingIE(InformationElement[] ies) {
+        Interworking interworking = new Interworking();
+        if (ies != null) {
+            for (InformationElement ie : ies) {
+                if (ie.id == InformationElement.EID_INTERWORKING) {
+                    try {
+                        interworking.from(ie);
+                    } catch (RuntimeException e) {
+                        Log.e(TAG, "Failed to parse Interworking IE: " + e.getMessage());
+                    }
+                }
+            }
+        }
+        return interworking;
+    }
 
     public static class BssLoad {
         public int stationCount = 0;
@@ -160,8 +224,6 @@
     public static class Interworking {
         public NetworkDetail.Ant ant = null;
         public boolean internet = false;
-        public VenueNameElement.VenueGroup venueGroup = null;
-        public VenueNameElement.VenueType venueType = null;
         public long hessid = 0L;
 
         public void from(InformationElement ie) {
@@ -172,24 +234,21 @@
             int anOptions = data.get() & Constants.BYTE_MASK;
             ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
             internet = (anOptions & 0x10) != 0;
-            // Len 1 none, 3 venue-info, 7 HESSID, 9 venue-info & HESSID
-            if (ie.bytes.length == 3 || ie.bytes.length == 9) {
-                try {
-                    ByteBuffer vinfo = data.duplicate();
-                    vinfo.limit(vinfo.position() + 2);
-                    VenueNameElement vne = new VenueNameElement(
-                            Constants.ANQPElementType.ANQPVenueName, vinfo);
-                    venueGroup = vne.getGroup();
-                    venueType = vne.getType();
-                } catch (ProtocolException pe) {
-                    /*Cannot happen*/
-                }
-            } else if (ie.bytes.length != 1 && ie.bytes.length != 7) {
-                throw new IllegalArgumentException("Bad Interworking element length: "
-                        + ie.bytes.length);
+            // There are only three possible lengths for the Interworking IE:
+            // Len 1: Access Network Options only
+            // Len 3: Access Network Options & Venue Info
+            // Len 7: Access Network Options & HESSID
+            // Len 9: Access Network Options, Venue Info, & HESSID
+            if (ie.bytes.length != 1
+                    && ie.bytes.length != 3
+                    && ie.bytes.length != 7
+                    && ie.bytes.length != 9) {
+                throw new IllegalArgumentException(
+                        "Bad Interworking element length: " + ie.bytes.length);
             }
+
             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
-                hessid = getInteger(data, ByteOrder.BIG_ENDIAN, 6);
+                hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
             }
         }
     }
@@ -223,15 +282,15 @@
             roamingConsortiums = new long[oiCount];
             if (oi1Length > 0 && roamingConsortiums.length > 0) {
                 roamingConsortiums[0] =
-                        getInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
+                        ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi1Length);
             }
             if (oi2Length > 0 && roamingConsortiums.length > 1) {
                 roamingConsortiums[1] =
-                        getInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
+                        ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi2Length);
             }
             if (oi3Length > 0 && roamingConsortiums.length > 2) {
                 roamingConsortiums[2] =
-                        getInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
+                        ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, oi3Length);
             }
         }
     }
@@ -268,37 +327,52 @@
         }
     }
 
+    /**
+     * This IE contained a bit field indicating the capabilities being advertised by the STA.
+     * The size of the bit field (number of bytes) is indicated by the |Length| field in the IE.
+     *
+     * Refer to Section 8.4.2.29 in IEEE 802.11-2012 Spec for capability associated with each
+     * bit.
+     *
+     * Here is the wire format of this IE:
+     * | Element ID | Length | Capabilities |
+     *       1           1          n
+     */
     public static class ExtendedCapabilities {
         private static final int RTT_RESP_ENABLE_BIT = 70;
-        private static final long SSID_UTF8_BIT = 0x0001000000000000L;
+        private static final int SSID_UTF8_BIT = 48;
 
-        public Long extendedCapabilities = null;
-        public boolean is80211McRTTResponder = false;
+        public BitSet capabilitiesBitSet;
+
+        /**
+         * @return true if SSID should be interpreted using UTF-8 encoding
+         */
+        public boolean isStrictUtf8() {
+            return capabilitiesBitSet.get(SSID_UTF8_BIT);
+        }
+
+        /**
+         * @return true if 802.11 MC RTT Response is enabled
+         */
+        public boolean is80211McRTTResponder() {
+            return capabilitiesBitSet.get(RTT_RESP_ENABLE_BIT);
+        }
 
         public ExtendedCapabilities() {
+            capabilitiesBitSet = new BitSet();
         }
 
         public ExtendedCapabilities(ExtendedCapabilities other) {
-            extendedCapabilities = other.extendedCapabilities;
-            is80211McRTTResponder = other.is80211McRTTResponder;
+            capabilitiesBitSet = other.capabilitiesBitSet;
         }
 
-        public boolean isStrictUtf8() {
-            return extendedCapabilities != null && (extendedCapabilities & SSID_UTF8_BIT) != 0;
-        }
-
+        /**
+         * Parse an ExtendedCapabilities from the IE containing raw bytes.
+         *
+         * @param ie The Information element data
+         */
         public void from(InformationElement ie) {
-            ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
-            extendedCapabilities =
-                    Constants.getInteger(data, ByteOrder.LITTLE_ENDIAN, ie.bytes.length);
-
-            int index = RTT_RESP_ENABLE_BIT / 8;
-            byte offset = RTT_RESP_ENABLE_BIT % 8;
-            if (ie.bytes.length < index + 1) {
-                is80211McRTTResponder = false;
-            } else {
-                is80211McRTTResponder = (ie.bytes[index] & ((byte) 0x1 << offset)) != 0;
-            }
+            capabilitiesBitSet = BitSet.valueOf(ie.bytes);
         }
     }
 
@@ -315,6 +389,7 @@
         private static final int CAP_PRIVACY_BIT_OFFSET = 4;
 
         private static final int WPA_VENDOR_OUI_TYPE_ONE = 0x01f25000;
+        private static final int WPS_VENDOR_OUI_TYPE = 0x04f25000;
         private static final short WPA_VENDOR_OUI_VERSION = 0x0001;
         private static final short RSNE_VERSION = 0x0001;
 
@@ -328,6 +403,23 @@
         private static final int WPA2_AKM_EAP_SHA256 = 0x05ac0f00;
         private static final int WPA2_AKM_PSK_SHA256 = 0x06ac0f00;
 
+        private static final int WPA_CIPHER_NONE = 0x00f25000;
+        private static final int WPA_CIPHER_TKIP = 0x02f25000;
+        private static final int WPA_CIPHER_CCMP = 0x04f25000;
+
+        private static final int RSN_CIPHER_NONE = 0x00ac0f00;
+        private static final int RSN_CIPHER_TKIP = 0x02ac0f00;
+        private static final int RSN_CIPHER_CCMP = 0x04ac0f00;
+        private static final int RSN_CIPHER_NO_GROUP_ADDRESSED = 0x07ac0f00;
+
+        public ArrayList<Integer> protocol;
+        public ArrayList<ArrayList<Integer>> keyManagement;
+        public ArrayList<ArrayList<Integer>> pairwiseCipher;
+        public ArrayList<Integer> groupCipher;
+        public boolean isESS;
+        public boolean isPrivacy;
+        public boolean isWPS;
+
         public Capabilities() {
         }
 
@@ -344,80 +436,112 @@
         //
         // Note: InformationElement.bytes has 'Element ID' and 'Length'
         //       stripped off already
-        private static String parseRsnElement(InformationElement ie) {
+        private void parseRsnElement(InformationElement ie) {
             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
 
             try {
                 // version
                 if (buf.getShort() != RSNE_VERSION) {
                     // incorrect version
-                    return null;
+                    return;
                 }
 
-                // group data cipher suite
-                // here we simply advance the buffer position
-                buf.getInt();
-
                 // found the RSNE IE, hence start building the capability string
-                String security = "[WPA2";
+                protocol.add(ScanResult.PROTOCOL_WPA2);
+
+                // group data cipher suite
+                groupCipher.add(parseRsnCipher(buf.getInt()));
 
                 // pairwise cipher suite count
                 short cipherCount = buf.getShort();
-
+                ArrayList<Integer> rsnPairwiseCipher = new ArrayList<>();
                 // pairwise cipher suite list
                 for (int i = 0; i < cipherCount; i++) {
-                    // here we simply advance the buffer position
-                    buf.getInt();
+                    rsnPairwiseCipher.add(parseRsnCipher(buf.getInt()));
                 }
+                pairwiseCipher.add(rsnPairwiseCipher);
 
                 // AKM
                 // AKM suite count
                 short akmCount = buf.getShort();
+                ArrayList<Integer> rsnKeyManagement = new ArrayList<>();
 
-                // parse AKM suite list
-                if (akmCount == 0) {
-                    security += "-EAP"; //default AKM
-                }
-                boolean found = false;
                 for (int i = 0; i < akmCount; i++) {
                     int akm = buf.getInt();
                     switch (akm) {
                         case WPA2_AKM_EAP:
-                            security += (found ? "+" : "-") + "EAP";
-                            found = true;
+                            rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
                             break;
                         case WPA2_AKM_PSK:
-                            security += (found ? "+" : "-") + "PSK";
-                            found = true;
+                            rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK);
                             break;
                         case WPA2_AKM_FT_EAP:
-                            security += (found ? "+" : "-") + "FT/EAP";
-                            found = true;
+                            rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_EAP);
                             break;
                         case WPA2_AKM_FT_PSK:
-                            security += (found ? "+" : "-") + "FT/PSK";
-                            found = true;
+                            rsnKeyManagement.add(ScanResult.KEY_MGMT_FT_PSK);
                             break;
                         case WPA2_AKM_EAP_SHA256:
-                            security += (found ? "+" : "-") + "EAP-SHA256";
-                            found = true;
+                            rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP_SHA256);
                             break;
                         case WPA2_AKM_PSK_SHA256:
-                            security += (found ? "+" : "-") + "PSK-SHA256";
-                            found = true;
+                            rsnKeyManagement.add(ScanResult.KEY_MGMT_PSK_SHA256);
                             break;
                         default:
                             // do nothing
                             break;
                     }
                 }
-
-                // we parsed what we want at this point
-                security += "]";
-                return security;
+                // Default AKM
+                if (rsnKeyManagement.isEmpty()) {
+                    rsnKeyManagement.add(ScanResult.KEY_MGMT_EAP);
+                }
+                keyManagement.add(rsnKeyManagement);
             } catch (BufferUnderflowException e) {
                 Log.e("IE_Capabilities", "Couldn't parse RSNE, buffer underflow");
-                return null;
+            }
+        }
+
+        private static int parseWpaCipher(int cipher) {
+            switch (cipher) {
+                case WPA_CIPHER_NONE:
+                    return ScanResult.CIPHER_NONE;
+                case WPA_CIPHER_TKIP:
+                    return ScanResult.CIPHER_TKIP;
+                case WPA_CIPHER_CCMP:
+                    return ScanResult.CIPHER_CCMP;
+                default:
+                    Log.w("IE_Capabilities", "Unknown WPA cipher suite: "
+                            + Integer.toHexString(cipher));
+                    return ScanResult.CIPHER_NONE;
+            }
+        }
+
+        private static int parseRsnCipher(int cipher) {
+            switch (cipher) {
+                case RSN_CIPHER_NONE:
+                    return ScanResult.CIPHER_NONE;
+                case RSN_CIPHER_TKIP:
+                    return ScanResult.CIPHER_TKIP;
+                case RSN_CIPHER_CCMP:
+                    return ScanResult.CIPHER_CCMP;
+                case RSN_CIPHER_NO_GROUP_ADDRESSED:
+                    return ScanResult.CIPHER_NO_GROUP_ADDRESSED;
+                default:
+                    Log.w("IE_Capabilities", "Unknown RSN cipher suite: "
+                            + Integer.toHexString(cipher));
+                    return ScanResult.CIPHER_NONE;
+            }
+        }
+
+        private static boolean isWpsElement(InformationElement ie) {
+            ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
+            try {
+                // WPS OUI and type
+                return (buf.getInt() == WPS_VENDOR_OUI_TYPE);
+            } catch (BufferUnderflowException e) {
+                Log.e("IE_Capabilities", "Couldn't parse VSA IE, buffer underflow");
+                return false;
             }
         }
 
@@ -437,6 +561,8 @@
         //
         // | Element ID | Length | OUI | Type | Version |
         //      1           1       3     1        2
+        // | Group Data Cipher Suite |
+        //             4
         // | Pairwise Cipher Suite Count | Pairwise Cipher Suite List |
         //              2                            4 * m
         // | AKM Suite Count | AKM Suite List |
@@ -445,7 +571,7 @@
         // Note: InformationElement.bytes has 'Element ID' and 'Length'
         //       stripped off already
         //
-        private static String parseWpaOneElement(InformationElement ie) {
+        private void parseWpaOneElement(InformationElement ie) {
             ByteBuffer buf = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
 
             try {
@@ -453,105 +579,175 @@
                 // been called for verification before we reach here.
                 buf.getInt();
 
-                // start building the string
-                String security = "[WPA";
-
                 // version
                 if (buf.getShort() != WPA_VENDOR_OUI_VERSION)  {
                     // incorrect version
-                    return null;
+                    return;
                 }
 
+                // start building the string
+                protocol.add(ScanResult.PROTOCOL_WPA);
+
                 // group data cipher suite
-                // here we simply advance buffer position
-                buf.getInt();
+                groupCipher.add(parseWpaCipher(buf.getInt()));
 
                 // pairwise cipher suite count
                 short cipherCount = buf.getShort();
-
+                ArrayList<Integer> wpaPairwiseCipher = new ArrayList<>();
                 // pairwise chipher suite list
                 for (int i = 0; i < cipherCount; i++) {
-                    // here we simply advance buffer position
-                    buf.getInt();
+                    wpaPairwiseCipher.add(parseWpaCipher(buf.getInt()));
                 }
+                pairwiseCipher.add(wpaPairwiseCipher);
 
                 // AKM
                 // AKM suite count
                 short akmCount = buf.getShort();
+                ArrayList<Integer> wpaKeyManagement = new ArrayList<>();
 
                 // AKM suite list
-                if (akmCount == 0) {
-                    security += "-EAP"; //default AKM
-                }
-                boolean found = false;
                 for (int i = 0; i < akmCount; i++) {
                     int akm = buf.getInt();
                     switch (akm) {
                         case WPA_AKM_EAP:
-                            security += (found ? "+" : "-") + "EAP";
-                            found = true;
+                            wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
                             break;
                         case WPA_AKM_PSK:
-                            security += (found ? "+" : "-") + "PSK";
-                            found = true;
+                            wpaKeyManagement.add(ScanResult.KEY_MGMT_PSK);
                             break;
                         default:
                             // do nothing
                             break;
                     }
                 }
-
-                // we parsed what we want at this point
-                security += "]";
-                return security;
+                // Default AKM
+                if (wpaKeyManagement.isEmpty()) {
+                    wpaKeyManagement.add(ScanResult.KEY_MGMT_EAP);
+                }
+                keyManagement.add(wpaKeyManagement);
             } catch (BufferUnderflowException e) {
                 Log.e("IE_Capabilities", "Couldn't parse type 1 WPA, buffer underflow");
-                return null;
             }
         }
 
         /**
          * Parse the Information Element and the 16-bit Capability Information field
-         * to build the ScanResult.capabilities String.
+         * to build the InformationElemmentUtil.capabilities object.
          *
          * @param ies -- Information Element array
          * @param beaconCap -- 16-bit Beacon Capability Information field
-         * @return security string that mirrors what wpa_supplicant generates
          */
-        public static String buildCapabilities(InformationElement[] ies, BitSet beaconCap) {
-            String capabilities = "";
-            boolean rsneFound = false;
-            boolean wpaFound = false;
+
+        public void from(InformationElement[] ies, BitSet beaconCap) {
+            protocol = new ArrayList<Integer>();
+            keyManagement = new ArrayList<ArrayList<Integer>>();
+            groupCipher = new ArrayList<Integer>();
+            pairwiseCipher = new ArrayList<ArrayList<Integer>>();
 
             if (ies == null || beaconCap == null) {
-                return capabilities;
+                return;
             }
-
-            boolean ess = beaconCap.get(CAP_ESS_BIT_OFFSET);
-            boolean privacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
-
+            isESS = beaconCap.get(CAP_ESS_BIT_OFFSET);
+            isPrivacy = beaconCap.get(CAP_PRIVACY_BIT_OFFSET);
             for (InformationElement ie : ies) {
                 if (ie.id == InformationElement.EID_RSN) {
-                    rsneFound = true;
-                    capabilities += parseRsnElement(ie);
+                    parseRsnElement(ie);
                 }
 
                 if (ie.id == InformationElement.EID_VSA) {
                     if (isWpaOneElement(ie)) {
-                        wpaFound = true;
-                        capabilities += parseWpaOneElement(ie);
+                        parseWpaOneElement(ie);
+                    }
+                    if (isWpsElement(ie)) {
+                        // TODO(b/62134557): parse WPS IE to provide finer granularity information.
+                        isWPS = true;
                     }
                 }
             }
+        }
 
-            if (!rsneFound && !wpaFound && privacy) {
-                //private Beacon without an RSNE or WPA IE, hence WEP0
+        private String protocolToString(int protocol) {
+            switch (protocol) {
+                case ScanResult.PROTOCOL_NONE:
+                    return "None";
+                case ScanResult.PROTOCOL_WPA:
+                    return "WPA";
+                case ScanResult.PROTOCOL_WPA2:
+                    return "WPA2";
+                default:
+                    return "?";
+            }
+        }
+
+        private String keyManagementToString(int akm) {
+            switch (akm) {
+                case ScanResult.KEY_MGMT_NONE:
+                    return "None";
+                case ScanResult.KEY_MGMT_PSK:
+                    return "PSK";
+                case ScanResult.KEY_MGMT_EAP:
+                    return "EAP";
+                case ScanResult.KEY_MGMT_FT_EAP:
+                    return "FT/EAP";
+                case ScanResult.KEY_MGMT_FT_PSK:
+                    return "FT/PSK";
+                case ScanResult.KEY_MGMT_EAP_SHA256:
+                    return "EAP-SHA256";
+                case ScanResult.KEY_MGMT_PSK_SHA256:
+                    return "PSK-SHA256";
+                default:
+                    return "?";
+            }
+        }
+
+        private String cipherToString(int cipher) {
+            switch (cipher) {
+                case ScanResult.CIPHER_NONE:
+                    return "None";
+                case ScanResult.CIPHER_CCMP:
+                    return "CCMP";
+                case ScanResult.CIPHER_TKIP:
+                    return "TKIP";
+                default:
+                    return "?";
+            }
+        }
+
+        /**
+         * Build the ScanResult.capabilities String.
+         *
+         * @return security string that mirrors what wpa_supplicant generates
+         */
+        public String generateCapabilitiesString() {
+            String capabilities = "";
+            // private Beacon without an RSNE or WPA IE, hence WEP0
+            boolean isWEP = (protocol.isEmpty()) && isPrivacy;
+
+            if (isWEP) {
                 capabilities += "[WEP]";
             }
-
-            if (ess) {
+            for (int i = 0; i < protocol.size(); i++) {
+                capabilities += "[" + protocolToString(protocol.get(i));
+                if (i < keyManagement.size()) {
+                    for (int j = 0; j < keyManagement.get(i).size(); j++) {
+                        capabilities += ((j == 0) ? "-" : "+")
+                                + keyManagementToString(keyManagement.get(i).get(j));
+                    }
+                }
+                if (i < pairwiseCipher.size()) {
+                    for (int j = 0; j < pairwiseCipher.get(i).size(); j++) {
+                        capabilities += ((j == 0) ? "-" : "+")
+                                + cipherToString(pairwiseCipher.get(i).get(j));
+                    }
+                }
+                capabilities += "]";
+            }
+            if (isESS) {
                 capabilities += "[ESS]";
             }
+            if (isWPS) {
+                capabilities += "[WPS]";
+            }
 
             return capabilities;
         }
diff --git a/service/java/com/android/server/wifi/util/NativeUtil.java b/service/java/com/android/server/wifi/util/NativeUtil.java
new file mode 100644
index 0000000..07c3f9b
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/NativeUtil.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import android.text.TextUtils;
+
+import com.android.server.wifi.ByteBufferReader;
+
+import libcore.util.HexEncoding;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/**
+ * Provide utility functions for native interfacing modules.
+ */
+public class NativeUtil {
+    private static final String ANY_MAC_STR = "any";
+    public static final byte[] ANY_MAC_BYTES = {0, 0, 0, 0, 0, 0};
+    private static final int MAC_LENGTH = 6;
+    private static final int MAC_OUI_LENGTH = 3;
+    private static final int MAC_STR_LENGTH = MAC_LENGTH * 2 + 5;
+
+    /**
+     * Convert the string to byte array list.
+     *
+     * @return the UTF_8 char byte values of str, as an ArrayList.
+     * @throws IllegalArgumentException if a null string is sent.
+     */
+    public static ArrayList<Byte> stringToByteArrayList(String str) {
+        if (str == null) {
+            throw new IllegalArgumentException("null string");
+        }
+        ArrayList<Byte> byteArrayList = new ArrayList<Byte>();
+        for (byte b : str.getBytes(StandardCharsets.UTF_8)) {
+            byteArrayList.add(new Byte(b));
+        }
+        return byteArrayList;
+    }
+
+    /**
+     * Convert the byte array list to string.
+     *
+     * @return the string decoded from UTF_8 byte values in byteArrayList.
+     * @throws IllegalArgumentException if a null byte array list is sent.
+     */
+    public static String stringFromByteArrayList(ArrayList<Byte> byteArrayList) {
+        if (byteArrayList == null) {
+            throw new IllegalArgumentException("null byte array list");
+        }
+        byte[] byteArray = new byte[byteArrayList.size()];
+        int i = 0;
+        for (Byte b : byteArrayList) {
+            byteArray[i] = b;
+            i++;
+        }
+        return new String(byteArray, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Convert the string to byte array.
+     *
+     * @return the UTF_8 char byte values of str, as an Array.
+     * @throws IllegalArgumentException if a null string is sent.
+     */
+    public static byte[] stringToByteArray(String str) {
+        if (str == null) {
+            throw new IllegalArgumentException("null string");
+        }
+        return str.getBytes(StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Convert the byte array list to string.
+     *
+     * @return the string decoded from UTF_8 byte values in byteArray.
+     * @throws IllegalArgumentException if a null byte array is sent.
+     */
+    public static String stringFromByteArray(byte[] byteArray) {
+        if (byteArray == null) {
+            throw new IllegalArgumentException("null byte array");
+        }
+        return new String(byteArray);
+    }
+
+    /**
+     * Converts a mac address string to an array of Bytes.
+     *
+     * @param macStr string of format: "XX:XX:XX:XX:XX:XX" or "XXXXXXXXXXXX", where X is any
+     *        hexadecimal digit.
+     *        Passing null, empty string or "any" is the same as 00:00:00:00:00:00
+     * @throws IllegalArgumentException for various malformed inputs.
+     */
+    public static byte[] macAddressToByteArray(String macStr) {
+        if (TextUtils.isEmpty(macStr) || ANY_MAC_STR.equals(macStr)) return ANY_MAC_BYTES;
+        String cleanMac = macStr.replace(":", "");
+        if (cleanMac.length() != MAC_LENGTH * 2) {
+            throw new IllegalArgumentException("invalid mac string length: " + cleanMac);
+        }
+        return HexEncoding.decode(cleanMac.toCharArray(), false);
+    }
+
+    /**
+     * Converts an array of 6 bytes to a HexEncoded String with format: "XX:XX:XX:XX:XX:XX", where X
+     * is any hexadecimal digit.
+     *
+     * @param macArray byte array of mac values, must have length 6
+     * @throws IllegalArgumentException for malformed inputs.
+     */
+    public static String macAddressFromByteArray(byte[] macArray) {
+        if (macArray == null) {
+            throw new IllegalArgumentException("null mac bytes");
+        }
+        if (macArray.length != MAC_LENGTH) {
+            throw new IllegalArgumentException("invalid macArray length: " + macArray.length);
+        }
+        StringBuilder sb = new StringBuilder(MAC_STR_LENGTH);
+        for (int i = 0; i < macArray.length; i++) {
+            if (i != 0) sb.append(":");
+            sb.append(new String(HexEncoding.encode(macArray, i, 1)));
+        }
+        return sb.toString().toLowerCase();
+    }
+
+    /**
+     * Converts a mac address OUI string to an array of Bytes.
+     *
+     * @param macStr string of format: "XX:XX:XX" or "XXXXXX", where X is any hexadecimal digit.
+     * @throws IllegalArgumentException for various malformed inputs.
+     */
+    public static byte[] macAddressOuiToByteArray(String macStr) {
+        if (macStr == null) {
+            throw new IllegalArgumentException("null mac string");
+        }
+        String cleanMac = macStr.replace(":", "");
+        if (cleanMac.length() != MAC_OUI_LENGTH * 2) {
+            throw new IllegalArgumentException("invalid mac oui string length: " + cleanMac);
+        }
+        return HexEncoding.decode(cleanMac.toCharArray(), false);
+    }
+
+    /**
+     * Converts an array of 6 bytes to a long representing the MAC address.
+     *
+     * @param macArray byte array of mac values, must have length 6
+     * @return Long value of the mac address.
+     * @throws IllegalArgumentException for malformed inputs.
+     */
+    public static Long macAddressToLong(byte[] macArray) {
+        if (macArray == null) {
+            throw new IllegalArgumentException("null mac bytes");
+        }
+        if (macArray.length != MAC_LENGTH) {
+            throw new IllegalArgumentException("invalid macArray length: " + macArray.length);
+        }
+        try {
+            return ByteBufferReader.readInteger(
+                    ByteBuffer.wrap(macArray), ByteOrder.BIG_ENDIAN, macArray.length);
+        } catch (BufferUnderflowException | IllegalArgumentException e) {
+            throw new IllegalArgumentException("invalid macArray");
+        }
+    }
+
+    /**
+     * Remove enclosed quotes of the provided string.
+     *
+     * @param quotedStr String to be unquoted.
+     * @return String without the enclosing quotes.
+     */
+    public static String removeEnclosingQuotes(String quotedStr) {
+        int length = quotedStr.length();
+        if ((length >= 2)
+                && (quotedStr.charAt(0) == '"') && (quotedStr.charAt(length - 1) == '"')) {
+            return quotedStr.substring(1, length - 1);
+        }
+        return quotedStr;
+    }
+
+    /**
+     * Add enclosing quotes of the provided string.
+     *
+     * @param str String to be uoted.
+     * @return String with the enclosing quotes.
+     */
+    public static String addEnclosingQuotes(String str) {
+        return "\"" + str + "\"";
+    }
+
+    /**
+     * Converts an string to an arraylist of UTF_8 byte values.
+     * These forms are acceptable:
+     * a) ASCII String encapsulated in quotes, or
+     * b) Hex string with no delimiters.
+     *
+     * @param str String to be converted.
+     * @throws IllegalArgumentException for null string.
+     */
+    public static ArrayList<Byte> hexOrQuotedAsciiStringToBytes(String str) {
+        if (str == null) {
+            throw new IllegalArgumentException("null string");
+        }
+        int length = str.length();
+        if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) {
+            str = str.substring(1, str.length() - 1);
+            return stringToByteArrayList(str);
+        } else {
+            return byteArrayToArrayList(hexStringToByteArray(str));
+        }
+    }
+
+    /**
+     * Converts an ArrayList<Byte> of UTF_8 byte values to string.
+     * The string will either be:
+     * a) ASCII String encapsulated in quotes (if all the bytes are ASCII encodeable and non null),
+     * or
+     * b) Hex string with no delimiters.
+     *
+     * @param bytes List of bytes for ssid.
+     * @throws IllegalArgumentException for null bytes.
+     */
+    public static String bytesToHexOrQuotedAsciiString(ArrayList<Byte> bytes) {
+        if (bytes == null) {
+            throw new IllegalArgumentException("null ssid bytes");
+        }
+        byte[] byteArray = byteArrayFromArrayList(bytes);
+        // Check for 0's in the byte stream in which case we cannot convert this into a string.
+        if (!bytes.contains(Byte.valueOf((byte) 0))) {
+            CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder();
+            try {
+                CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray));
+                return "\"" + decoded.toString() + "\"";
+            } catch (CharacterCodingException cce) {
+            }
+        }
+        return hexStringFromByteArray(byteArray);
+    }
+
+    /**
+     * Converts an ssid string to an arraylist of UTF_8 byte values.
+     * These forms are acceptable:
+     * a) ASCII String encapsulated in quotes, or
+     * b) Hex string with no delimiters.
+     *
+     * @param ssidStr String to be converted.
+     * @throws IllegalArgumentException for null string.
+     */
+    public static ArrayList<Byte> decodeSsid(String ssidStr) {
+        return hexOrQuotedAsciiStringToBytes(ssidStr);
+    }
+
+    /**
+     * Converts an ArrayList<Byte> of UTF_8 byte values to ssid string.
+     * The string will either be:
+     * a) ASCII String encapsulated in quotes (if all the bytes are ASCII encodeable and non null),
+     * or
+     * b) Hex string with no delimiters.
+     *
+     * @param ssidBytes List of bytes for ssid.
+     * @throws IllegalArgumentException for null bytes.
+     */
+    public static String encodeSsid(ArrayList<Byte> ssidBytes) {
+        return bytesToHexOrQuotedAsciiString(ssidBytes);
+    }
+
+    /**
+     * Convert from an array of primitive bytes to an array list of Byte.
+     */
+    public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) {
+        ArrayList<Byte> byteList = new ArrayList<>();
+        for (Byte b : bytes) {
+            byteList.add(b);
+        }
+        return byteList;
+    }
+
+    /**
+     * Convert from an array list of Byte to an array of primitive bytes.
+     */
+    public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) {
+        byte[] byteArray = new byte[bytes.size()];
+        int i = 0;
+        for (Byte b : bytes) {
+            byteArray[i++] = b;
+        }
+        return byteArray;
+    }
+
+    /**
+     * Converts a hex string to byte array.
+     *
+     * @param hexStr String to be converted.
+     * @throws IllegalArgumentException for null string.
+     */
+    public static byte[] hexStringToByteArray(String hexStr) {
+        if (hexStr == null) {
+            throw new IllegalArgumentException("null hex string");
+        }
+        return HexEncoding.decode(hexStr.toCharArray(), false);
+    }
+
+    /**
+     * Converts a byte array to hex string.
+     *
+     * @param bytes List of bytes for ssid.
+     * @throws IllegalArgumentException for null bytes.
+     */
+    public static String hexStringFromByteArray(byte[] bytes) {
+        if (bytes == null) {
+            throw new IllegalArgumentException("null hex bytes");
+        }
+        return new String(HexEncoding.encode(bytes)).toLowerCase();
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/OWNERS b/service/java/com/android/server/wifi/util/OWNERS
new file mode 100644
index 0000000..9e0a7ec
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/OWNERS
@@ -0,0 +1,12 @@
+# diagnostics
+per-file ByteArrayRingBuffer*=quiche@google.com
+per-file FrameParser*=quiche@google.com
+
+# random bits
+per-file StringUtil*=quiche@google.com
+per-file WifiPermissionsUtil*=sohanirao@google.com
+per-file WifiPermissionsWrapper*=sohanirao@google.com
+
+# tracing (app/framework interaction)
+per-file WifiAsyncChannel*=sohanirao@google.com
+per-file WifiHandler*=sohanirao@google.com
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 c5ec92a..0000000
--- a/service/java/com/android/server/wifi/util/ScanDetailUtil.java
+++ /dev/null
@@ -1,48 +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);
-    }
-
-    /**
-     * Helper method to quote the SSID in Scan result to use for comparing/filling SSID stored in
-     * WifiConfiguration object.
-     */
-    public static String createQuotedSSID(String ssid) {
-        return "\"" + ssid + "\"";
-    }
-}
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..0e08701
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/ScanResultUtil.java
@@ -0,0 +1,150 @@
+/*
+ * 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 android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.WifiConfigurationUtil;
+import com.android.server.wifi.hotspot2.NetworkDetail;
+
+/**
+ * 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);
+    }
+
+    /**
+     * 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 quote the SSID in Scan result to use for comparing/filling SSID stored in
+     * WifiConfiguration object.
+     */
+    @VisibleForTesting
+    public static String createQuotedSSID(String ssid) {
+        return "\"" + ssid + "\"";
+    }
+
+    /**
+     * Checks if the provided |scanResult| match with the provided |config|. Essentially checks
+     * if the network config and scan result have the same SSID and encryption type.
+     */
+    public static boolean doesScanResultMatchWithNetwork(
+            ScanResult scanResult, WifiConfiguration config) {
+        // Add the double quotes to the scan result SSID for comparison with the network configs.
+        String configSSID = createQuotedSSID(scanResult.SSID);
+        if (TextUtils.equals(config.SSID, configSSID)) {
+            if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
+                    && WifiConfigurationUtil.isConfigForPskNetwork(config)) {
+                return true;
+            }
+            if (ScanResultUtil.isScanResultForEapNetwork(scanResult)
+                    && WifiConfigurationUtil.isConfigForEapNetwork(config)) {
+                return true;
+            }
+            if (ScanResultUtil.isScanResultForWepNetwork(scanResult)
+                    && WifiConfigurationUtil.isConfigForWepNetwork(config)) {
+                return true;
+            }
+            if (ScanResultUtil.isScanResultForOpenNetwork(scanResult)
+                    && WifiConfigurationUtil.isConfigForOpenNetwork(config)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Creates a network configuration object using the provided |scanResult|.
+     * This is used to create ephemeral network configurations.
+     */
+    public static WifiConfiguration createNetworkFromScanResult(ScanResult scanResult) {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = createQuotedSSID(scanResult.SSID);
+        setAllowedKeyManagementFromScanResult(scanResult, config);
+        return config;
+    }
+
+    /**
+     * Sets the {@link WifiConfiguration#allowedKeyManagement} field on the given
+     * {@link WifiConfiguration} based on its corresponding {@link ScanResult}.
+     */
+    public static void setAllowedKeyManagementFromScanResult(ScanResult scanResult,
+            WifiConfiguration config) {
+        if (isScanResultForPskNetwork(scanResult)) {
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        } else if (isScanResultForEapNetwork(scanResult)) {
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        } else if (isScanResultForWepNetwork(scanResult)) {
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
+            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
+        } else {
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/TelephonyUtil.java b/service/java/com/android/server/wifi/util/TelephonyUtil.java
index 3d7ce45..5c489b8 100644
--- a/service/java/com/android/server/wifi/util/TelephonyUtil.java
+++ b/service/java/com/android/server/wifi/util/TelephonyUtil.java
@@ -16,33 +16,40 @@
 
 package com.android.server.wifi.util;
 
-import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.telephony.TelephonyManager;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.server.wifi.WifiNative;
 
 /**
  * Utilities for the Wifi Service to interact with telephony.
  */
 public class TelephonyUtil {
+    public static final String TAG = "TelephonyUtil";
 
     /**
-     * Get the identity for the current SIM or null if the sim is not available
+     * Get the identity for the current SIM or null if the SIM is not available
+     *
+     * @param tm TelephonyManager instance
+     * @param config WifiConfiguration that indicates what sort of authentication is necessary
+     * @return String with the identity or none if the SIM is not available or config is invalid
      */
-    public static String getSimIdentity(Context context, int eapMethod) {
-        TelephonyManager tm = TelephonyManager.from(context);
-        if (tm != null) {
-            String imsi = tm.getSubscriberId();
-            String mccMnc = "";
-
-            if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) {
-                mccMnc = tm.getSimOperator();
-            }
-
-            return buildIdentity(eapMethod, imsi, mccMnc);
-        } else {
+    public static String getSimIdentity(TelephonyManager tm, WifiConfiguration config) {
+        if (tm == null) {
+            Log.e(TAG, "No valid TelephonyManager");
             return null;
         }
+        String imsi = tm.getSubscriberId();
+        String mccMnc = "";
+
+        if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) {
+            mccMnc = tm.getSimOperator();
+        }
+
+        return buildIdentity(getSimMethodForConfig(config), imsi, mccMnc);
     }
 
     /**
@@ -55,6 +62,7 @@
      */
     private static String buildIdentity(int eapMethod, String imsi, String mccMnc) {
         if (imsi == null || imsi.isEmpty()) {
+            Log.e(TAG, "No IMSI or IMSI is null");
             return null;
         }
 
@@ -65,7 +73,8 @@
             prefix = "0";
         } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME) {
             prefix = "6";
-        } else {  // not a valide EapMethod
+        } else {
+            Log.e(TAG, "Invalid EAP method");
             return null;
         }
 
@@ -88,28 +97,293 @@
     }
 
     /**
-     * Checks if the network is a sim config.
+     * Return the associated SIM method for the configuration.
      *
-     * @param config Config corresponding to the network.
-     * @return true if it is a sim config, false otherwise.
+     * @param config WifiConfiguration corresponding to the network.
+     * @return the outer EAP method associated with this SIM configuration.
      */
-    public static boolean isSimConfig(WifiConfiguration config) {
+    private static int getSimMethodForConfig(WifiConfiguration config) {
         if (config == null || config.enterpriseConfig == null) {
-            return false;
+            return WifiEnterpriseConfig.Eap.NONE;
+        }
+        int eapMethod = config.enterpriseConfig.getEapMethod();
+        if (eapMethod == WifiEnterpriseConfig.Eap.PEAP) {
+            // Translate known inner eap methods into an equivalent outer eap method.
+            switch (config.enterpriseConfig.getPhase2Method()) {
+                case WifiEnterpriseConfig.Phase2.SIM:
+                    eapMethod = WifiEnterpriseConfig.Eap.SIM;
+                    break;
+                case WifiEnterpriseConfig.Phase2.AKA:
+                    eapMethod = WifiEnterpriseConfig.Eap.AKA;
+                    break;
+                case WifiEnterpriseConfig.Phase2.AKA_PRIME:
+                    eapMethod = WifiEnterpriseConfig.Eap.AKA_PRIME;
+                    break;
+            }
         }
 
-        return isSimEapMethod(config.enterpriseConfig.getEapMethod());
+        return isSimEapMethod(eapMethod) ? eapMethod : WifiEnterpriseConfig.Eap.NONE;
     }
 
     /**
-     * Checks if the network is a sim config.
+     * Checks if the network is a SIM config.
      *
-     * @param method
-     * @return true if it is a sim config, false otherwise.
+     * @param config Config corresponding to the network.
+     * @return true if it is a SIM config, false otherwise.
+     */
+    public static boolean isSimConfig(WifiConfiguration config) {
+        return getSimMethodForConfig(config) != WifiEnterpriseConfig.Eap.NONE;
+    }
+
+    /**
+     * Checks if the EAP outer method is SIM related.
+     *
+     * @param eapMethod WifiEnterpriseConfig Eap method.
+     * @return true if this EAP outer method is SIM-related, false otherwise.
      */
     public static boolean isSimEapMethod(int eapMethod) {
         return eapMethod == WifiEnterpriseConfig.Eap.SIM
                 || eapMethod == WifiEnterpriseConfig.Eap.AKA
                 || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
     }
+
+    // TODO replace some of this code with Byte.parseByte
+    private static int parseHex(char ch) {
+        if ('0' <= ch && ch <= '9') {
+            return ch - '0';
+        } else if ('a' <= ch && ch <= 'f') {
+            return ch - 'a' + 10;
+        } else if ('A' <= ch && ch <= 'F') {
+            return ch - 'A' + 10;
+        } else {
+            throw new NumberFormatException("" + ch + " is not a valid hex digit");
+        }
+    }
+
+    private static byte[] parseHex(String hex) {
+        /* This only works for good input; don't throw bad data at it */
+        if (hex == null) {
+            return new byte[0];
+        }
+
+        if (hex.length() % 2 != 0) {
+            throw new NumberFormatException(hex + " is not a valid hex string");
+        }
+
+        byte[] result = new byte[(hex.length()) / 2 + 1];
+        result[0] = (byte) ((hex.length()) / 2);
+        for (int i = 0, j = 1; i < hex.length(); i += 2, j++) {
+            int val = parseHex(hex.charAt(i)) * 16 + parseHex(hex.charAt(i + 1));
+            byte b = (byte) (val & 0xFF);
+            result[j] = b;
+        }
+
+        return result;
+    }
+
+    private static String makeHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02x", b));
+        }
+        return sb.toString();
+    }
+
+    private static String makeHex(byte[] bytes, int from, int len) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < len; i++) {
+            sb.append(String.format("%02x", bytes[from + i]));
+        }
+        return sb.toString();
+    }
+
+    private static byte[] concatHex(byte[] array1, byte[] array2) {
+
+        int len = array1.length + array2.length;
+
+        byte[] result = new byte[len];
+
+        int index = 0;
+        if (array1.length != 0) {
+            for (byte b : array1) {
+                result[index] = b;
+                index++;
+            }
+        }
+
+        if (array2.length != 0) {
+            for (byte b : array2) {
+                result[index] = b;
+                index++;
+            }
+        }
+
+        return result;
+    }
+
+    public static String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) {
+        if (tm == null) {
+            Log.e(TAG, "No valid TelephonyManager");
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (String challenge : requestData) {
+            if (challenge == null || challenge.isEmpty()) {
+                continue;
+            }
+            Log.d(TAG, "RAND = " + challenge);
+
+            byte[] rand = null;
+            try {
+                rand = parseHex(challenge);
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "malformed challenge");
+                continue;
+            }
+
+            String base64Challenge = Base64.encodeToString(rand, Base64.NO_WRAP);
+
+            // Try USIM first for authentication.
+            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(TelephonyManager.APPTYPE_SIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
+            }
+            Log.v(TAG, "Raw Response - " + tmResponse);
+
+            if (tmResponse == null || tmResponse.length() <= 4) {
+                Log.e(TAG, "bad response - " + tmResponse);
+                return null;
+            }
+
+            byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
+            Log.v(TAG, "Hex Response -" + makeHex(result));
+            int sresLen = result[0];
+            if (sresLen >= result.length) {
+                Log.e(TAG, "malfomed response - " + tmResponse);
+                return null;
+            }
+            String sres = makeHex(result, 1, sresLen);
+            int kcOffset = 1 + sresLen;
+            if (kcOffset >= result.length) {
+                Log.e(TAG, "malfomed response - " + tmResponse);
+                return null;
+            }
+            int kcLen = result[kcOffset];
+            if (kcOffset + kcLen > result.length) {
+                Log.e(TAG, "malfomed response - " + tmResponse);
+                return null;
+            }
+            String kc = makeHex(result, 1 + kcOffset, kcLen);
+            sb.append(":" + kc + ":" + sres);
+            Log.v(TAG, "kc:" + kc + " sres:" + sres);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Data supplied when making a SIM Auth Request
+     */
+    public static class SimAuthRequestData {
+        public SimAuthRequestData() {}
+        public SimAuthRequestData(int networkId, int protocol, String ssid, String[] data) {
+            this.networkId = networkId;
+            this.protocol = protocol;
+            this.ssid = ssid;
+            this.data = data;
+        }
+
+        public int networkId;
+        public int protocol;
+        public String ssid;
+        // EAP-SIM: data[] contains the 3 rand, one for each of the 3 challenges
+        // EAP-AKA/AKA': data[] contains rand & authn couple for the single challenge
+        public String[] data;
+    }
+
+    /**
+     * The response to a SIM Auth request if successful
+     */
+    public static class SimAuthResponseData {
+        public SimAuthResponseData(String type, String response) {
+            this.type = type;
+            this.response = response;
+        }
+
+        public String type;
+        public String response;
+    }
+
+    public static SimAuthResponseData get3GAuthResponse(SimAuthRequestData requestData,
+            TelephonyManager tm) {
+        StringBuilder sb = new StringBuilder();
+        byte[] rand = null;
+        byte[] authn = null;
+        String resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTH;
+
+        if (requestData.data.length == 2) {
+            try {
+                rand = parseHex(requestData.data[0]);
+                authn = parseHex(requestData.data[1]);
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "malformed challenge");
+            }
+        } else {
+            Log.e(TAG, "malformed challenge");
+        }
+
+        String tmResponse = "";
+        if (rand != null && authn != null) {
+            String base64Challenge = Base64.encodeToString(concatHex(rand, authn), Base64.NO_WRAP);
+            if (tm != null) {
+                tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge);
+                Log.v(TAG, "Raw Response - " + tmResponse);
+            } else {
+                Log.e(TAG, "No valid TelephonyManager");
+            }
+        }
+
+        boolean goodReponse = false;
+        if (tmResponse != null && tmResponse.length() > 4) {
+            byte[] result = Base64.decode(tmResponse, Base64.DEFAULT);
+            Log.e(TAG, "Hex Response - " + makeHex(result));
+            byte tag = result[0];
+            if (tag == (byte) 0xdb) {
+                Log.v(TAG, "successful 3G authentication ");
+                int resLen = result[1];
+                String res = makeHex(result, 2, resLen);
+                int ckLen = result[resLen + 2];
+                String ck = makeHex(result, resLen + 3, ckLen);
+                int ikLen = result[resLen + ckLen + 3];
+                String ik = makeHex(result, resLen + ckLen + 4, ikLen);
+                sb.append(":" + ik + ":" + ck + ":" + res);
+                Log.v(TAG, "ik:" + ik + "ck:" + ck + " res:" + res);
+                goodReponse = true;
+            } else if (tag == (byte) 0xdc) {
+                Log.e(TAG, "synchronisation failure");
+                int autsLen = result[1];
+                String auts = makeHex(result, 2, autsLen);
+                resType = WifiNative.SIM_AUTH_RESP_TYPE_UMTS_AUTS;
+                sb.append(":" + auts);
+                Log.v(TAG, "auts:" + auts);
+                goodReponse = true;
+            } else {
+                Log.e(TAG, "bad response - unknown tag = " + tag);
+            }
+        } else {
+            Log.e(TAG, "bad response - " + tmResponse);
+        }
+
+        if (goodReponse) {
+            String response = sb.toString();
+            Log.v(TAG, "Supplicant Response -" + response);
+            return new SimAuthResponseData(resType, response);
+        } else {
+            return null;
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/WifiAsyncChannel.java b/service/java/com/android/server/wifi/util/WifiAsyncChannel.java
new file mode 100644
index 0000000..08025e3
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/WifiAsyncChannel.java
@@ -0,0 +1,104 @@
+/*
+ * 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.annotation.NonNull;
+import android.os.Message;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiLog;
+
+/**
+ * This class subclasses AsyncChannel and adds logging
+ * to the sendMessage() API
+ */
+public class WifiAsyncChannel extends AsyncChannel {
+    private static final String LOG_TAG = "WifiAsyncChannel";
+    private WifiLog mLog;
+    private String mTag;
+    /**
+     * AsyncChannelWithLogging constructor
+     */
+    public WifiAsyncChannel(String serviceTag) {
+        mTag = LOG_TAG + "." + serviceTag;
+    }
+
+    @NonNull
+    private WifiLog getOrInitLog() {
+        // Lazy initization of mLog
+        if (mLog == null) {
+            mLog = WifiInjector.getInstance().makeLog(mTag);
+        }
+        return mLog;
+    }
+
+    /**
+     * Send a message to the destination handler.
+     *
+     * @param msg
+     */
+    @Override
+    public void sendMessage(Message msg) {
+        getOrInitLog().trace("sendMessage message=%")
+            .c(msg.what)
+            .flush();
+        super.sendMessage(msg);
+    }
+
+    /**
+     * Reply to srcMsg
+     *
+     * @param srcMsg
+     * @param dstMsg
+     */
+    @Override
+    public void replyToMessage(Message srcMsg, Message dstMsg) {
+        getOrInitLog()
+                .trace("replyToMessage recvdMessage=% sendingUid=% sentMessage=%")
+                .c(srcMsg.what)
+                .c(srcMsg.sendingUid)
+                .c(dstMsg.what)
+                .flush();
+        super.replyToMessage(srcMsg, dstMsg);
+    }
+
+    /**
+     * Send the Message synchronously.
+     *
+     * @param msg to send
+     * @return reply message or null if an error.
+     */
+    @Override
+    public Message sendMessageSynchronously(Message msg) {
+        getOrInitLog().trace("sendMessageSynchronously.send message=%")
+            .c(msg.what)
+            .flush();
+        Message replyMessage = super.sendMessageSynchronously(msg);
+        getOrInitLog().trace("sendMessageSynchronously.recv message=% sendingUid=%")
+            .c(replyMessage.what)
+            .c(replyMessage.sendingUid)
+            .flush();
+        return replyMessage;
+    }
+
+    @VisibleForTesting
+    public void setWifiLog(WifiLog log) {
+        mLog = log;
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/WifiHandler.java b/service/java/com/android/server/wifi/util/WifiHandler.java
new file mode 100644
index 0000000..8750601
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/WifiHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiLog;
+
+/**
+ * This class subclasses Handler to log incoming messages
+ */
+public class WifiHandler extends Handler {
+    private static final String LOG_TAG = "WifiHandler";
+    private WifiLog mLog;
+    private String mTag;
+
+    public WifiHandler(String tag, Looper looper) {
+        super(looper);
+        mTag = LOG_TAG + "." + tag;
+    }
+
+    @NonNull
+    private WifiLog getOrInitLog() {
+        // Lazy initialization of mLog
+        if (mLog == null) {
+            mLog = WifiInjector.getInstance().makeLog(mTag);
+        }
+        return mLog;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        getOrInitLog().trace("Received message=%d sendingUid=%")
+                .c(msg.what)
+                .c(msg.sendingUid)
+                .flush();
+    }
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public void setWifiLog(WifiLog wifiLog) {
+        // TODO WifiInjector should be passed as a variable in the constructor
+        // b/33308811 tracks removing lazy initializations of mLog
+        mLog = wifiLog;
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
new file mode 100644
index 0000000..f945437
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -0,0 +1,242 @@
+/*
+ * 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.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkScoreManager;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiLog;
+import com.android.server.wifi.WifiSettingsStore;
+
+import java.util.List;
+
+/**
+ * A wifi permissions utility assessing permissions
+ * for getting scan results by a package.
+ */
+public class WifiPermissionsUtil {
+    private static final String TAG = "WifiPermissionsUtil";
+    private final WifiPermissionsWrapper mWifiPermissionsWrapper;
+    private final Context mContext;
+    private final AppOpsManager mAppOps;
+    private final UserManager mUserManager;
+    private final WifiSettingsStore mSettingsStore;
+    private final NetworkScoreManager mNetworkScoreManager;
+    private WifiLog mLog;
+
+    public WifiPermissionsUtil(WifiPermissionsWrapper wifiPermissionsWrapper,
+            Context context, WifiSettingsStore settingsStore, UserManager userManager,
+            NetworkScoreManager networkScoreManager, WifiInjector wifiInjector) {
+        mWifiPermissionsWrapper = wifiPermissionsWrapper;
+        mContext = context;
+        mUserManager = userManager;
+        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mSettingsStore = settingsStore;
+        mLog = wifiInjector.makeLog(TAG);
+        mNetworkScoreManager = networkScoreManager;
+    }
+
+    /**
+     * 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.
+     */
+    public boolean checkConfigOverridePermission(int uid) {
+        try {
+            int permission = mWifiPermissionsWrapper.getOverrideWifiConfigPermission(uid);
+            return (permission == PackageManager.PERMISSION_GRANTED);
+        } catch (RemoteException e) {
+            mLog.err("Error checking for permission: %").r(e.getMessage()).flush();
+            return false;
+        }
+    }
+
+    /**
+     * Check and enforce tether change permission.
+     *
+     * @param context Context object of the caller.
+     */
+    public void enforceTetherChangePermission(Context context) {
+        ConnectivityManager.enforceTetherChangePermission(context);
+    }
+
+    /**
+     * Check and enforce Location permission.
+     *
+     * @param pkgName PackageName of the application requesting access
+     * @param uid The uid of the package
+     */
+    public void enforceLocationPermission(String pkgName, int uid) {
+        if (!checkCallersLocationPermission(pkgName, uid)) {
+            throw new SecurityException("UID " + uid + " does not have Location permission");
+        }
+    }
+
+    /**
+     * API to determine if the caller has permissions to get
+     * scan results.
+     * @param pkgName Packagename of the application requesting access
+     * @param uid The uid of the package
+     * @param minVersion Minimum app API Version number to enforce location permission
+     * @return boolean true or false if permissions is granted
+     */
+    public boolean canAccessScanResults(String pkgName, int uid,
+                int minVersion) throws SecurityException {
+        mAppOps.checkPackage(uid, pkgName);
+        // Check if the calling Uid has CAN_READ_PEER_MAC_ADDRESS
+        // permission or is an Active Nw scorer.
+        boolean canCallingUidAccessLocation = checkCallerHasPeersMacAddressPermission(uid)
+                || isCallerActiveNwScorer(uid);
+        // LocationAccess by App: For AppVersion older than minVersion,
+        // it is sufficient to check if the App is foreground.
+        // Otherwise, Location Mode must be enabled and caller must have
+        // Coarse Location permission to have access to location information.
+        boolean canAppPackageUseLocation = isLegacyForeground(pkgName, minVersion)
+                || (isLocationModeEnabled(pkgName)
+                        && checkCallersLocationPermission(pkgName, uid));
+        // If neither caller or app has location access, there is no need to check
+        // any other permissions. Deny access to scan results.
+        if (!canCallingUidAccessLocation && !canAppPackageUseLocation) {
+            mLog.tC("Denied: no location permission");
+            return false;
+        }
+        // Check if Wifi Scan request is an operation allowed for this App.
+        if (!isScanAllowedbyApps(pkgName, uid)) {
+            mLog.tC("Denied: app wifi scan not allowed");
+            return false;
+        }
+        // If the User or profile is current, permission is granted
+        // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
+        if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+            mLog.tC("Denied: Profile not permitted");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if the caller holds PEERS_MAC_ADDRESS permission.
+     */
+    private boolean checkCallerHasPeersMacAddressPermission(int uid) {
+        return mWifiPermissionsWrapper.getUidPermission(
+                android.Manifest.permission.PEERS_MAC_ADDRESS, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * Returns true if the caller is an Active Network Scorer.
+     */
+    private boolean isCallerActiveNwScorer(int uid) {
+        return mNetworkScoreManager.isCallerActiveScorer(uid);
+    }
+
+    /**
+     * Returns true if Wifi scan operation is allowed for this caller
+     * and package.
+     */
+    private boolean isScanAllowedbyApps(String pkgName, int uid) {
+        return checkAppOpAllowed(AppOpsManager.OP_WIFI_SCAN, pkgName, uid);
+    }
+
+    /**
+     * Returns true if the caller holds INTERACT_ACROSS_USERS_FULL.
+     */
+    private boolean checkInteractAcrossUsersFull(int uid) {
+        return mWifiPermissionsWrapper.getUidPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * Returns true if the calling user is the current one or a profile of the
+     * current user.
+     */
+    private boolean isCurrentProfile(int uid) {
+        int currentUser = mWifiPermissionsWrapper.getCurrentUser();
+        int callingUserId = mWifiPermissionsWrapper.getCallingUserId(uid);
+        if (callingUserId == currentUser) {
+            return true;
+        } else {
+            List<UserInfo> userProfiles = mUserManager.getProfiles(currentUser);
+            for (UserInfo user: userProfiles) {
+                if (user.id == callingUserId) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the App version is older than minVersion.
+     */
+    private boolean isLegacyVersion(String pkgName, int minVersion) {
+        try {
+            if (mContext.getPackageManager().getApplicationInfo(pkgName, 0)
+                    .targetSdkVersion < minVersion) {
+                return true;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // In case of exception, assume known app (more strict checking)
+            // Note: This case will never happen since checkPackage is
+            // called to verify valididity before checking App's version.
+        }
+        return false;
+    }
+
+    private boolean checkAppOpAllowed(int op, String pkgName, int uid) {
+        return mAppOps.noteOp(op, uid, pkgName) == AppOpsManager.MODE_ALLOWED;
+    }
+
+    private boolean isLegacyForeground(String pkgName, int version) {
+        return isLegacyVersion(pkgName, version) && isForegroundApp(pkgName);
+    }
+
+    private boolean isForegroundApp(String pkgName) {
+        return pkgName.equals(mWifiPermissionsWrapper.getTopPkgName());
+    }
+
+    /**
+     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION
+     * and a corresponding app op is allowed for this package and uid.
+     */
+    private boolean checkCallersLocationPermission(String pkgName, int uid) {
+        // Coarse Permission implies Fine permission
+        if ((mWifiPermissionsWrapper.getUidPermission(
+                Manifest.permission.ACCESS_COARSE_LOCATION, uid)
+                == PackageManager.PERMISSION_GRANTED)
+                && checkAppOpAllowed(AppOpsManager.OP_COARSE_LOCATION, pkgName, uid)) {
+            return true;
+        }
+        return false;
+    }
+    private boolean isLocationModeEnabled(String pkgName) {
+        // Location mode check on applications that are later than version.
+        return (mSettingsStore.getLocationModeSetting(mContext)
+                 != Settings.Secure.LOCATION_MODE_OFF);
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
new file mode 100644
index 0000000..6ca2f02
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
@@ -0,0 +1,98 @@
+/*
+ * 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.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.server.LocalServices;
+
+import java.util.List;
+
+/**
+ * A wifi permissions dependency class to wrap around external
+ * calls to static methods that enable testing.
+ */
+public class WifiPermissionsWrapper {
+    private static final String TAG = "WifiPermissionsWrapper";
+    private final Context mContext;
+
+    public WifiPermissionsWrapper(Context context) {
+        mContext = context;
+    }
+
+    public int getCurrentUser() {
+        return ActivityManager.getCurrentUser();
+    }
+
+    /**
+     * Returns the user ID corresponding to the UID
+     * @param uid Calling Uid
+     * @return userid Corresponding user id
+     */
+    public int getCallingUserId(int uid) {
+        return UserHandle.getUserId(uid);
+    }
+
+    /**
+     * Get the PackageName of the top running task
+     * @return String corresponding to the package
+     */
+    public String getTopPkgName() {
+        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        String topTaskPkg = " ";
+        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
+        if (!tasks.isEmpty()) {
+            return tasks.get(0).topActivity.getPackageName();
+        }
+        return topTaskPkg;
+    }
+
+    /**
+     * API is wrap around ActivityManager class to
+     * get location permissions for a certain UID
+     * @param: Manifest permission string
+     * @param: Uid to get permission for
+     * @return: Permissions setting
+     */
+    public int getUidPermission(String permissionType, int uid) {
+        return ActivityManager.checkUidPermission(permissionType, uid);
+    }
+
+    /**
+     * Gets the local service {link@ DevicePolicyManagerInternal}, can be null
+     */
+    public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+        return LocalServices.getService(DevicePolicyManagerInternal.class);
+    }
+
+    /**
+     * Determines if the caller has the override wifi config permission.
+     *
+     * @param uid to check the permission for
+     * @return int representation of success or denied
+     * @throws RemoteException
+     */
+    public int getOverrideWifiConfigPermission(int uid) throws RemoteException {
+        return AppGlobals.getPackageManager().checkUidPermission(
+                android.Manifest.permission.OVERRIDE_WIFI_CONFIG, uid);
+    }
+}
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..853136b
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/XmlUtil.java
@@ -0,0 +1,1105 @@
+/*
+ * 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.Arrays;
+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>
+ *
+ * Note: These utility methods are meant to be used for:
+ * 1. Backup/restore wifi network data to/from cloud.
+ * 2. Persisting wifi network data to/from disk.
+ */
+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_REQUIRE_PMF = "RequirePMF";
+        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_ALLOWED_GROUP_CIPHERS = "AllowedGroupCiphers";
+        public static final String XML_TAG_ALLOWED_PAIRWISE_CIPHERS = "AllowedPairwiseCiphers";
+        public static final String XML_TAG_SHARED = "Shared";
+        public static final String XML_TAG_STATUS = "Status";
+        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";
+        public static final String XML_TAG_IS_LEGACY_PASSPOINT_CONFIG = "IsLegacyPasspointConfig";
+        public static final String XML_TAG_ROAMING_CONSORTIUM_OIS = "RoamingConsortiumOIs";
+
+        /**
+         * 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_REQUIRE_PMF, configuration.requirePMF);
+            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_ALLOWED_GROUP_CIPHERS,
+                    configuration.allowedGroupCiphers.toByteArray());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_ALLOWED_PAIRWISE_CIPHERS,
+                    configuration.allowedPairwiseCiphers.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_STATUS, configuration.status);
+            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);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_IS_LEGACY_PASSPOINT_CONFIG,
+                    configuration.isLegacyPasspointConfig);
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_ROAMING_CONSORTIUM_OIS, configuration.roamingConsortiumIds);
+        }
+
+        /**
+         * 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_REQUIRE_PMF:
+                        configuration.requirePMF = (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_ALLOWED_GROUP_CIPHERS:
+                        byte[] allowedGroupCiphers = (byte[]) value;
+                        configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers);
+                        break;
+                    case XML_TAG_ALLOWED_PAIRWISE_CIPHERS:
+                        byte[] allowedPairwiseCiphers = (byte[]) value;
+                        configuration.allowedPairwiseCiphers =
+                                BitSet.valueOf(allowedPairwiseCiphers);
+                        break;
+                    case XML_TAG_SHARED:
+                        configuration.shared = (boolean) value;
+                        break;
+                    case XML_TAG_STATUS:
+                        int status = (int) value;
+                        // Any network which was CURRENT before reboot needs
+                        // to be restored to ENABLED.
+                        if (status == WifiConfiguration.Status.CURRENT) {
+                            status = WifiConfiguration.Status.ENABLED;
+                        }
+                        configuration.status = status;
+                        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;
+                    case XML_TAG_IS_LEGACY_PASSPOINT_CONFIG:
+                        configuration.isLegacyPasspointConfig = (boolean) value;
+                        break;
+                    case XML_TAG_ROAMING_CONSORTIUM_OIS:
+                        configuration.roamingConsortiumIds = (long[]) 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 selectionStatus NetworkSelectionStatus object to be serialized.
+         */
+        public static void writeToXml(XmlSerializer out, NetworkSelectionStatus selectionStatus)
+                throws XmlPullParserException, IOException {
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_SELECTION_STATUS, selectionStatus.getNetworkStatusString());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_DISABLE_REASON, selectionStatus.getNetworkDisableReasonString());
+            XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE, selectionStatus.getConnectChoice());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_CONNECT_CHOICE_TIMESTAMP,
+                    selectionStatus.getConnectChoiceTimestamp());
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_HAS_EVER_CONNECTED, selectionStatus.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 selectionStatus = new NetworkSelectionStatus();
+            String statusString = "";
+            String disableReasonString = "";
+
+            // 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:
+                        statusString = (String) value;
+                        break;
+                    case XML_TAG_DISABLE_REASON:
+                        disableReasonString = (String) value;
+                        break;
+                    case XML_TAG_CONNECT_CHOICE:
+                        selectionStatus.setConnectChoice((String) value);
+                        break;
+                    case XML_TAG_CONNECT_CHOICE_TIMESTAMP:
+                        selectionStatus.setConnectChoiceTimestamp((long) value);
+                        break;
+                    case XML_TAG_HAS_EVER_CONNECTED:
+                        selectionStatus.setHasEverConnected((boolean) value);
+                        break;
+                    default:
+                        throw new XmlPullParserException(
+                                "Unknown value name found: " + valueName[0]);
+                }
+            }
+            // Now figure out the network selection status codes from |selectionStatusString| &
+            // |disableReasonString|.
+            int status =
+                    Arrays.asList(NetworkSelectionStatus.QUALITY_NETWORK_SELECTION_STATUS)
+                            .indexOf(statusString);
+            int disableReason =
+                    Arrays.asList(NetworkSelectionStatus.QUALITY_NETWORK_SELECTION_DISABLE_REASON)
+                            .indexOf(disableReasonString);
+
+            // If either of the above codes are invalid or if the network was temporarily disabled
+            // (blacklisted), restore the status as enabled. We don't want to persist blacklists
+            // across reboots.
+            if (status == -1 || disableReason == -1 ||
+                    status == NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED) {
+                status = NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
+                disableReason = NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
+            }
+            selectionStatus.setNetworkSelectionStatus(status);
+            selectionStatus.setNetworkSelectionDisableReason(disableReason);
+            return selectionStatus;
+        }
+    }
+
+    /**
+     * 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";
+        public static final String XML_TAG_PLMN = "PLMN";
+        public static final String XML_TAG_REALM = "Realm";
+
+        /**
+         * 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());
+            XmlUtil.writeNextValue(out, XML_TAG_PLMN, enterpriseConfig.getPlmn());
+            XmlUtil.writeNextValue(out, XML_TAG_REALM, enterpriseConfig.getRealm());
+        }
+
+        /**
+         * 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;
+                    case XML_TAG_PLMN:
+                        enterpriseConfig.setPlmn((String) value);
+                        break;
+                    case XML_TAG_REALM:
+                        enterpriseConfig.setRealm((String) value);
+                        break;
+                    default:
+                        throw new XmlPullParserException(
+                                "Unknown value name found: " + valueName[0]);
+                }
+            }
+            return enterpriseConfig;
+        }
+    }
+}
+
diff --git a/service/java/com/android/server/wifi/wificond/ChannelSettings.java b/service/java/com/android/server/wifi/wificond/ChannelSettings.java
new file mode 100644
index 0000000..7b20983
--- /dev/null
+++ b/service/java/com/android/server/wifi/wificond/ChannelSettings.java
@@ -0,0 +1,95 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * ChannelSettings for wificond
+ *
+ * @hide
+ */
+public class ChannelSettings implements Parcelable {
+    private static final String TAG = "ChannelSettings";
+
+    public int frequency;
+
+    /** public constructor */
+    public ChannelSettings() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof ChannelSettings)) {
+            return false;
+        }
+        ChannelSettings channel = (ChannelSettings) rhs;
+        if (channel == null) {
+            return false;
+        }
+        return frequency == channel.frequency;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(frequency);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     * */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(frequency);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<ChannelSettings> CREATOR =
+            new Parcelable.Creator<ChannelSettings>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public ChannelSettings createFromParcel(Parcel in) {
+            ChannelSettings result = new ChannelSettings();
+            result.frequency = in.readInt();
+            if (in.dataAvail() != 0) {
+                Log.e(TAG, "Found trailing data after parcel parsing.");
+            }
+
+            return result;
+        }
+
+        @Override
+        public ChannelSettings[] newArray(int size) {
+            return new ChannelSettings[size];
+        }
+    };
+}
diff --git a/service/java/com/android/server/wifi/wificond/HiddenNetwork.java b/service/java/com/android/server/wifi/wificond/HiddenNetwork.java
new file mode 100644
index 0000000..def3b43
--- /dev/null
+++ b/service/java/com/android/server/wifi/wificond/HiddenNetwork.java
@@ -0,0 +1,92 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * HiddenNetwork for wificond
+ *
+ * @hide
+ */
+public class HiddenNetwork implements Parcelable {
+    private static final String TAG = "HiddenNetwork";
+
+    public byte[] ssid;
+
+    /** public constructor */
+    public HiddenNetwork() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof HiddenNetwork)) {
+            return false;
+        }
+        HiddenNetwork network = (HiddenNetwork) rhs;
+        return java.util.Arrays.equals(ssid, network.ssid);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(ssid);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(ssid);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<HiddenNetwork> CREATOR =
+            new Parcelable.Creator<HiddenNetwork>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public HiddenNetwork createFromParcel(Parcel in) {
+            HiddenNetwork result = new HiddenNetwork();
+            result.ssid = in.createByteArray();
+            if (in.dataAvail() != 0) {
+                Log.e(TAG, "Found trailing data after parcel parsing.");
+            }
+
+            return result;
+        }
+
+        @Override
+        public HiddenNetwork[] newArray(int size) {
+            return new HiddenNetwork[size];
+        }
+    };
+}
diff --git a/service/java/com/android/server/wifi/wificond/NativeScanResult.java b/service/java/com/android/server/wifi/wificond/NativeScanResult.java
new file mode 100644
index 0000000..d41237a
--- /dev/null
+++ b/service/java/com/android/server/wifi/wificond/NativeScanResult.java
@@ -0,0 +1,109 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.BitSet;
+
+/**
+ * ScanResult from wificond
+ *
+ * @hide
+ */
+public class NativeScanResult implements Parcelable {
+    private static final int CAPABILITY_SIZE = 16;
+
+    public byte[] ssid;
+    public byte[] bssid;
+    public byte[] infoElement;
+    public int frequency;
+    public int signalMbm;
+    public long tsf;
+    public BitSet capability;
+    public boolean associated;
+
+    /** public constructor */
+    public NativeScanResult() { }
+
+    /** copy constructor */
+    public NativeScanResult(NativeScanResult source) {
+        ssid = source.ssid.clone();
+        bssid = source.bssid.clone();
+        infoElement = source.infoElement.clone();
+        frequency = source.frequency;
+        signalMbm = source.signalMbm;
+        tsf = source.tsf;
+        capability = (BitSet) source.capability.clone();
+        associated = source.associated;
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(ssid);
+        out.writeByteArray(bssid);
+        out.writeByteArray(infoElement);
+        out.writeInt(frequency);
+        out.writeInt(signalMbm);
+        out.writeLong(tsf);
+        int capabilityInt = 0;
+        for (int i = 0; i < CAPABILITY_SIZE; i++) {
+            if (capability.get(i)) {
+                capabilityInt |= 1 << i;
+            }
+        }
+        out.writeInt(capabilityInt);
+        out.writeInt(associated ? 1 : 0);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<NativeScanResult> CREATOR =
+            new Parcelable.Creator<NativeScanResult>() {
+        @Override
+        public NativeScanResult createFromParcel(Parcel in) {
+            NativeScanResult result = new NativeScanResult();
+            result.ssid = in.createByteArray();
+            result.bssid = in.createByteArray();
+            result.infoElement = in.createByteArray();
+            result.frequency = in.readInt();
+            result.signalMbm = in.readInt();
+            result.tsf = in.readLong();
+            int capabilityInt = in.readInt();
+            result.capability = new BitSet(CAPABILITY_SIZE);
+            for (int i = 0; i < CAPABILITY_SIZE; i++) {
+                if ((capabilityInt & (1 << i)) != 0) {
+                    result.capability.set(i);
+                }
+            }
+            result.associated = (in.readInt() != 0);
+            return result;
+        }
+
+        @Override
+        public NativeScanResult[] newArray(int size) {
+            return new NativeScanResult[size];
+        }
+    };
+}
diff --git a/service/java/com/android/server/wifi/wificond/OWNERS b/service/java/com/android/server/wifi/wificond/OWNERS
new file mode 100644
index 0000000..dab2f95
--- /dev/null
+++ b/service/java/com/android/server/wifi/wificond/OWNERS
@@ -0,0 +1 @@
+nywang@google.com
diff --git a/service/java/com/android/server/wifi/wificond/PnoNetwork.java b/service/java/com/android/server/wifi/wificond/PnoNetwork.java
new file mode 100644
index 0000000..e05e993
--- /dev/null
+++ b/service/java/com/android/server/wifi/wificond/PnoNetwork.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * PnoNetwork for wificond
+ *
+ * @hide
+ */
+public class PnoNetwork implements Parcelable {
+
+    public boolean isHidden;
+    public byte[] ssid;
+
+    /** public constructor */
+    public PnoNetwork() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof PnoNetwork)) {
+            return false;
+        }
+        PnoNetwork network = (PnoNetwork) rhs;
+        return java.util.Arrays.equals(ssid, network.ssid)
+                && isHidden == network.isHidden;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(isHidden, ssid);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(isHidden ? 1 : 0);
+        out.writeByteArray(ssid);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<PnoNetwork> CREATOR =
+            new Parcelable.Creator<PnoNetwork>() {
+        @Override
+        public PnoNetwork createFromParcel(Parcel in) {
+            PnoNetwork result = new PnoNetwork();
+            result.isHidden = in.readInt() != 0 ? true : false;
+            result.ssid = in.createByteArray();
+            return result;
+        }
+
+        @Override
+        public PnoNetwork[] newArray(int size) {
+            return new PnoNetwork[size];
+        }
+    };
+}
diff --git a/service/java/com/android/server/wifi/wificond/PnoSettings.java b/service/java/com/android/server/wifi/wificond/PnoSettings.java
new file mode 100644
index 0000000..51a7cc1
--- /dev/null
+++ b/service/java/com/android/server/wifi/wificond/PnoSettings.java
@@ -0,0 +1,101 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * PnoSettings for wificond
+ *
+ * @hide
+ */
+public class PnoSettings implements Parcelable {
+    public int intervalMs;
+    public int min2gRssi;
+    public int min5gRssi;
+    public ArrayList<PnoNetwork> pnoNetworks;
+
+    /** public constructor */
+    public PnoSettings() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof PnoSettings)) {
+            return false;
+        }
+        PnoSettings settings = (PnoSettings) rhs;
+        if (settings == null) {
+            return false;
+        }
+        return intervalMs == settings.intervalMs
+                && min2gRssi == settings.min2gRssi
+                && min5gRssi == settings.min5gRssi
+                && pnoNetworks.equals(settings.pnoNetworks);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(intervalMs, min2gRssi, min5gRssi, pnoNetworks);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     * */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(intervalMs);
+        out.writeInt(min2gRssi);
+        out.writeInt(min5gRssi);
+        out.writeTypedList(pnoNetworks);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<PnoSettings> CREATOR =
+            new Parcelable.Creator<PnoSettings>() {
+        @Override
+        public PnoSettings createFromParcel(Parcel in) {
+            PnoSettings result = new PnoSettings();
+            result.intervalMs = in.readInt();
+            result.min2gRssi = in.readInt();
+            result.min5gRssi = in.readInt();
+
+            result.pnoNetworks = new ArrayList<PnoNetwork>();
+            in.readTypedList(result.pnoNetworks, PnoNetwork.CREATOR);
+
+            return result;
+        }
+
+        @Override
+        public PnoSettings[] newArray(int size) {
+            return new PnoSettings[size];
+        }
+    };
+}
diff --git a/service/java/com/android/server/wifi/wificond/SingleScanSettings.java b/service/java/com/android/server/wifi/wificond/SingleScanSettings.java
new file mode 100644
index 0000000..02950f0
--- /dev/null
+++ b/service/java/com/android/server/wifi/wificond/SingleScanSettings.java
@@ -0,0 +1,103 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * SingleScanSettings for wificond
+ *
+ * @hide
+ */
+public class SingleScanSettings implements Parcelable {
+    private static final String TAG = "SingleScanSettings";
+
+    public ArrayList<ChannelSettings> channelSettings;
+    public ArrayList<HiddenNetwork> hiddenNetworks;
+
+    /** public constructor */
+    public SingleScanSettings() { }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof SingleScanSettings)) {
+            return false;
+        }
+        SingleScanSettings settings = (SingleScanSettings) rhs;
+        if (settings == null) {
+            return false;
+        }
+        return channelSettings.equals(settings.channelSettings)
+                && hiddenNetworks.equals(settings.hiddenNetworks);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(channelSettings, hiddenNetworks);
+    }
+
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeTypedList(channelSettings);
+        out.writeTypedList(hiddenNetworks);
+    }
+
+    /** implement Parcelable interface */
+    public static final Parcelable.Creator<SingleScanSettings> CREATOR =
+            new Parcelable.Creator<SingleScanSettings>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public SingleScanSettings createFromParcel(Parcel in) {
+            SingleScanSettings result = new SingleScanSettings();
+            result.channelSettings = new ArrayList<ChannelSettings>();
+            in.readTypedList(result.channelSettings, ChannelSettings.CREATOR);
+            result.hiddenNetworks = new ArrayList<HiddenNetwork>();
+            in.readTypedList(result.hiddenNetworks, HiddenNetwork.CREATOR);
+            if (in.dataAvail() != 0) {
+                Log.e(TAG, "Found trailing data after parcel parsing.");
+            }
+
+            return result;
+        }
+
+        @Override
+        public SingleScanSettings[] newArray(int size) {
+            return new SingleScanSettings[size];
+        }
+    };
+}
diff --git a/service/jni/com_android_server_wifi_WifiNative.cpp b/service/jni/com_android_server_wifi_WifiNative.cpp
index ffd5b55..cf35028 100644
--- a/service/jni/com_android_server_wifi_WifiNative.cpp
+++ b/service/jni/com_android_server_wifi_WifiNative.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008, The Android Open Source Project
+ * Copyright 2017, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,2460 +14,22 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "wifi"
+#define LOG_TAG "wifi-jni"
 
-#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 <linux/if_arp.h>
 
-#include <algorithm>
-#include <limits>
-#include <vector>
+#include <log/log.h>
+#include <nativehelper/JniConstants.h>
+#include <nativehelper/ScopedBytes.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/jni.h>
 
-#include "wifi.h"
-#include "wifi_hal.h"
 #include "jni_helper.h"
-#include "rtt.h"
-#include "wifi_hal_stub.h"
-#define REPLY_BUF_SIZE 4096 + 1         // wpa_supplicant's maximum size + 1 for nul
-#define EVENT_BUF_SIZE 2048
-#define WAKE_REASON_TYPE_MAX 10
 
 namespace android {
 
-extern "C"
-jint Java_com_android_server_wifi_WifiNative_registerNanNatives(JNIEnv* env, jclass clazz);
-
-static jint DBG = false;
-constexpr int SAFE_NET_LOG_ID = 0x534e4554;
-
-//Please put all HAL function call here and call from the function table instead of directly call
-wifi_hal_fn hal_fn;
-static bool doCommand(JNIEnv* env, jstring javaCommand,
-                      char* reply, size_t reply_len) {
-    ScopedUtfChars command(env, javaCommand);
-    if (command.c_str() == NULL) {
-        return false; // ScopedUtfChars already threw on error.
-    }
-
-    if (DBG) {
-        ALOGD("doCommand: %s", command.c_str());
-    }
-
-    --reply_len; // Ensure we have room to add NUL termination.
-    if (::wifi_command(command.c_str(), reply, &reply_len) != 0) {
-        return false;
-    }
-
-    // Strip off trailing newline.
-    if (reply_len > 0 && reply[reply_len-1] == '\n') {
-        reply[reply_len-1] = '\0';
-    } else {
-        reply[reply_len] = '\0';
-    }
-    return true;
-}
-
-static jint doIntCommand(JNIEnv* env, jstring javaCommand) {
-    char reply[REPLY_BUF_SIZE];
-    if (!doCommand(env, javaCommand, reply, sizeof(reply))) {
-        return -1;
-    }
-    return static_cast<jint>(atoi(reply));
-}
-
-static jboolean doBooleanCommand(JNIEnv* env, jstring javaCommand) {
-    char reply[REPLY_BUF_SIZE];
-    if (!doCommand(env, javaCommand, reply, sizeof(reply))) {
-        return JNI_FALSE;
-    }
-    jboolean result = (strcmp(reply, "OK") == 0);
-    if (!result) {
-        ScopedUtfChars command(env, javaCommand);
-        ALOGI("command '%s' returned '%s", command.c_str(), reply);
-    }
-    return result;
-}
-
-// Send a command to the supplicant, and return the reply as a String.
-static jstring doStringCommand(JNIEnv* env, jstring javaCommand) {
-    char reply[REPLY_BUF_SIZE];
-    if (!doCommand(env, javaCommand, reply, sizeof(reply))) {
-        return NULL;
-    }
-    return env->NewStringUTF(reply);
-}
-
-static jboolean android_net_wifi_isDriverLoaded(JNIEnv* env, jclass)
-{
-    return (::is_wifi_driver_loaded() == 1);
-}
-
-static jboolean android_net_wifi_loadDriver(JNIEnv* env, jclass)
-{
-    return (::wifi_load_driver() == 0);
-}
-
-static jboolean android_net_wifi_unloadDriver(JNIEnv* env, jclass)
-{
-    return (::wifi_unload_driver() == 0);
-}
-
-static jboolean android_net_wifi_startSupplicant(JNIEnv* env, jclass, jboolean p2pSupported)
-{
-    return (::wifi_start_supplicant(p2pSupported) == 0);
-}
-
-static jboolean android_net_wifi_killSupplicant(JNIEnv* env, jclass, jboolean p2pSupported)
-{
-    return (::wifi_stop_supplicant(p2pSupported) == 0);
-}
-
-static jboolean android_net_wifi_connectToSupplicant(JNIEnv* env, jclass)
-{
-    return (::wifi_connect_to_supplicant() == 0);
-}
-
-static void android_net_wifi_closeSupplicantConnection(JNIEnv* env, jclass)
-{
-    ::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);
-    if (nread > 0) {
-        return env->NewStringUTF(buf);
-    } else {
-        return NULL;
-    }
-}
-
-static jboolean android_net_wifi_doBooleanCommand(JNIEnv* env, jclass, jstring javaCommand) {
-    return doBooleanCommand(env, javaCommand);
-}
-
-static jint android_net_wifi_doIntCommand(JNIEnv* env, jclass, jstring javaCommand) {
-    return doIntCommand(env, javaCommand);
-}
-
-static jstring android_net_wifi_doStringCommand(JNIEnv* env, jclass, jstring javaCommand) {
-    return doStringCommand(env,javaCommand);
-}
-
-/* wifi_hal <==> WifiNative bridge */
-
-static jclass mCls;                             /* saved WifiNative object */
-static JavaVM *mVM;                             /* saved JVM pointer */
-
-static const char *WifiHandleVarName = "sWifiHalHandle";
-static const char *WifiIfaceHandleVarName = "sWifiIfaceHandles";
-
-wifi_handle getWifiHandle(JNIHelper &helper, jclass cls) {
-    return (wifi_handle) helper.getStaticLongField(cls, WifiHandleVarName);
-}
-
-wifi_interface_handle getIfaceHandle(JNIHelper &helper, jclass cls, jint index) {
-    return (wifi_interface_handle) helper.getStaticLongArrayField(cls, WifiIfaceHandleVarName, index);
-}
-
-jboolean setSSIDField(JNIHelper helper, jobject scanResult, const char *rawSsid) {
-
-    int len = strlen(rawSsid);
-
-    if (len > 0) {
-        JNIObject<jbyteArray> ssidBytes = helper.newByteArray(len);
-        helper.setByteArrayRegion(ssidBytes, 0, len, (jbyte *) rawSsid);
-        jboolean ret = helper.callStaticMethod(mCls,
-                "setSsid", "([BLandroid/net/wifi/ScanResult;)Z", ssidBytes.get(), scanResult);
-        return ret;
-    } else {
-        //empty SSID or SSID start with \0
-        return true;
-    }
-}
-static JNIObject<jobject> createScanResult(JNIHelper &helper, wifi_scan_result *result,
-        bool fill_ie) {
-    // ALOGD("creating scan result");
-    JNIObject<jobject> scanResult = helper.createObject("android/net/wifi/ScanResult");
-    if (scanResult == NULL) {
-        ALOGE("Error in creating scan result");
-        return JNIObject<jobject>(helper, NULL);
-    }
-
-    ALOGV("setting SSID to %s", result->ssid);
-
-    if (!setSSIDField(helper, scanResult, result->ssid)) {
-        ALOGE("Error on set SSID");
-        return JNIObject<jobject>(helper, NULL);
-    }
-
-    char bssid[32];
-    sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", result->bssid[0], result->bssid[1],
-        result->bssid[2], result->bssid[3], result->bssid[4], result->bssid[5]);
-
-    helper.setStringField(scanResult, "BSSID", bssid);
-
-    helper.setIntField(scanResult, "level", result->rssi);
-    helper.setIntField(scanResult, "frequency", result->channel);
-    helper.setLongField(scanResult, "timestamp", result->ts);
-
-    if (fill_ie) {
-        JNIObject<jbyteArray> elements = helper.newByteArray(result->ie_length);
-        if (elements == NULL) {
-            ALOGE("Error in allocating elements array, length=%d", result->ie_length);
-            return JNIObject<jobject>(helper, NULL);
-        }
-        jbyte * bytes = (jbyte *)&(result->ie_data[0]);
-        helper.setByteArrayRegion(elements, 0, result->ie_length, bytes);
-        helper.setObjectField(scanResult, "bytes", "[B", elements);
-    }
-
-    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);
-}
-
-static jboolean android_net_wifi_startHal(JNIEnv* env, jclass cls) {
-    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);
-    }
-}
-
-void android_net_wifi_hal_cleaned_up_handler(wifi_handle handle) {
-    ALOGD("In wifi cleaned up handler");
-
-    JNIHelper helper(mVM);
-    helper.setStaticLongField(mCls, WifiHandleVarName, 0);
-
-    helper.deleteGlobalRef(mCls);
-    mCls = NULL;
-    mVM  = NULL;
-}
-
-static void android_net_wifi_stopHal(JNIEnv* env, jclass cls) {
-    ALOGD("In wifi stop Hal");
-
-    JNIHelper helper(env);
-    wifi_handle halHandle = getWifiHandle(helper, cls);
-    if (halHandle == NULL)
-        return;
-
-    ALOGD("halHandle = %p, mVM = %p, mCls = %p", halHandle, mVM, mCls);
-    hal_fn.wifi_cleanup(halHandle, android_net_wifi_hal_cleaned_up_handler);
-}
-
-static void android_net_wifi_waitForHalEvents(JNIEnv* env, jclass cls) {
-
-    ALOGD("waitForHalEvents called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
-
-    JNIHelper helper(env);
-    wifi_handle halHandle = getWifiHandle(helper, cls);
-    hal_fn.wifi_event_loop(halHandle);
-    set_iface_flags("wlan0", false);
-}
-
-static int android_net_wifi_getInterfaces(JNIEnv *env, jclass cls) {
-    int n = 0;
-
-    JNIHelper helper(env);
-
-    wifi_handle halHandle = getWifiHandle(helper, cls);
-    wifi_interface_handle *ifaceHandles = NULL;
-    int result = hal_fn.wifi_get_ifaces(halHandle, &n, &ifaceHandles);
-    if (result < 0) {
-        return result;
-    }
-
-    if (n < 0) {
-        THROW(helper,"android_net_wifi_getInterfaces no interfaces");
-        return 0;
-    }
-
-    if (ifaceHandles == NULL) {
-       THROW(helper,"android_net_wifi_getInterfaces null interface array");
-       return 0;
-    }
-
-    if (n > 8) {
-        THROW(helper,"Too many interfaces");
-        return 0;
-    }
-
-    jlongArray array = (env)->NewLongArray(n);
-    if (array == NULL) {
-        THROW(helper,"Error in accessing array");
-        return 0;
-    }
-
-    jlong elems[8];
-    for (int i = 0; i < n; i++) {
-        elems[i] = reinterpret_cast<jlong>(ifaceHandles[i]);
-    }
-
-    helper.setLongArrayRegion(array, 0, n, elems);
-    helper.setStaticLongArrayField(cls, WifiIfaceHandleVarName, array);
-
-    return (result < 0) ? result : n;
-}
-
-static jstring android_net_wifi_getInterfaceName(JNIEnv *env, jclass cls, jint i) {
-
-    char buf[EVENT_BUF_SIZE];
-
-    JNIHelper helper(env);
-
-    jlong value = helper.getStaticLongArrayField(cls, WifiIfaceHandleVarName, i);
-    wifi_interface_handle handle = (wifi_interface_handle) value;
-    int result = hal_fn.wifi_get_iface_name(handle, buf, sizeof(buf));
-    if (result < 0) {
-        return NULL;
-    } else {
-        JNIObject<jstring> name = helper.newStringUTF(buf);
-        return name.detach();
-    }
-}
-
-
-static void onScanEvent(wifi_request_id id, wifi_scan_event event) {
-
-    JNIHelper helper(mVM);
-
-    // ALOGD("onScanStatus called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
-
-    helper.reportEvent(mCls, "onScanStatus", "(II)V", id, event);
-}
-
-static void onFullScanResult(wifi_request_id id, wifi_scan_result *result,
-        unsigned buckets_scanned) {
-
-    JNIHelper helper(mVM);
-
-    //ALOGD("onFullScanResult called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
-
-    JNIObject<jobject> scanResult = createScanResult(helper, result, true);
-
-    if (scanResult == NULL) {
-        return;
-    }
-
-    helper.reportEvent(mCls, "onFullScanResult", "(ILandroid/net/wifi/ScanResult;II)V", id,
-            scanResult.get(), buckets_scanned, (jint) result->capability);
-}
-
-static jboolean android_net_wifi_startScan(
-        JNIEnv *env, jclass cls, jint iface, jint id, jobject settings) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    // ALOGD("starting scan on interface[%d] = %p", iface, handle);
-
-    wifi_scan_cmd_params params;
-    memset(&params, 0, sizeof(params));
-
-    params.base_period = helper.getIntField(settings, "base_period_ms");
-    params.max_ap_per_scan = helper.getIntField(settings, "max_ap_per_scan");
-    params.report_threshold_percent = helper.getIntField(settings, "report_threshold_percent");
-    params.report_threshold_num_scans = helper.getIntField(settings, "report_threshold_num_scans");
-
-    ALOGD("Initialized common fields %d, %d, %d, %d", params.base_period, params.max_ap_per_scan,
-            params.report_threshold_percent, params.report_threshold_num_scans);
-
-    const char *bucket_array_type = "[Lcom/android/server/wifi/WifiNative$BucketSettings;";
-    const char *channel_array_type = "[Lcom/android/server/wifi/WifiNative$ChannelSettings;";
-
-    params.num_buckets = helper.getIntField(settings, "num_buckets");
-
-    // ALOGD("Initialized num_buckets to %d", params.num_buckets);
-
-    for (int i = 0; i < params.num_buckets; i++) {
-        JNIObject<jobject> bucket = helper.getObjectArrayField(
-                settings, "buckets", bucket_array_type, i);
-
-        params.buckets[i].bucket = helper.getIntField(bucket, "bucket");
-        params.buckets[i].band = (wifi_band) helper.getIntField(bucket, "band");
-        params.buckets[i].period = helper.getIntField(bucket, "period_ms");
-        params.buckets[i].max_period = helper.getIntField(bucket, "max_period_ms");
-        // Although HAL API allows configurable base value for the truncated
-        // exponential back off scan. Native API and above support only
-        // truncated binary exponential back off scan.
-        // Hard code value of base to 2 here.
-        params.buckets[i].base = 2;
-        params.buckets[i].step_count = helper.getIntField(bucket, "step_count");
-
-        int report_events = helper.getIntField(bucket, "report_events");
-        params.buckets[i].report_events = report_events;
-
-        if (DBG) {
-            ALOGD("bucket[%d] = %d:%d:%d:%d:%d:%d:%d", i, params.buckets[i].bucket,
-                    params.buckets[i].band, params.buckets[i].period,
-                    params.buckets[i].max_period, params.buckets[i].base,
-                    params.buckets[i].step_count, report_events);
-        }
-
-        params.buckets[i].num_channels = helper.getIntField(bucket, "num_channels");
-        // ALOGD("Initialized num_channels to %d", params.buckets[i].num_channels);
-
-        for (int j = 0; j < params.buckets[i].num_channels; j++) {
-            JNIObject<jobject> channel = helper.getObjectArrayField(
-                    bucket, "channels", channel_array_type, j);
-
-            params.buckets[i].channels[j].channel = helper.getIntField(channel, "frequency");
-            params.buckets[i].channels[j].dwellTimeMs = helper.getIntField(channel, "dwell_time_ms");
-
-            bool passive = helper.getBoolField(channel, "passive");
-            params.buckets[i].channels[j].passive = (passive ? 1 : 0);
-
-            // ALOGD("Initialized channel %d", params.buckets[i].channels[j].channel);
-        }
-    }
-
-    // ALOGD("Initialized all fields");
-
-    wifi_scan_result_handler handler;
-    memset(&handler, 0, sizeof(handler));
-    handler.on_full_scan_result = &onFullScanResult;
-    handler.on_scan_event = &onScanEvent;
-
-    return hal_fn.wifi_start_gscan(id, handle, params, handler) == WIFI_SUCCESS;
-}
-
-static jboolean android_net_wifi_stopScan(JNIEnv *env, jclass cls, jint iface, jint id) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    // ALOGD("stopping scan on interface[%d] = %p", iface, handle);
-
-    return hal_fn.wifi_stop_gscan(id, handle)  == WIFI_SUCCESS;
-}
-
-static int compare_scan_result_timestamp(const void *v1, const void *v2) {
-    const wifi_scan_result *result1 = static_cast<const wifi_scan_result *>(v1);
-    const wifi_scan_result *result2 = static_cast<const wifi_scan_result *>(v2);
-    return result1->ts - result2->ts;
-}
-
-static jobject android_net_wifi_getScanResults(
-        JNIEnv *env, jclass cls, jint iface, jboolean flush)  {
-
-    JNIHelper helper(env);
-    wifi_cached_scan_results scan_data[64];
-    int num_scan_data = 64;
-
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    // ALOGD("getting scan results on interface[%d] = %p", iface, handle);
-
-    byte b = flush ? 0xFF : 0;
-    int result = hal_fn.wifi_get_cached_gscan_results(handle, b, num_scan_data, scan_data, &num_scan_data);
-    if (result == WIFI_SUCCESS) {
-        JNIObject<jobjectArray> scanData = helper.createObjectArray(
-                "android/net/wifi/WifiScanner$ScanData", num_scan_data);
-        if (scanData == NULL) {
-            ALOGE("Error in allocating array of scanData for getScanResults, length=%d",
-                  num_scan_data);
-            return NULL;
-        }
-
-        for (int i = 0; i < num_scan_data; i++) {
-
-            JNIObject<jobject> data = helper.createObject("android/net/wifi/WifiScanner$ScanData");
-            if (data == NULL) {
-                ALOGE("Error in allocating scanData for getScanResults");
-                return NULL;
-            }
-
-            helper.setIntField(data, "mId", scan_data[i].scan_id);
-            helper.setIntField(data, "mFlags", scan_data[i].flags);
-            helper.setIntField(data, "mBucketsScanned", scan_data[i].buckets_scanned);
-
-            /* sort all scan results by timestamp */
-            qsort(scan_data[i].results, scan_data[i].num_results,
-                    sizeof(wifi_scan_result), compare_scan_result_timestamp);
-
-            JNIObject<jobjectArray> scanResults = helper.createObjectArray(
-                    "android/net/wifi/ScanResult", scan_data[i].num_results);
-            if (scanResults == NULL) {
-                ALOGE("Error in allocating scanResult array for getScanResults, length=%d",
-                      scan_data[i].num_results);
-                return NULL;
-            }
-
-            wifi_scan_result *results = scan_data[i].results;
-            for (int j = 0; j < scan_data[i].num_results; j++) {
-
-                JNIObject<jobject> scanResult = createScanResult(helper, &results[j], false);
-                if (scanResult == NULL) {
-                    ALOGE("Error in creating scan result for getScanResults");
-                    return NULL;
-                }
-
-                helper.setObjectArrayElement(scanResults, j, scanResult);
-            }
-
-            helper.setObjectField(data, "mResults", "[Landroid/net/wifi/ScanResult;", scanResults);
-            helper.setObjectArrayElement(scanData, i, data);
-        }
-
-        // ALOGD("retrieved %d scan data from interface[%d] = %p", num_scan_data, iface, handle);
-        return scanData.detach();
-    } else {
-        return NULL;
-    }
-}
-
-
-static jboolean android_net_wifi_getScanCapabilities(
-        JNIEnv *env, jclass cls, jint iface, jobject capabilities) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    // ALOGD("getting scan capabilities on interface[%d] = %p", iface, handle);
-
-    wifi_gscan_capabilities c;
-    memset(&c, 0, sizeof(c));
-    int result = hal_fn.wifi_get_gscan_capabilities(handle, &c);
-    if (result != WIFI_SUCCESS) {
-        ALOGD("failed to get capabilities : %d", result);
-        return JNI_FALSE;
-    }
-
-    helper.setIntField(capabilities, "max_scan_cache_size", c.max_scan_cache_size);
-    helper.setIntField(capabilities, "max_scan_buckets", c.max_scan_buckets);
-    helper.setIntField(capabilities, "max_ap_cache_per_scan", c.max_ap_cache_per_scan);
-    helper.setIntField(capabilities, "max_rssi_sample_size", c.max_rssi_sample_size);
-    helper.setIntField(capabilities, "max_scan_reporting_threshold", c.max_scan_reporting_threshold);
-    helper.setIntField(capabilities, "max_hotlist_bssids", c.max_hotlist_bssids);
-    helper.setIntField(capabilities, "max_significant_wifi_change_aps",
-            c.max_significant_wifi_change_aps);
-    helper.setIntField(capabilities, "max_bssid_history_entries", c.max_bssid_history_entries);
-    helper.setIntField(capabilities, "max_number_epno_networks", c.max_number_epno_networks);
-    helper.setIntField(capabilities, "max_number_epno_networks_by_ssid",
-            c.max_number_epno_networks_by_ssid);
-    helper.setIntField(capabilities, "max_number_of_white_listed_ssid",
-            c.max_number_of_white_listed_ssid);
-
-    return JNI_TRUE;
-}
-
-
-static byte parseHexChar(char ch) {
-    if (isdigit(ch))
-        return ch - '0';
-    else if ('A' <= ch && ch <= 'F')
-        return ch - 'A' + 10;
-    else if ('a' <= ch && ch <= 'f')
-        return ch - 'a' + 10;
-    else {
-        ALOGE("invalid character in bssid %c", ch);
-        return 0;
-    }
-}
-
-static byte parseHexByte(const char * &str) {
-    if (str[0] == '\0') {
-        ALOGE("Passed an empty string");
-        return 0;
-    }
-    byte b = parseHexChar(str[0]);
-    if (str[1] == '\0' || str[1] == ':') {
-        str ++;
-    } else {
-        b = b << 4 | parseHexChar(str[1]);
-        str += 2;
-    }
-
-    // Skip trailing delimiter if not at the end of the string.
-    if (str[0] != '\0') {
-        str++;
-    }
-    return b;
-}
-
-static void parseMacAddress(const char *str, mac_addr addr) {
-    addr[0] = parseHexByte(str);
-    addr[1] = parseHexByte(str);
-    addr[2] = parseHexByte(str);
-    addr[3] = parseHexByte(str);
-    addr[4] = parseHexByte(str);
-    addr[5] = parseHexByte(str);
-}
-
-static bool parseMacAddress(JNIEnv *env, jobject obj, mac_addr addr) {
-    JNIHelper helper(env);
-    JNIObject<jstring> macAddrString = helper.getStringField(obj, "bssid");
-    if (macAddrString == NULL) {
-        ALOGE("Error getting bssid field");
-        return false;
-    }
-
-    ScopedUtfChars chars(env, macAddrString);
-    const char *bssid = chars.c_str();
-    if (bssid == NULL) {
-        ALOGE("Error getting bssid");
-        return false;
-    }
-
-    parseMacAddress(bssid, addr);
-    return true;
-}
-
-static void onHotlistApFound(wifi_request_id id,
-        unsigned num_results, wifi_scan_result *results) {
-
-    JNIHelper helper(mVM);
-    ALOGD("onHotlistApFound called, vm = %p, obj = %p, num_results = %d", mVM, mCls, num_results);
-
-    JNIObject<jobjectArray> scanResults = helper.newObjectArray(num_results,
-            "android/net/wifi/ScanResult", NULL);
-    if (scanResults == NULL) {
-        ALOGE("Error in allocating ScanResult array in onHotlistApFound, length=%d", num_results);
-        return;
-    }
-
-    for (unsigned i = 0; i < num_results; i++) {
-
-        JNIObject<jobject> scanResult = createScanResult(helper, &results[i], false);
-        if (scanResult == NULL) {
-            ALOGE("Error in creating scan result in onHotlistApFound");
-            return;
-        }
-
-        helper.setObjectArrayElement(scanResults, i, scanResult);
-
-        ALOGD("Found AP %32s", results[i].ssid);
-    }
-
-    helper.reportEvent(mCls, "onHotlistApFound", "(I[Landroid/net/wifi/ScanResult;)V",
-        id, scanResults.get());
-}
-
-static void onHotlistApLost(wifi_request_id id,
-        unsigned num_results, wifi_scan_result *results) {
-
-    JNIHelper helper(mVM);
-    ALOGD("onHotlistApLost called, vm = %p, obj = %p, num_results = %d", mVM, mCls, num_results);
-
-    JNIObject<jobjectArray> scanResults = helper.newObjectArray(num_results,
-            "android/net/wifi/ScanResult", NULL);
-    if (scanResults == NULL) {
-        ALOGE("Error in allocating ScanResult array onHotlistApLost, length=%d", num_results);
-        return;
-    }
-
-    for (unsigned i = 0; i < num_results; i++) {
-
-        JNIObject<jobject> scanResult = createScanResult(helper, &results[i], false);
-        if (scanResult == NULL) {
-            ALOGE("Error in creating scan result in onHotlistApLost");
-            return;
-        }
-
-        helper.setObjectArrayElement(scanResults, i, scanResult);
-
-        ALOGD("Lost AP %32s", results[i].ssid);
-    }
-
-    helper.reportEvent(mCls, "onHotlistApLost", "(I[Landroid/net/wifi/ScanResult;)V",
-        id, scanResults.get());
-}
-
-
-static jboolean android_net_wifi_setHotlist(
-        JNIEnv *env, jclass cls, jint iface, jint id, jobject ap)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("setting hotlist on interface[%d] = %p", iface, handle);
-
-    wifi_bssid_hotlist_params params;
-    memset(&params, 0, sizeof(params));
-
-    params.lost_ap_sample_size = helper.getIntField(ap, "apLostThreshold");
-
-    JNIObject<jobjectArray> array = helper.getArrayField(
-            ap, "bssidInfos", "[Landroid/net/wifi/WifiScanner$BssidInfo;");
-    params.num_bssid = helper.getArrayLength(array);
-
-    if (params.num_bssid == 0) {
-        ALOGE("setHotlist array length was 0");
-        return false;
-    }
-
-    if (params.num_bssid >
-            static_cast<int>(sizeof(params.ap) / sizeof(params.ap[0]))) {
-        ALOGE("setHotlist array length is too long");
-        android_errorWriteLog(SAFE_NET_LOG_ID, "31856351");
-        return false;
-    }
-
-    for (int i = 0; i < params.num_bssid; i++) {
-        JNIObject<jobject> objAp = helper.getObjectArrayElement(array, i);
-
-        JNIObject<jstring> macAddrString = helper.getStringField(objAp, "bssid");
-        if (macAddrString == NULL) {
-            ALOGE("Error getting bssid field");
-            return false;
-        }
-
-        ScopedUtfChars chars(env, macAddrString);
-        const char *bssid = chars.c_str();
-        if (bssid == NULL) {
-            ALOGE("Error getting bssid");
-            return false;
-        }
-        parseMacAddress(bssid, params.ap[i].bssid);
-
-        mac_addr addr;
-        memcpy(addr, params.ap[i].bssid, sizeof(mac_addr));
-
-        char bssidOut[32];
-        sprintf(bssidOut, "%0x:%0x:%0x:%0x:%0x:%0x", addr[0], addr[1],
-            addr[2], addr[3], addr[4], addr[5]);
-
-        ALOGD("Added bssid %s", bssidOut);
-
-        params.ap[i].low = helper.getIntField(objAp, "low");
-        params.ap[i].high = helper.getIntField(objAp, "high");
-    }
-
-    wifi_hotlist_ap_found_handler handler;
-    memset(&handler, 0, sizeof(handler));
-
-    handler.on_hotlist_ap_found = &onHotlistApFound;
-    handler.on_hotlist_ap_lost  = &onHotlistApLost;
-    return hal_fn.wifi_set_bssid_hotlist(id, handle, params, handler) == WIFI_SUCCESS;
-}
-
-static jboolean android_net_wifi_resetHotlist(JNIEnv *env, jclass cls, jint iface, jint id)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("resetting hotlist on interface[%d] = %p", iface, handle);
-
-    return hal_fn.wifi_reset_bssid_hotlist(id, handle) == WIFI_SUCCESS;
-}
-
-void onSignificantWifiChange(wifi_request_id id,
-        unsigned num_results, wifi_significant_change_result **results) {
-
-    JNIHelper helper(mVM);
-
-    ALOGD("onSignificantWifiChange called, vm = %p, obj = %p", mVM, mCls);
-
-    JNIObject<jobjectArray> scanResults = helper.newObjectArray(
-            num_results, "android/net/wifi/ScanResult", NULL);
-    if (scanResults == NULL) {
-        ALOGE("Error in allocating ScanResult array in onSignificantWifiChange, length=%d",
-              num_results);
-        return;
-    }
-
-    for (unsigned i = 0; i < num_results; i++) {
-
-        wifi_significant_change_result &result = *(results[i]);
-
-        JNIObject<jobject> scanResult = helper.createObject("android/net/wifi/ScanResult");
-        if (scanResult == NULL) {
-            ALOGE("Error in creating scan result in onSignificantWifiChange");
-            return;
-        }
-
-        // helper.setStringField(scanResult, "SSID", results[i].ssid);
-
-        char bssid[32];
-        sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", result.bssid[0], result.bssid[1],
-            result.bssid[2], result.bssid[3], result.bssid[4], result.bssid[5]);
-
-        helper.setStringField(scanResult, "BSSID", bssid);
-
-        helper.setIntField(scanResult, "level", result.rssi[0]);
-        helper.setIntField(scanResult, "frequency", result.channel);
-        // helper.setLongField(scanResult, "timestamp", result.ts);
-
-        helper.setObjectArrayElement(scanResults, i, scanResult);
-    }
-
-    helper.reportEvent(mCls, "onSignificantWifiChange", "(I[Landroid/net/wifi/ScanResult;)V",
-        id, scanResults.get());
-
-}
-
-static jboolean android_net_wifi_trackSignificantWifiChange(
-        JNIEnv *env, jclass cls, jint iface, jint id, jobject settings)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("tracking significant wifi change on interface[%d] = %p", iface, handle);
-
-    wifi_significant_change_params params;
-    memset(&params, 0, sizeof(params));
-
-    params.rssi_sample_size = helper.getIntField(settings, "rssiSampleSize");
-    params.lost_ap_sample_size = helper.getIntField(settings, "lostApSampleSize");
-    params.min_breaching = helper.getIntField(settings, "minApsBreachingThreshold");
-
-    const char *bssid_info_array_type = "[Landroid/net/wifi/WifiScanner$BssidInfo;";
-    JNIObject<jobjectArray> bssids = helper.getArrayField(
-            settings, "bssidInfos", bssid_info_array_type);
-    params.num_bssid = helper.getArrayLength(bssids);
-
-    if (params.num_bssid == 0) {
-        ALOGE("BssidInfo array length was 0");
-        return false;
-    }
-    if (params.num_bssid >
-        static_cast<int>(sizeof(params.ap) / sizeof(params.ap[0]))) {
-        ALOGE("trackSignificantWifiChange array length is too long");
-        android_errorWriteLog(SAFE_NET_LOG_ID, "37775935");
-        return false;
-    }
-    ALOGD("Initialized common fields %d, %d, %d, %d", params.rssi_sample_size,
-            params.lost_ap_sample_size, params.min_breaching, params.num_bssid);
-
-    for (int i = 0; i < params.num_bssid; i++) {
-        JNIObject<jobject> objAp = helper.getObjectArrayElement(bssids, i);
-
-        JNIObject<jstring> macAddrString = helper.getStringField(objAp, "bssid");
-        if (macAddrString == NULL) {
-            ALOGE("Error getting bssid field");
-            return false;
-        }
-
-        ScopedUtfChars chars(env, macAddrString.get());
-        const char *bssid = chars.c_str();
-        if (bssid == NULL) {
-            ALOGE("Error getting bssid");
-            return false;
-        }
-
-        mac_addr addr;
-        parseMacAddress(bssid, addr);
-        memcpy(params.ap[i].bssid, addr, sizeof(mac_addr));
-
-        char bssidOut[32];
-        sprintf(bssidOut, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1],
-            addr[2], addr[3], addr[4], addr[5]);
-
-        params.ap[i].low = helper.getIntField(objAp, "low");
-        params.ap[i].high = helper.getIntField(objAp, "high");
-
-        ALOGD("Added bssid %s, [%04d, %04d]", bssidOut, params.ap[i].low, params.ap[i].high);
-    }
-
-    ALOGD("Added %d bssids", params.num_bssid);
-
-    wifi_significant_change_handler handler;
-    memset(&handler, 0, sizeof(handler));
-
-    handler.on_significant_change = &onSignificantWifiChange;
-    return hal_fn.wifi_set_significant_change_handler(id, handle, params, handler) == WIFI_SUCCESS;
-}
-
-static jboolean android_net_wifi_untrackSignificantWifiChange(
-        JNIEnv *env, jclass cls, jint iface, jint id)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("resetting significant wifi change on interface[%d] = %p", iface, handle);
-
-    return hal_fn.wifi_reset_significant_change_handler(id, handle) == WIFI_SUCCESS;
-}
-
-wifi_iface_stat link_stat;
-wifi_radio_stat radio_stat; // L release has support for only one radio
-u32 *tx_time_per_level_arr = 0;
-// Let's cache the supported feature set to avoid unnecessary HAL invocations.
-feature_set cached_feature_set = 0;
-
-bool isTxLevelStatsPresent(wifi_radio_stat *radio_stats) {
-    if (IS_SUPPORTED_FEATURE(WIFI_FEATURE_TX_TRANSMIT_POWER, cached_feature_set)) {
-        if(radio_stats->tx_time_per_levels != 0 && radio_stats->num_tx_levels > 0) {
-            return true;
-        } else {
-            ALOGE("Ignoring invalid tx_level info in radio_stats");
-        }
-    }
-    return false;
-}
-
-void onLinkStatsResults(wifi_request_id id, wifi_iface_stat *iface_stat,
-         int num_radios, wifi_radio_stat *radio_stats)
-{
-    if (iface_stat != 0) {
-        memcpy(&link_stat, iface_stat, sizeof(wifi_iface_stat));
-    } else {
-        memset(&link_stat, 0, sizeof(wifi_iface_stat));
-    }
-
-    if (num_radios > 0 && radio_stats != 0) {
-        memcpy(&radio_stat, radio_stats, sizeof(wifi_radio_stat));
-        if (isTxLevelStatsPresent(radio_stats)) {
-            // This realloc should be a no-op after the first allocation because for a given
-            // device, the number of power levels should not change.
-            u32 arr_size = sizeof(u32) * radio_stats->num_tx_levels;
-            tx_time_per_level_arr = (u32 *)realloc(tx_time_per_level_arr, arr_size);
-            memcpy(tx_time_per_level_arr, radio_stats->tx_time_per_levels, arr_size);
-            radio_stat.tx_time_per_levels = tx_time_per_level_arr;
-        } else {
-            radio_stat.num_tx_levels = 0;
-            radio_stat.tx_time_per_levels = 0;
-        }
-    } else {
-        memset(&radio_stat, 0, sizeof(wifi_radio_stat));
-    }
-}
-
-static void android_net_wifi_setLinkLayerStats (JNIEnv *env, jclass cls, jint iface, int enable)  {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    wifi_link_layer_params params;
-    params.aggressive_statistics_gathering = enable;
-    params.mpdu_size_threshold = 128;
-
-    ALOGD("android_net_wifi_setLinkLayerStats: %u\n", enable);
-
-    hal_fn.wifi_set_link_stats(handle, params);
-}
-
-static jobject android_net_wifi_getLinkLayerStats (JNIEnv *env, jclass cls, jint iface)  {
-
-    JNIHelper helper(env);
-    wifi_stats_result_handler handler;
-    memset(&handler, 0, sizeof(handler));
-    handler.on_link_stats_results = &onLinkStatsResults;
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    int result;
-    // Cache the features supported by the device to determine if tx level stats are present or not
-    if (cached_feature_set == 0) {
-        result = hal_fn.wifi_get_supported_feature_set(handle, &cached_feature_set);
-        if (result != WIFI_SUCCESS) {
-            cached_feature_set = 0;
-        }
-    }
-
-    result = hal_fn.wifi_get_link_stats(0, handle, handler);
-    if (result < 0) {
-        ALOGE("android_net_wifi_getLinkLayerStats: failed to get link statistics\n");
-        return NULL;
-    }
-
-    JNIObject<jobject> wifiLinkLayerStats = helper.createObject(
-            "android/net/wifi/WifiLinkLayerStats");
-    if (wifiLinkLayerStats == NULL) {
-       ALOGE("Error in allocating wifiLinkLayerStats");
-       return NULL;
-    }
-
-    JNIObject<jintArray> tx_time_per_level = helper.newIntArray(radio_stat.num_tx_levels);
-    if (tx_time_per_level == NULL) {
-        ALOGE("Error in allocating wifiLinkLayerStats");
-        return NULL;
-    }
-
-    helper.setIntField(wifiLinkLayerStats, "beacon_rx", link_stat.beacon_rx);
-    helper.setIntField(wifiLinkLayerStats, "rssi_mgmt", link_stat.rssi_mgmt);
-    helper.setLongField(wifiLinkLayerStats, "rxmpdu_be", link_stat.ac[WIFI_AC_BE].rx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "rxmpdu_bk", link_stat.ac[WIFI_AC_BK].rx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "rxmpdu_vi", link_stat.ac[WIFI_AC_VI].rx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "rxmpdu_vo", link_stat.ac[WIFI_AC_VO].rx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "txmpdu_be", link_stat.ac[WIFI_AC_BE].tx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "txmpdu_bk", link_stat.ac[WIFI_AC_BK].tx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "txmpdu_vi", link_stat.ac[WIFI_AC_VI].tx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "txmpdu_vo", link_stat.ac[WIFI_AC_VO].tx_mpdu);
-    helper.setLongField(wifiLinkLayerStats, "lostmpdu_be", link_stat.ac[WIFI_AC_BE].mpdu_lost);
-    helper.setLongField(wifiLinkLayerStats, "lostmpdu_bk", link_stat.ac[WIFI_AC_BK].mpdu_lost);
-    helper.setLongField(wifiLinkLayerStats, "lostmpdu_vi",  link_stat.ac[WIFI_AC_VI].mpdu_lost);
-    helper.setLongField(wifiLinkLayerStats, "lostmpdu_vo", link_stat.ac[WIFI_AC_VO].mpdu_lost);
-    helper.setLongField(wifiLinkLayerStats, "retries_be", link_stat.ac[WIFI_AC_BE].retries);
-    helper.setLongField(wifiLinkLayerStats, "retries_bk", link_stat.ac[WIFI_AC_BK].retries);
-    helper.setLongField(wifiLinkLayerStats, "retries_vi", link_stat.ac[WIFI_AC_VI].retries);
-    helper.setLongField(wifiLinkLayerStats, "retries_vo", link_stat.ac[WIFI_AC_VO].retries);
-
-    helper.setIntField(wifiLinkLayerStats, "on_time", radio_stat.on_time);
-    helper.setIntField(wifiLinkLayerStats, "tx_time", radio_stat.tx_time);
-    helper.setIntField(wifiLinkLayerStats, "rx_time", radio_stat.rx_time);
-    helper.setIntField(wifiLinkLayerStats, "on_time_scan", radio_stat.on_time_scan);
-    if (radio_stat.tx_time_per_levels != 0) {
-        helper.setIntArrayRegion(tx_time_per_level, 0, radio_stat.num_tx_levels,
-                (jint *)radio_stat.tx_time_per_levels);
-    }
-    helper.setObjectField(wifiLinkLayerStats, "tx_time_per_level", "[I", tx_time_per_level);
-
-
-    return wifiLinkLayerStats.detach();
-}
-
-static jint android_net_wifi_getSupportedFeatures(JNIEnv *env, jclass cls, jint iface) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    feature_set set = 0;
-
-    wifi_error result = WIFI_SUCCESS;
-    /*
-    set = WIFI_FEATURE_INFRA
-        | WIFI_FEATURE_INFRA_5G
-        | WIFI_FEATURE_HOTSPOT
-        | WIFI_FEATURE_P2P
-        | WIFI_FEATURE_SOFT_AP
-        | WIFI_FEATURE_GSCAN
-        | WIFI_FEATURE_PNO
-        | WIFI_FEATURE_TDLS
-        | WIFI_FEATURE_EPR;
-    */
-
-    result = hal_fn.wifi_get_supported_feature_set(handle, &set);
-    if (result == WIFI_SUCCESS) {
-        // ALOGD("wifi_get_supported_feature_set returned set = 0x%x", set);
-        return set;
-    } else {
-        ALOGE("wifi_get_supported_feature_set returned error = 0x%x", result);
-        return 0;
-    }
-}
-
-static void onRttResults(wifi_request_id id, unsigned num_results, wifi_rtt_result* results[]) {
-
-    JNIHelper helper(mVM);
-
-    if (DBG) ALOGD("onRttResults called, vm = %p, obj = %p", mVM, mCls);
-
-    JNIObject<jobjectArray> rttResults = helper.newObjectArray(
-            num_results, "android/net/wifi/RttManager$RttResult", NULL);
-    if (rttResults == NULL) {
-        ALOGE("Error in allocating RttResult array in onRttResults, length=%d", num_results);
-        return;
-    }
-
-    for (unsigned i = 0; i < num_results; i++) {
-
-        wifi_rtt_result *result = results[i];
-
-        JNIObject<jobject> rttResult = helper.createObject("android/net/wifi/RttManager$RttResult");
-        if (rttResult == NULL) {
-            ALOGE("Error in creating rtt result in onRttResults");
-            return;
-        }
-
-        char bssid[32];
-        sprintf(bssid, "%02x:%02x:%02x:%02x:%02x:%02x", result->addr[0], result->addr[1],
-            result->addr[2], result->addr[3], result->addr[4], result->addr[5]);
-
-        helper.setStringField(rttResult, "bssid", bssid);
-        helper.setIntField( rttResult, "burstNumber",              result->burst_num);
-        helper.setIntField( rttResult, "measurementFrameNumber",   result->measurement_number);
-        helper.setIntField( rttResult, "successMeasurementFrameNumber",   result->success_number);
-        helper.setIntField(rttResult, "frameNumberPerBurstPeer",   result->number_per_burst_peer);
-        helper.setIntField( rttResult, "status",                   result->status);
-        helper.setIntField( rttResult, "measurementType",          result->type);
-        helper.setIntField(rttResult, "retryAfterDuration",       result->retry_after_duration);
-        helper.setLongField(rttResult, "ts",                       result->ts);
-        helper.setIntField( rttResult, "rssi",                     result->rssi);
-        helper.setIntField( rttResult, "rssiSpread",               result->rssi_spread);
-        helper.setIntField( rttResult, "txRate",                   result->tx_rate.bitrate);
-        helper.setIntField( rttResult, "rxRate",                   result->rx_rate.bitrate);
-        helper.setLongField(rttResult, "rtt",                      result->rtt);
-        helper.setLongField(rttResult, "rttStandardDeviation",     result->rtt_sd);
-        helper.setIntField( rttResult, "distance",                 result->distance_mm / 10);
-        helper.setIntField( rttResult, "distanceStandardDeviation", result->distance_sd_mm / 10);
-        helper.setIntField( rttResult, "distanceSpread",           result->distance_spread_mm / 10);
-        helper.setIntField( rttResult, "burstDuration",             result->burst_duration);
-        helper.setIntField( rttResult, "negotiatedBurstNum",      result->negotiated_burst_num);
-
-        JNIObject<jobject> LCI = helper.createObject(
-                "android/net/wifi/RttManager$WifiInformationElement");
-        if (result->LCI != NULL && result->LCI->len > 0) {
-            helper.setByteField(LCI, "id", result->LCI->id);
-            JNIObject<jbyteArray> elements = helper.newByteArray(result->LCI->len);
-            jbyte *bytes = (jbyte *)&(result->LCI->data[0]);
-            helper.setByteArrayRegion(elements, 0, result->LCI->len, bytes);
-            helper.setObjectField(LCI, "data", "[B", elements);
-        } else {
-            helper.setByteField(LCI, "id", (byte)(0xff));
-        }
-        helper.setObjectField(rttResult, "LCI",
-            "Landroid/net/wifi/RttManager$WifiInformationElement;", LCI);
-
-        JNIObject<jobject> LCR = helper.createObject(
-                "android/net/wifi/RttManager$WifiInformationElement");
-        if (result->LCR != NULL && result->LCR->len > 0) {
-            helper.setByteField(LCR, "id",           result->LCR->id);
-            JNIObject<jbyteArray> elements = helper.newByteArray(result->LCI->len);
-            jbyte *bytes = (jbyte *)&(result->LCR->data[0]);
-            helper.setByteArrayRegion(elements, 0, result->LCI->len, bytes);
-            helper.setObjectField(LCR, "data", "[B", elements);
-        } else {
-            helper.setByteField(LCR, "id", (byte)(0xff));
-        }
-        helper.setObjectField(rttResult, "LCR",
-            "Landroid/net/wifi/RttManager$WifiInformationElement;", LCR);
-
-        helper.setObjectArrayElement(rttResults, i, rttResult);
-    }
-
-    helper.reportEvent(mCls, "onRttResults", "(I[Landroid/net/wifi/RttManager$RttResult;)V",
-        id, rttResults.get());
-}
-
-const int MaxRttConfigs = 16;
-
-static jboolean android_net_wifi_requestRange(
-        JNIEnv *env, jclass cls, jint iface, jint id, jobject params)  {
-
-    JNIHelper helper(env);
-
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    if (DBG) ALOGD("sending rtt request [%d] = %p", id, handle);
-    if (params == NULL) {
-        ALOGE("ranging params are empty");
-        return false;
-    }
-
-    wifi_rtt_config configs[MaxRttConfigs];
-    memset(&configs, 0, sizeof(configs));
-
-    int len = helper.getArrayLength((jobjectArray)params);
-    if (len > MaxRttConfigs) {
-        return false;
-    }
-
-    for (int i = 0; i < len; i++) {
-
-        JNIObject<jobject> param = helper.getObjectArrayElement((jobjectArray)params, i);
-        if (param == NULL) {
-            ALOGW("could not get element %d", i);
-            continue;
-        }
-
-        wifi_rtt_config &config = configs[i];
-
-        parseMacAddress(env, param, config.addr);
-        config.type = (wifi_rtt_type)helper.getIntField(param, "requestType");
-        config.peer = (rtt_peer_type)helper.getIntField(param, "deviceType");
-        config.channel.center_freq = helper.getIntField(param, "frequency");
-        config.channel.width = (wifi_channel_width) helper.getIntField(param, "channelWidth");
-        config.channel.center_freq0 = helper.getIntField(param, "centerFreq0");
-        config.channel.center_freq1 = helper.getIntField(param, "centerFreq1");
-
-        config.num_burst = helper.getIntField(param, "numberBurst");
-        config.burst_period = (unsigned) helper.getIntField(param, "interval");
-        config.num_frames_per_burst = (unsigned) helper.getIntField(param, "numSamplesPerBurst");
-        config.num_retries_per_rtt_frame = (unsigned) helper.getIntField(param,
-                "numRetriesPerMeasurementFrame");
-        config.num_retries_per_ftmr = (unsigned) helper.getIntField(param, "numRetriesPerFTMR");
-        config.LCI_request = helper.getBoolField(param, "LCIRequest") ? 1 : 0;
-        config.LCR_request = helper.getBoolField(param, "LCRRequest") ? 1 : 0;
-        config.burst_duration = (unsigned) helper.getIntField(param, "burstTimeout");
-        config.preamble = (wifi_rtt_preamble) helper.getIntField(param, "preamble");
-        config.bw = (wifi_rtt_bw) helper.getIntField(param, "bandwidth");
-    }
-
-    wifi_rtt_event_handler handler;
-    handler.on_rtt_results = &onRttResults;
-
-    return hal_fn.wifi_rtt_range_request(id, handle, len, configs, handler) == WIFI_SUCCESS;
-}
-
-static jboolean android_net_wifi_cancelRange(
-        JNIEnv *env, jclass cls, jint iface, jint id, jobject params)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    if (DBG) ALOGD("cancelling rtt request [%d] = %p", id, handle);
-
-    if (params == NULL) {
-        ALOGE("ranging params are empty");
-        return false;
-    }
-
-    mac_addr addrs[MaxRttConfigs];
-    memset(&addrs, 0, sizeof(addrs));
-
-    int len = helper.getArrayLength((jobjectArray)params);
-    if (len > MaxRttConfigs) {
-        return false;
-    }
-
-    for (int i = 0; i < len; i++) {
-
-        JNIObject<jobject> param = helper.getObjectArrayElement(params, i);
-        if (param == NULL) {
-            ALOGW("could not get element %d", i);
-            continue;
-        }
-
-        parseMacAddress(env, param, addrs[i]);
-    }
-
-    return hal_fn.wifi_rtt_range_cancel(id, handle, len, addrs) == WIFI_SUCCESS;
-}
-
-static jobject android_net_wifi_enableResponder(
-        JNIEnv *env, jclass cls, jint iface, jint id, jint timeout_seconds, jobject channel_hint) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    if (DBG) ALOGD("enabling responder request [%d] = %p", id, handle);
-    wifi_channel_info channel;
-    // Get channel information from HAL if it's not provided by caller.
-    if (channel_hint == NULL) {
-        wifi_rtt_responder responder_info_hint;
-        bool status = hal_fn.wifi_rtt_get_responder_info(handle, &responder_info_hint);
-        if (status != WIFI_SUCCESS) {
-            ALOGE("could not get available channel for responder");
-            return NULL;
-        }
-        channel = responder_info_hint.channel;
-    } else {
-        channel.center_freq = helper.getIntField(channel_hint, "mPrimaryFrequency");
-        channel.center_freq0 = helper.getIntField(channel_hint, "mCenterFrequency0");
-        channel.center_freq1 = helper.getIntField(channel_hint, "mCenterFrequency1");
-        channel.width = (wifi_channel_width)helper.getIntField(channel_hint, "mChannelWidth");
-    }
-
-    if (DBG) {
-        ALOGD("wifi_channel_width: %d, center_freq: %d, center_freq0: %d",
-              channel.width, channel.center_freq, channel.center_freq0);
-    }
-
-    wifi_rtt_responder responder_info_used;
-    bool status = hal_fn.wifi_enable_responder(id, handle, channel, timeout_seconds,
-            &responder_info_used);
-    if (status != WIFI_SUCCESS) {
-        ALOGE("enabling responder mode failed");
-        return NULL;
-    }
-    wifi_channel_info channel_used = responder_info_used.channel;
-    if (DBG) {
-        ALOGD("wifi_channel_width: %d, center_freq: %d, center_freq0: %d",
-              channel_used.width, channel_used.center_freq, channel_used.center_freq0);
-    }
-    JNIObject<jobject> responderConfig =
-        helper.createObject("android/net/wifi/RttManager$ResponderConfig");
-    if (responderConfig == NULL) return NULL;
-    helper.setIntField(responderConfig, "frequency", channel_used.center_freq);
-    helper.setIntField(responderConfig, "centerFreq0", channel_used.center_freq0);
-    helper.setIntField(responderConfig, "centerFreq1", channel_used.center_freq1);
-    helper.setIntField(responderConfig, "channelWidth", channel_used.width);
-    helper.setIntField(responderConfig, "preamble", responder_info_used.preamble);
-    return responderConfig.detach();
-}
-
-static jboolean android_net_wifi_disableResponder(
-        JNIEnv *env, jclass cls, jint iface, jint id)  {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    if (DBG) ALOGD("disabling responder request [%d] = %p", id, handle);
-    return hal_fn.wifi_disable_responder(id, handle) == WIFI_SUCCESS;
-}
-
-
-static jboolean android_net_wifi_setScanningMacOui(JNIEnv *env, jclass cls,
-        jint iface, jbyteArray param)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("setting scan oui %p", handle);
-
-    static const unsigned oui_len = 3;          /* OUI is upper 3 bytes of mac_address */
-    int len = helper.getArrayLength(param);
-    if (len != oui_len) {
-        ALOGE("invalid oui length %d", len);
-        return false;
-    }
-
-    ScopedBytesRW paramBytes(env, param);
-    jbyte* bytes = paramBytes.get();
-    if (bytes == NULL) {
-        ALOGE("failed to get setScanningMacOui param array");
-        return false;
-    }
-
-    return hal_fn.wifi_set_scanning_mac_oui(handle, (byte *)bytes) == WIFI_SUCCESS;
-}
-
-static jboolean android_net_wifi_is_get_channels_for_band_supported(JNIEnv *env, jclass cls){
-    return (hal_fn.wifi_get_valid_channels == wifi_get_valid_channels_stub);
-}
-
-static jintArray android_net_wifi_getValidChannels(JNIEnv *env, jclass cls,
-        jint iface, jint band)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGV("getting valid channels %p", handle);
-
-    static const int MaxChannels = 64;
-    wifi_channel channels[64];
-    int num_channels = 0;
-    wifi_error result = hal_fn.wifi_get_valid_channels(handle, band, MaxChannels,
-            channels, &num_channels);
-
-    if (result == WIFI_SUCCESS) {
-        JNIObject<jintArray> channelArray = helper.newIntArray(num_channels);
-        if (channelArray == NULL) {
-            ALOGE("failed to allocate channel list, num_channels=%d", num_channels);
-            return NULL;
-        }
-
-        helper.setIntArrayRegion(channelArray, 0, num_channels, channels);
-        return channelArray.detach();
-    } else {
-        ALOGE("failed to get channel list : %d", result);
-        return NULL;
-    }
-}
-
-static jboolean android_net_wifi_setDfsFlag(JNIEnv *env, jclass cls, jint iface, jboolean dfs) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("setting dfs flag to %s, %p", dfs ? "true" : "false", handle);
-
-    u32 nodfs = dfs ? 0 : 1;
-    wifi_error result = hal_fn.wifi_set_nodfs_flag(handle, nodfs);
-    return result == WIFI_SUCCESS;
-}
-
-static jobject android_net_wifi_get_rtt_capabilities(JNIEnv *env, jclass cls, jint iface) {
-
-    JNIHelper helper(env);
-    wifi_rtt_capabilities rtt_capabilities;
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    wifi_error ret = hal_fn.wifi_get_rtt_capabilities(handle, &rtt_capabilities);
-
-    if(WIFI_SUCCESS == ret) {
-         JNIObject<jobject> capabilities = helper.createObject(
-                "android/net/wifi/RttManager$RttCapabilities");
-         helper.setBooleanField(capabilities, "oneSidedRttSupported",
-                 rtt_capabilities.rtt_one_sided_supported == 1);
-         helper.setBooleanField(capabilities, "twoSided11McRttSupported",
-                 rtt_capabilities.rtt_ftm_supported == 1);
-         helper.setBooleanField(capabilities, "lciSupported",
-                 rtt_capabilities.lci_support);
-         helper.setBooleanField(capabilities, "lcrSupported",
-                 rtt_capabilities.lcr_support);
-         helper.setIntField(capabilities, "preambleSupported",
-                 rtt_capabilities.preamble_support);
-         helper.setIntField(capabilities, "bwSupported",
-                 rtt_capabilities.bw_support);
-         helper.setBooleanField(capabilities, "responderSupported",
-                 rtt_capabilities.responder_supported == 1);
-         if (DBG) {
-             ALOGD("One side RTT is %s", rtt_capabilities.rtt_one_sided_supported == 1 ?
-                "supported" : "not supported");
-             ALOGD("Two side RTT is %s", rtt_capabilities.rtt_ftm_supported == 1 ?
-                "supported" : "not supported");
-             ALOGD("LCR is %s", rtt_capabilities.lcr_support == 1 ? "supported" : "not supported");
-             ALOGD("LCI is %s", rtt_capabilities.lci_support == 1 ? "supported" : "not supported");
-             ALOGD("Supported preamble is %d", rtt_capabilities.preamble_support);
-             ALOGD("Supported bandwidth is %d", rtt_capabilities.bw_support);
-             ALOGD("Sta responder is %s",
-                 rtt_capabilities.responder_supported == 1 ? "supported" : "not supported");
-         }
-         return capabilities.detach();
-    } else {
-        return NULL;
-    }
-}
-
-static jobject android_net_wifi_get_apf_capabilities(JNIEnv *env, jclass cls,
-        jint iface) {
-
-    JNIHelper helper(env);
-    u32 version = 0, max_len = 0;
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    wifi_error ret = hal_fn.wifi_get_packet_filter_capabilities(handle, &version, &max_len);
-
-    if (WIFI_SUCCESS == ret) {
-        // Cannot just use createObject() because members are final and initializer values must be
-        // passed via ApfCapabilities().
-        JNIObject<jclass> apf_cls(helper, env->FindClass("android/net/apf/ApfCapabilities"));
-        if (apf_cls == NULL) {
-            ALOGE("Error in finding class android/net/apf/ApfCapabilities");
-            return NULL;
-        }
-        jmethodID constructor = env->GetMethodID(apf_cls, "<init>", "(III)V");
-        if (constructor == 0) {
-            ALOGE("Error in constructor ID for android/net/apf/ApfCapabilities");
-            return NULL;
-        }
-        JNIObject<jobject> capabilities(helper, env->NewObject(apf_cls, constructor, version,
-                max_len, ARPHRD_ETHER));
-        if (capabilities == NULL) {
-            ALOGE("Could not create new object of android/net/apf/ApfCapabilities");
-            return NULL;
-        }
-        ALOGD("APF version supported: %d", version);
-        ALOGD("Maximum APF program size: %d", max_len);
-        return capabilities.detach();
-    } else {
-        return NULL;
-    }
-}
-
-static jboolean android_net_wifi_install_packet_filter(JNIEnv *env, jclass cls, jint iface,
-        jbyteArray jfilter) {
-
-    JNIHelper helper(env);
-    const u8* filter = (uint8_t*)env->GetByteArrayElements(jfilter, NULL);
-    const u32 filter_len = env->GetArrayLength(jfilter);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    wifi_error ret = hal_fn.wifi_set_packet_filter(handle, filter, filter_len);
-    env->ReleaseByteArrayElements(jfilter, (jbyte*)filter, JNI_ABORT);
-    return WIFI_SUCCESS == ret;
-}
-
-static jboolean android_net_wifi_set_Country_Code_Hal(JNIEnv *env,jclass cls, jint iface,
-        jstring country_code) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    ScopedUtfChars chars(env, country_code);
-    const char *country = chars.c_str();
-
-    ALOGD("set country code: %s", country);
-    wifi_error res = hal_fn.wifi_set_country_code(handle, country);
-    return res == WIFI_SUCCESS;
-}
-
-static jboolean android_net_wifi_enable_disable_tdls(JNIEnv *env,jclass cls, jint iface,
-        jboolean enable, jstring addr) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    mac_addr address;
-    parseMacAddress(env, addr, address);
-    wifi_tdls_handler tdls_handler;
-    //tdls_handler.on_tdls_state_changed = &on_tdls_state_changed;
-
-    if(enable) {
-        return (hal_fn.wifi_enable_tdls(handle, address, NULL, tdls_handler) == WIFI_SUCCESS);
-    } else {
-        return (hal_fn.wifi_disable_tdls(handle, address) == WIFI_SUCCESS);
-    }
-}
-
-static void on_tdls_state_changed(mac_addr addr, wifi_tdls_status status) {
-
-    JNIHelper helper(mVM);
-
-    ALOGD("on_tdls_state_changed is called: vm = %p, obj = %p", mVM, mCls);
-
-    char mac[32];
-    sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4],
-            addr[5]);
-
-    JNIObject<jstring> mac_address = helper.newStringUTF(mac);
-    helper.reportEvent(mCls, "onTdlsStatus", "(Ljava/lang/StringII;)V",
-        mac_address.get(), status.state, status.reason);
-
-}
-
-static jobject android_net_wifi_get_tdls_status(JNIEnv *env,jclass cls, jint iface,jstring addr) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    mac_addr address;
-    parseMacAddress(env, addr, address);
-
-    wifi_tdls_status status;
-
-    wifi_error ret;
-    ret = hal_fn.wifi_get_tdls_status(handle, address, &status );
-
-    if (ret != WIFI_SUCCESS) {
-        return NULL;
-    } else {
-        JNIObject<jobject> tdls_status = helper.createObject(
-                "com/android/server/wifi/WifiNative$TdlsStatus");
-        helper.setIntField(tdls_status, "channel", status.channel);
-        helper.setIntField(tdls_status, "global_operating_class", status.global_operating_class);
-        helper.setIntField(tdls_status, "state", status.state);
-        helper.setIntField(tdls_status, "reason", status.reason);
-        return tdls_status.detach();
-    }
-}
-
-static jobject android_net_wifi_get_tdls_capabilities(JNIEnv *env, jclass cls, jint iface) {
-
-    JNIHelper helper(env);
-    wifi_tdls_capabilities tdls_capabilities;
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    wifi_error ret = hal_fn.wifi_get_tdls_capabilities(handle, &tdls_capabilities);
-
-    if (WIFI_SUCCESS == ret) {
-         JNIObject<jobject> capabilities = helper.createObject(
-                 "com/android/server/wifi/WifiNative$TdlsCapabilities");
-         helper.setIntField(capabilities, "maxConcurrentTdlsSessionNumber",
-                 tdls_capabilities.max_concurrent_tdls_session_num);
-         helper.setBooleanField(capabilities, "isGlobalTdlsSupported",
-                 tdls_capabilities.is_global_tdls_supported == 1);
-         helper.setBooleanField(capabilities, "isPerMacTdlsSupported",
-                 tdls_capabilities.is_per_mac_tdls_supported == 1);
-         helper.setBooleanField(capabilities, "isOffChannelTdlsSupported",
-                 tdls_capabilities.is_off_channel_tdls_supported);
-
-         ALOGD("TDLS Max Concurrent Tdls Session Number is: %d",
-                 tdls_capabilities.max_concurrent_tdls_session_num);
-         ALOGD("Global Tdls is: %s", tdls_capabilities.is_global_tdls_supported == 1 ? "support" :
-                 "not support");
-         ALOGD("Per Mac Tdls is: %s", tdls_capabilities.is_per_mac_tdls_supported == 1 ? "support" :
-                 "not support");
-         ALOGD("Off Channel Tdls is: %s", tdls_capabilities.is_off_channel_tdls_supported == 1 ?
-                 "support" : "not support");
-
-         return capabilities.detach();
-    } else {
-        return NULL;
-    }
-}
-
-// ----------------------------------------------------------------------------
-// Debug framework
-// ----------------------------------------------------------------------------
-static jint android_net_wifi_get_supported_logger_feature(JNIEnv *env, jclass cls, jint iface){
-    //Not implemented yet
-    return -1;
-}
-
-static jobject android_net_wifi_get_driver_version(JNIEnv *env, jclass cls, jint iface) {
-     //Need to be fixed. The memory should be allocated from lower layer
-    //char *buffer = NULL;
-    JNIHelper helper(env);
-    int buffer_length =  256;
-    char *buffer = (char *)malloc(buffer_length);
-    if (!buffer) return NULL;
-    memset(buffer, 0, buffer_length);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    ALOGD("android_net_wifi_get_driver_version = %p", handle);
-
-    if (handle == 0) {
-        free(buffer);
-        return NULL;
-    }
-
-    wifi_error result = hal_fn.wifi_get_driver_version(handle, buffer, buffer_length);
-
-    if (result == WIFI_SUCCESS) {
-        ALOGD("buffer is %p, length is %d", buffer, buffer_length);
-        JNIObject<jstring> driver_version = helper.newStringUTF(buffer);
-        free(buffer);
-        return driver_version.detach();
-    } else {
-        ALOGE("Fail to get driver version");
-        free(buffer);
-        return NULL;
-    }
-}
-
-static jobject android_net_wifi_get_firmware_version(JNIEnv *env, jclass cls, jint iface) {
-
-    //char *buffer = NULL;
-    JNIHelper helper(env);
-    int buffer_length = 256;
-    char *buffer = (char *)malloc(buffer_length);
-    if (!buffer) return NULL;
-    memset(buffer, 0, buffer_length);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    ALOGD("android_net_wifi_get_firmware_version = %p", handle);
-
-    if (handle == 0) {
-        free(buffer);
-        return NULL;
-    }
-
-    wifi_error result = hal_fn.wifi_get_firmware_version(handle, buffer, buffer_length);
-
-    if (result == WIFI_SUCCESS) {
-        ALOGD("buffer is %p, length is %d", buffer, buffer_length);
-        JNIObject<jstring> firmware_version = helper.newStringUTF(buffer);
-        free(buffer);
-        return firmware_version.detach();
-    } else {
-        ALOGE("Fail to get Firmware version");
-        free(buffer);
-        return NULL;
-    }
-}
-
-static jobject android_net_wifi_get_ring_buffer_status (JNIEnv *env, jclass cls, jint iface) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    ALOGD("android_net_wifi_get_ring_buffer_status = %p", handle);
-
-    if (handle == 0) {
-        return NULL;
-    }
-
-    //wifi_ring_buffer_status *status = NULL;
-    u32 num_rings = 10;
-    wifi_ring_buffer_status *status =
-        (wifi_ring_buffer_status *)malloc(sizeof(wifi_ring_buffer_status) * num_rings);
-    if (!status) return NULL;
-    memset(status, 0, sizeof(wifi_ring_buffer_status) * num_rings);
-    wifi_error result = hal_fn.wifi_get_ring_buffers_status(handle, &num_rings, status);
-    if (result == WIFI_SUCCESS) {
-        ALOGD("status is %p, number is %d", status, num_rings);
-
-        JNIObject<jobjectArray> ringBuffersStatus = helper.newObjectArray(
-            num_rings, "com/android/server/wifi/WifiNative$RingBufferStatus", NULL);
-
-        wifi_ring_buffer_status *tmp = status;
-
-        for(u32 i = 0; i < num_rings; i++, tmp++) {
-
-            JNIObject<jobject> ringStatus = helper.createObject(
-                    "com/android/server/wifi/WifiNative$RingBufferStatus");
-
-            if (ringStatus == NULL) {
-                ALOGE("Error in creating ringBufferStatus");
-                free(status);
-                return NULL;
-            }
-
-            char name[32];
-            for(int j = 0; j < 32; j++) {
-                name[j] = tmp->name[j];
-            }
-
-            helper.setStringField(ringStatus, "name", name);
-            helper.setIntField(ringStatus, "flag", tmp->flags);
-            helper.setIntField(ringStatus, "ringBufferId", tmp->ring_id);
-            helper.setIntField(ringStatus, "ringBufferByteSize", tmp->ring_buffer_byte_size);
-            helper.setIntField(ringStatus, "verboseLevel", tmp->verbose_level);
-            helper.setIntField(ringStatus, "writtenBytes", tmp->written_bytes);
-            helper.setIntField(ringStatus, "readBytes", tmp->read_bytes);
-            helper.setIntField(ringStatus, "writtenRecords", tmp->written_records);
-
-            helper.setObjectArrayElement(ringBuffersStatus, i, ringStatus);
-        }
-
-        free(status);
-        return ringBuffersStatus.detach();
-    } else {
-        free(status);
-        return NULL;
-    }
-}
-
-static void on_ring_buffer_data(char *ring_name, char *buffer, int buffer_size,
-        wifi_ring_buffer_status *status) {
-
-    if (!ring_name || !buffer || !status ||
-            (unsigned int)buffer_size <= sizeof(wifi_ring_buffer_entry)) {
-        ALOGE("Error input for on_ring_buffer_data!");
-        return;
-    }
-
-
-    JNIHelper helper(mVM);
-    /* ALOGD("on_ring_buffer_data called, vm = %p, obj = %p, env = %p buffer size = %d", mVM,
-            mCls, env, buffer_size); */
-
-    JNIObject<jobject> ringStatus = helper.createObject(
-                    "com/android/server/wifi/WifiNative$RingBufferStatus");
-    if (status == NULL) {
-        ALOGE("Error in creating ringBufferStatus");
-        return;
-    }
-
-    helper.setStringField(ringStatus, "name", ring_name);
-    helper.setIntField(ringStatus, "flag", status->flags);
-    helper.setIntField(ringStatus, "ringBufferId", status->ring_id);
-    helper.setIntField(ringStatus, "ringBufferByteSize", status->ring_buffer_byte_size);
-    helper.setIntField(ringStatus, "verboseLevel", status->verbose_level);
-    helper.setIntField(ringStatus, "writtenBytes", status->written_bytes);
-    helper.setIntField(ringStatus, "readBytes", status->read_bytes);
-    helper.setIntField(ringStatus, "writtenRecords", status->written_records);
-
-    JNIObject<jbyteArray> bytes = helper.newByteArray(buffer_size);
-    helper.setByteArrayRegion(bytes, 0, buffer_size, (jbyte*)buffer);
-
-    helper.reportEvent(mCls,"onRingBufferData",
-            "(Lcom/android/server/wifi/WifiNative$RingBufferStatus;[B)V",
-            ringStatus.get(), bytes.get());
-}
-
-static void on_alert_data(wifi_request_id id, char *buffer, int buffer_size, int err_code){
-
-    JNIHelper helper(mVM);
-    ALOGD("on_alert_data called, vm = %p, obj = %p, buffer_size = %d, error code = %d"
-            , mVM, mCls, buffer_size, err_code);
-
-    if (buffer_size > 0) {
-        JNIObject<jbyteArray> records = helper.newByteArray(buffer_size);
-        jbyte *bytes = (jbyte *) buffer;
-        helper.setByteArrayRegion(records, 0,buffer_size, bytes);
-        helper.reportEvent(mCls,"onWifiAlert","([BI)V", records.get(), err_code);
-    } else {
-        helper.reportEvent(mCls,"onWifiAlert","([BI)V", NULL, err_code);
-    }
-}
-
-
-static jboolean android_net_wifi_start_logging_ring_buffer(JNIEnv *env, jclass cls, jint iface,
-        jint verbose_level,jint flags, jint max_interval,jint min_data_size, jstring ring_name) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    ALOGD("android_net_wifi_start_logging_ring_buffer = %p", handle);
-
-    if (handle == 0) {
-        return false;
-    }
-
-    ScopedUtfChars chars(env, ring_name);
-    const char* ring_name_const_char = chars.c_str();
-    int ret = hal_fn.wifi_start_logging(handle, verbose_level,
-            flags, max_interval, min_data_size, const_cast<char *>(ring_name_const_char));
-
-    if (ret != WIFI_SUCCESS) {
-        ALOGE("Fail to start logging for ring %s", ring_name_const_char);
-    } else {
-        ALOGD("start logging for ring %s", ring_name_const_char);
-    }
-
-    return ret == WIFI_SUCCESS;
-}
-
-static jboolean android_net_wifi_get_ring_buffer_data(JNIEnv *env, jclass cls, jint iface,
-        jstring ring_name) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    // ALOGD("android_net_wifi_get_ring_buffer_data = %p", handle);
-
-    ScopedUtfChars chars(env, ring_name);
-    const char* ring_name_const_char = chars.c_str();
-    int result = hal_fn.wifi_get_ring_data(handle, const_cast<char *>(ring_name_const_char));
-    return result == WIFI_SUCCESS;
-}
-
-
-static void on_firmware_memory_dump(char *buffer, int buffer_size) {
-
-    JNIHelper helper(mVM);
-    /* ALOGD("on_firmware_memory_dump called, vm = %p, obj = %p, env = %p buffer_size = %d"
-            , mVM, mCls, env, buffer_size); */
-
-    if (buffer_size > 0) {
-        JNIObject<jbyteArray> dump = helper.newByteArray(buffer_size);
-        jbyte *bytes = (jbyte *) (buffer);
-        helper.setByteArrayRegion(dump, 0, buffer_size, bytes);
-        helper.reportEvent(mCls,"onWifiFwMemoryAvailable","([B)V", dump.get());
-    }
-}
-
-static jboolean android_net_wifi_get_fw_memory_dump(JNIEnv *env, jclass cls, jint iface){
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    // ALOGD("android_net_wifi_get_fw_memory_dump = %p", handle);
-
-    if (handle == NULL) {
-        ALOGE("Can not get wifi_interface_handle");
-        return false;
-    }
-
-    wifi_firmware_memory_dump_handler fw_dump_handle;
-    fw_dump_handle.on_firmware_memory_dump = on_firmware_memory_dump;
-    int result = hal_fn.wifi_get_firmware_memory_dump(handle, fw_dump_handle);
-    return result == WIFI_SUCCESS;
-
-}
-
-std::vector<jbyte>* driver_state_dump_buffer_for_callback = nullptr;
-
-static void on_driver_state_dump(char *buffer, int buffer_size);
-static wifi_driver_memory_dump_callbacks driver_state_dump_callbacks = {
-    on_driver_state_dump
-};
-
-static void on_driver_state_dump(char *buffer, int buffer_size) {
-
-    if (!driver_state_dump_buffer_for_callback) {
-        ALOGE("Unexpected call from HAL implementation, into %s", __func__);
-        return;
-    }
-
-    if (buffer_size > 0) {
-        driver_state_dump_buffer_for_callback->insert(
-            driver_state_dump_buffer_for_callback->end(), buffer, buffer + buffer_size);
-    }
-}
-
-// TODO(quiche): Add unit tests. b/28072392
-static jbyteArray android_net_wifi_get_driver_state_dump(JNIEnv *env, jclass cls, jint iface){
-
-    JNIHelper helper(env);
-    wifi_interface_handle interface_handle = getIfaceHandle(helper, cls, iface);
-
-    if (!interface_handle) {
-        return nullptr;
-    }
-
-    int result;
-    std::vector<jbyte> state_dump_buffer_local;
-    driver_state_dump_buffer_for_callback = &state_dump_buffer_local;
-    result = hal_fn.wifi_get_driver_memory_dump(interface_handle, driver_state_dump_callbacks);
-    driver_state_dump_buffer_for_callback = nullptr;
-
-    if (result != WIFI_SUCCESS) {
-        ALOGW("HAL's wifi_get_driver_memory_dump returned %d", result);
-        return nullptr;
-    }
-
-    if (state_dump_buffer_local.empty()) {
-        ALOGW("HAL's wifi_get_driver_memory_dump provided zero bytes");
-        return nullptr;
-    }
-
-    const size_t dump_size = state_dump_buffer_local.size();
-    JNIObject<jbyteArray> driver_dump_java = helper.newByteArray(dump_size);
-    if (!driver_dump_java)  {
-        ALOGW("Failed to allocate Java buffer for driver state dump");
-        return nullptr;
-    }
-
-    helper.setByteArrayRegion(driver_dump_java, 0, dump_size, state_dump_buffer_local.data());
-    return driver_dump_java.detach();
-}
-
-static jboolean android_net_wifi_set_log_handler(JNIEnv *env, jclass cls, jint iface, jint id) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("android_net_wifi_set_log_handler = %p", handle);
-
-    //initialize the handler on first time
-    wifi_ring_buffer_data_handler handler;
-    handler.on_ring_buffer_data = &on_ring_buffer_data;
-    int result = hal_fn.wifi_set_log_handler(id, handle, handler);
-    if (result != WIFI_SUCCESS) {
-        ALOGE("Fail to set logging handler");
-        return false;
-    }
-
-    //set alter handler This will start alert too
-    wifi_alert_handler alert_handler;
-    alert_handler.on_alert = &on_alert_data;
-    result = hal_fn.wifi_set_alert_handler(id, handle, alert_handler);
-    if (result != WIFI_SUCCESS) {
-        ALOGE(" Fail to set alert handler");
-        return false;
-    }
-
-    return true;
-}
-
-static jboolean android_net_wifi_reset_log_handler(JNIEnv *env, jclass cls, jint iface, jint id) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-
-    //reset alter handler
-    ALOGD("android_net_wifi_reset_alert_handler = %p", handle);
-    int result = hal_fn.wifi_reset_alert_handler(id, handle);
-    if (result != WIFI_SUCCESS) {
-        ALOGE(" Fail to reset alert handler");
-        return false;
-    }
-
-    //reset log handler
-    ALOGD("android_net_wifi_reset_log_handler = %p", handle);
-    result = hal_fn.wifi_reset_log_handler(id, handle);
-    if (result != WIFI_SUCCESS) {
-        ALOGE("Fail to reset logging handler");
-        return false;
-    }
-
-    return true;
-}
-
-static jint android_net_wifi_start_pkt_fate_monitoring(JNIEnv *env, jclass cls, jint iface) {
-
-    JNIHelper helper(env);
-    return hal_fn.wifi_start_pkt_fate_monitoring(
-        getIfaceHandle(helper, cls, iface));
-}
-
-// Helper for make_default_fate().
-template<typename T> void set_to_max(T* value) {
-    if (!value) {
-        return;
-    }
-    *value = std::numeric_limits<T>::max();
-}
-
-// make_default_fate() has two purposes:
-// 1) Minimize the chances of data leakage. In case the HAL gives us an overlong long |frame_len|,
-//    for example, we want to return zeros, rather than other data from this process.
-// 2) Make it obvious when the HAL doesn't set a field. We accomplish this by setting fields
-//    to "impossible" values, where possible.
-// Normally, such work would be done in a ctor. However, doing so would make the HAL API
-// incompatible with C. So we use a free-standing function instead.
-//
-// TODO(quiche): Add unit test for this function. b/27726696
-template<typename FateReportT> FateReportT make_default_fate() {
-
-    FateReportT fate_report;
-    set_to_max(&fate_report.fate);
-    std::fill(std::begin(fate_report.md5_prefix), std::end(fate_report.md5_prefix), 0);
-    set_to_max(&fate_report.frame_inf.payload_type);
-    fate_report.frame_inf.frame_len = 0;
-    fate_report.frame_inf.driver_timestamp_usec = 0;
-    fate_report.frame_inf.firmware_timestamp_usec = 0;
-    std::fill(std::begin(fate_report.frame_inf.frame_content.ieee_80211_mgmt_bytes),
-        std::end(fate_report.frame_inf.frame_content.ieee_80211_mgmt_bytes), 0);
-    return fate_report;
-}
-
-// TODO(quiche): Add unit test for this function. b/27726696
-template<typename FateReportT, typename HalFateFetcherT> wifi_error get_pkt_fates(
-    HalFateFetcherT fate_fetcher_func, const char *java_fate_type,
-    JNIEnv *env, jclass cls, jint iface, jobjectArray reports) {
-
-    JNIHelper helper(env);
-    const size_t n_reports_wanted =
-        std::min(helper.getArrayLength(reports), MAX_FATE_LOG_LEN);
-
-    std::vector<FateReportT> report_bufs(n_reports_wanted, make_default_fate<FateReportT>());
-    size_t n_reports_provided = 0;
-    wifi_error result = fate_fetcher_func(
-        getIfaceHandle(helper, cls, iface),
-        report_bufs.data(),
-        n_reports_wanted,
-        &n_reports_provided);
-    if (result != WIFI_SUCCESS) {
-        return result;
-    }
-
-    if (n_reports_provided > n_reports_wanted) {
-        LOG_ALWAYS_FATAL(
-            "HAL data exceeds request; memory may be corrupt (provided: %zu, requested: %zu)",
-            n_reports_provided, n_reports_wanted);
-    }
-
-    for (size_t i = 0; i < n_reports_provided; ++i) {
-        const FateReportT& report(report_bufs[i]);
-
-        const char *frame_bytes_native = nullptr;
-        size_t max_frame_len;
-        switch (report.frame_inf.payload_type) {
-            case FRAME_TYPE_UNKNOWN:
-            case FRAME_TYPE_ETHERNET_II:
-                max_frame_len = MAX_FRAME_LEN_ETHERNET;
-                frame_bytes_native = report.frame_inf.frame_content.ethernet_ii_bytes;
-                break;
-            case FRAME_TYPE_80211_MGMT:
-                max_frame_len = MAX_FRAME_LEN_80211_MGMT;
-                frame_bytes_native = report.frame_inf.frame_content.ieee_80211_mgmt_bytes;
-                break;
-            default:
-                max_frame_len = 0;
-                frame_bytes_native = 0;
-        }
-
-        size_t copy_len = report.frame_inf.frame_len;
-        if (copy_len > max_frame_len) {
-            ALOGW("Overly long frame (len: %zu, max: %zu)", copy_len, max_frame_len);
-            copy_len = max_frame_len;
-        }
-
-        JNIObject<jbyteArray> frame_bytes_java = helper.newByteArray(copy_len);
-        if (frame_bytes_java.isNull()) {
-            ALOGE("Failed to allocate frame data buffer");
-            return WIFI_ERROR_OUT_OF_MEMORY;
-        }
-        helper.setByteArrayRegion(frame_bytes_java, 0, copy_len,
-            reinterpret_cast<const jbyte *>(frame_bytes_native));
-
-        JNIObject<jobject> fate_report = helper.createObjectWithArgs(
-            java_fate_type,
-            "(BJB[B)V",  // byte, long, byte, byte array
-            static_cast<jbyte>(report.fate),
-            static_cast<jlong>(report.frame_inf.driver_timestamp_usec),
-            static_cast<jbyte>(report.frame_inf.payload_type),
-            frame_bytes_java.get());
-        if (fate_report.isNull()) {
-            ALOGE("Failed to create %s", java_fate_type);
-            return WIFI_ERROR_OUT_OF_MEMORY;
-        }
-        helper.setObjectArrayElement(reports, i, fate_report);
-    }
-
-    return result;
-}
-
-static jint android_net_wifi_get_tx_pkt_fates(JNIEnv *env, jclass cls, jint iface,
-    jobjectArray reports) {
-
-    return get_pkt_fates<wifi_tx_report>(
-        hal_fn.wifi_get_tx_pkt_fates, "com/android/server/wifi/WifiNative$TxFateReport",
-        env, cls, iface, reports);
-}
-
-static jint android_net_wifi_get_rx_pkt_fates(JNIEnv *env, jclass cls, jint iface,
-    jobjectArray reports) {
-
-    return get_pkt_fates<wifi_rx_report>(
-        hal_fn.wifi_get_rx_pkt_fates, "com/android/server/wifi/WifiNative$RxFateReport",
-        env, cls, iface, reports);
-}
-
-// ----------------------------------------------------------------------------
-// ePno framework
-// ----------------------------------------------------------------------------
-
-
-static void onPnoNetworkFound(wifi_request_id id,
-                                          unsigned num_results, wifi_scan_result *results) {
-    JNIHelper helper(mVM);
-    ALOGD("onPnoNetworkFound called, vm = %p, obj = %p, num_results %u", mVM, mCls, num_results);
-
-    if (results == NULL || num_results == 0) {
-       ALOGE("onPnoNetworkFound: Error no results");
-       return;
-    }
-
-    JNIObject<jobjectArray> scanResults = helper.newObjectArray(num_results,
-            "android/net/wifi/ScanResult", NULL);
-    if (scanResults == NULL) {
-        ALOGE("onpnoNetworkFound: Error in allocating scanResults array");
-        return;
-    }
-
-    JNIObject<jintArray> beaconCaps = helper.newIntArray(num_results);
-    if (beaconCaps == NULL) {
-        ALOGE("onpnoNetworkFound: Error in allocating beaconCaps array");
-        return;
-    }
-
-    for (unsigned i=0; i<num_results; i++) {
-
-        JNIObject<jobject> scanResult = createScanResult(helper, &results[i], true);
-        if (scanResult == NULL) {
-            ALOGE("Error in creating scan result");
-            return;
-        }
-
-        helper.setObjectArrayElement(scanResults, i, scanResult);
-        helper.setIntArrayRegion(beaconCaps, i, 1, (jint *)&(results[i].capability));
-
-        if (DBG) {
-            ALOGD("ScanResult: IE length %d, i %u, <%s> rssi=%d %02x:%02x:%02x:%02x:%02x:%02x",
-                    results->ie_length, i, results[i].ssid, results[i].rssi,
-                    results[i].bssid[0], results[i].bssid[1],results[i].bssid[2],
-                    results[i].bssid[3], results[i].bssid[4], results[i].bssid[5]);
-        }
-    }
-
-    helper.reportEvent(mCls, "onPnoNetworkFound", "(I[Landroid/net/wifi/ScanResult;[I)V", id,
-               scanResults.get(), beaconCaps.get());
-}
-
-static jboolean android_net_wifi_setPnoListNative(
-        JNIEnv *env, jclass cls, jint iface, jint id, jobject settings)  {
-
-    JNIHelper helper(env);
-    wifi_epno_handler handler;
-    handler.on_network_found = &onPnoNetworkFound;
-
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("configure ePno list request [%d] = %p", id, handle);
-
-    if (settings == NULL) {
-        return false;
-    }
-
-    JNIObject<jobjectArray> list = helper.getArrayField(settings, "networkList",
-            "[Lcom/android/server/wifi/WifiNative$PnoNetwork;");
-    if (list == NULL) {
-        return false;
-    }
-
-    size_t len = helper.getArrayLength(list);
-    if (len > (size_t)MAX_EPNO_NETWORKS) {
-        return false;
-    }
-
-    wifi_epno_params params;
-    memset(&params, 0, sizeof(params));
-
-    for (unsigned int i = 0; i < len; i++) {
-
-        JNIObject<jobject> pno_net = helper.getObjectArrayElement(list, i);
-        if (pno_net == NULL) {
-            ALOGE("setPnoListNative: could not get element %d", i);
-            continue;
-        }
-
-        JNIObject<jstring> sssid = helper.getStringField(pno_net, "ssid");
-        if (sssid == NULL) {
-              ALOGE("Error setPnoListNative: getting ssid field");
-              return false;
-        }
-
-        ScopedUtfChars chars(env, (jstring)sssid.get());
-        const char *ssid = chars.c_str();
-        if (ssid == NULL) {
-             ALOGE("Error setPnoListNative: getting ssid");
-             return false;
-        }
-        int ssid_len = strnlen((const char*)ssid, 33);
-        if (ssid_len > 32) {
-           ALOGE("Error setPnoListNative: long ssid %zu", strnlen((const char*)ssid, 256));
-           return false;
-        }
-
-        if (ssid_len > 1 && ssid[0] == '"' && ssid[ssid_len-1] == '"')
-        {
-            // strip leading and trailing '"'
-            ssid++;
-            ssid_len-=2;
-        }
-        if (ssid_len == 0) {
-            ALOGE("Error setPnoListNative: zero length ssid, skip it");
-            continue;
-        }
-        memcpy(params.networks[i].ssid, ssid, ssid_len);
-
-        params.networks[i].auth_bit_field = helper.getByteField(pno_net, "auth_bit_field");
-        params.networks[i].flags = helper.getByteField(pno_net, "flags");
-        ALOGD(" setPnoListNative: idx %u auth %x flags %x [%s]", i,
-                params.networks[i].auth_bit_field, params.networks[i].flags,
-                params.networks[i].ssid);
-    }
-    params.min5GHz_rssi = helper.getIntField(settings, "min5GHzRssi");
-    params.min24GHz_rssi = helper.getIntField(settings, "min24GHzRssi");
-    params.initial_score_max = helper.getIntField(settings, "initialScoreMax");
-    params.current_connection_bonus = helper.getIntField(settings, "currentConnectionBonus");
-    params.same_network_bonus = helper.getIntField(settings, "sameNetworkBonus");
-    params.secure_bonus = helper.getIntField(settings, "secureBonus");
-    params.band5GHz_bonus = helper.getIntField(settings, "band5GHzBonus");
-    params.num_networks = len;
-
-    int result = hal_fn.wifi_set_epno_list(id, handle, &params, handler);
-    ALOGD(" setPnoListNative: result %d", result);
-
-    return result >= 0;
-}
-
-static jboolean android_net_wifi_resetPnoListNative(
-        JNIEnv *env, jclass cls, jint iface, jint id)  {
-
-    JNIHelper helper(env);
-
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("reset ePno list request [%d] = %p", id, handle);
-
-    // stop pno
-    int result = hal_fn.wifi_reset_epno_list(id, handle);
-    ALOGD(" ressetPnoListNative: result = %d", result);
-    return result >= 0;
-}
-
-static jboolean android_net_wifi_setBssidBlacklist(
-        JNIEnv *env, jclass cls, jint iface, jint id, jobject list)  {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("configure BSSID black list request [%d] = %p", id, handle);
-
-    wifi_bssid_params params;
-    memset(&params, 0, sizeof(params));
-
-    if (list != NULL) {
-        size_t len = helper.getArrayLength((jobjectArray)list);
-        if (len > (size_t)MAX_BLACKLIST_BSSID) {
-            return false;
-        }
-        for (unsigned int i = 0; i < len; i++) {
-
-            JNIObject<jobject> jbssid = helper.getObjectArrayElement(list, i);
-            if (jbssid == NULL) {
-                ALOGE("configure BSSID blacklist: could not get element %d", i);
-                continue;
-            }
-
-            ScopedUtfChars chars(env, (jstring)jbssid.get());
-            const char *bssid = chars.c_str();
-            if (bssid == NULL) {
-                ALOGE("Error getting bssid");
-                return false;
-            }
-
-            mac_addr addr;
-            parseMacAddress(bssid, addr);
-            memcpy(params.bssids[i], addr, sizeof(mac_addr));
-
-            char bssidOut[32];
-            sprintf(bssidOut, "%0x:%0x:%0x:%0x:%0x:%0x", addr[0], addr[1],
-                addr[2], addr[3], addr[4], addr[5]);
-
-            ALOGD("BSSID blacklist: added bssid %s", bssidOut);
-
-            params.num_bssid++;
-        }
-    }
-
-    ALOGD("Added %d bssids", params.num_bssid);
-    return hal_fn.wifi_set_bssid_blacklist(id, handle, params) == WIFI_SUCCESS;
-}
-
-static jint android_net_wifi_start_sending_offloaded_packet(JNIEnv *env, jclass cls, jint iface,
-                    jint idx, jbyteArray srcMac, jbyteArray dstMac, jbyteArray pkt, jint period)  {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("Start packet offload [%d] = %p", idx, handle);
-    wifi_error ret;
-    wifi_request_id id = idx;
-
-    ScopedBytesRO pktBytes(env, pkt), srcMacBytes(env, srcMac), dstMacBytes(env, dstMac);
-
-    byte * pkt_data = (byte*) pktBytes.get();
-    unsigned short pkt_len = env->GetArrayLength(pkt);
-    byte* src_mac_addr = (byte*) srcMacBytes.get();
-    byte* dst_mac_addr = (byte*) dstMacBytes.get();
-    int i;
-    char macAddr[32];
-    sprintf(macAddr, "%0x:%0x:%0x:%0x:%0x:%0x", src_mac_addr[0], src_mac_addr[1],
-            src_mac_addr[2], src_mac_addr[3], src_mac_addr[4], src_mac_addr[5]);
-    ALOGD("src_mac_addr %s", macAddr);
-    sprintf(macAddr, "%0x:%0x:%0x:%0x:%0x:%0x", dst_mac_addr[0], dst_mac_addr[1],
-            dst_mac_addr[2], dst_mac_addr[3], dst_mac_addr[4], dst_mac_addr[5]);
-    ALOGD("dst_mac_addr %s", macAddr);
-    ALOGD("pkt_len %d\n", pkt_len);
-    ALOGD("Pkt data : ");
-    for(i = 0; i < pkt_len; i++) {
-        ALOGD(" %x ", pkt_data[i]);
-    }
-    ALOGD("\n");
-    ret =  hal_fn.wifi_start_sending_offloaded_packet(id, handle, pkt_data, pkt_len,
-                src_mac_addr, dst_mac_addr, period);
-    ALOGD("ret= %d\n", ret);
-    return ret;
-}
-
-static jint android_net_wifi_stop_sending_offloaded_packet(JNIEnv *env, jclass cls,
-                    jint iface, jint idx) {
-    int ret;
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("Stop packet offload [%d] = %p", idx, handle);
-    ret =  hal_fn.wifi_stop_sending_offloaded_packet(idx, handle);
-    ALOGD("ret= %d\n", ret);
-    return ret;
-}
-
-static void onRssiThresholdbreached(wifi_request_id id, u8 *cur_bssid, s8 cur_rssi) {
-
-    ALOGD("RSSI threshold breached, cur RSSI - %d!!\n", cur_rssi);
-    ALOGD("BSSID %02x:%02x:%02x:%02x:%02x:%02x\n",
-            cur_bssid[0], cur_bssid[1], cur_bssid[2],
-            cur_bssid[3], cur_bssid[4], cur_bssid[5]);
-    JNIHelper helper(mVM);
-    //ALOGD("onRssiThresholdbreached called, vm = %p, obj = %p, env = %p", mVM, mCls, env);
-    helper.reportEvent(mCls, "onRssiThresholdBreached", "(IB)V", id, cur_rssi);
-}
-
-static jint android_net_wifi_start_rssi_monitoring_native(JNIEnv *env, jclass cls, jint iface,
-        jint idx, jbyte maxRssi, jbyte minRssi) {
-
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("Start Rssi monitoring = %p", handle);
-    ALOGD("MinRssi %d MaxRssi %d", minRssi, maxRssi);
-    wifi_error ret;
-    wifi_request_id id = idx;
-    wifi_rssi_event_handler eh;
-    eh.on_rssi_threshold_breached = onRssiThresholdbreached;
-    ret = hal_fn.wifi_start_rssi_monitoring(id, handle, maxRssi, minRssi, eh);
-    return ret;
-}
-
-static jint android_net_wifi_stop_rssi_monitoring_native(JNIEnv *env, jclass cls,
-        jint iface, jint idx) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("Stop Rssi monitoring = %p", handle);
-    wifi_error ret;
-    wifi_request_id id = idx;
-    ret = hal_fn.wifi_stop_rssi_monitoring(id, handle);
-    return ret;
-}
-
-static jobject android_net_wifi_get_wlan_wake_reason_count(JNIEnv *env, jclass cls, jint iface) {
-
-    JNIHelper helper(env);
-    WLAN_DRIVER_WAKE_REASON_CNT wake_reason_cnt;
-    int cmd_event_wake_cnt_array[WAKE_REASON_TYPE_MAX];
-    int driver_fw_local_wake_cnt_array[WAKE_REASON_TYPE_MAX];
-    wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    wifi_error ret;
-
-    wake_reason_cnt.cmd_event_wake_cnt = cmd_event_wake_cnt_array;
-    wake_reason_cnt.cmd_event_wake_cnt_sz = WAKE_REASON_TYPE_MAX;
-    wake_reason_cnt.cmd_event_wake_cnt_used = 0;
-
-    wake_reason_cnt.driver_fw_local_wake_cnt = driver_fw_local_wake_cnt_array;
-    wake_reason_cnt.driver_fw_local_wake_cnt_sz = WAKE_REASON_TYPE_MAX;
-    wake_reason_cnt.driver_fw_local_wake_cnt_used = 0;
-
-    ret = hal_fn.wifi_get_wake_reason_stats(handle, &wake_reason_cnt);
-
-    if (ret != WIFI_SUCCESS) {
-        ALOGE("android_net_wifi_get_wlan_wake_reason_count: failed to get wake reason count\n");
-        return NULL;
-    }
-
-    JNIObject<jobject> stats = helper.createObject( "android/net/wifi/WifiWakeReasonAndCounts");
-    if (stats == NULL) {
-        ALOGE("android_net_wifi_get_wlan_wake_reason_count: error allocating object\n");
-        return NULL;
-    }
-    JNIObject<jintArray> cmd_wake_arr =
-            helper.newIntArray(wake_reason_cnt.cmd_event_wake_cnt_used);
-    if (cmd_wake_arr == NULL) {
-        ALOGE("android_net_wifi_get_wlan_wake_reason_count: error allocating array object\n");
-        return NULL;
-    }
-    JNIObject<jintArray> local_wake_arr =
-            helper.newIntArray(wake_reason_cnt.driver_fw_local_wake_cnt_used);
-    if (local_wake_arr == NULL) {
-        ALOGE("android_net_wifi_get_wlan_wake_reason_count: error allocating array object\n");
-        return NULL;
-    }
-
-    helper.setIntField(stats, "totalCmdEventWake", wake_reason_cnt.total_cmd_event_wake);
-    helper.setIntField(stats, "totalDriverFwLocalWake", wake_reason_cnt.total_driver_fw_local_wake);
-    helper.setIntField(stats, "totalRxDataWake", wake_reason_cnt.total_rx_data_wake);
-    helper.setIntField(stats, "rxUnicast", wake_reason_cnt.rx_wake_details.rx_unicast_cnt);
-    helper.setIntField(stats, "rxMulticast", wake_reason_cnt.rx_wake_details.rx_multicast_cnt);
-    helper.setIntField(stats, "rxBroadcast", wake_reason_cnt.rx_wake_details.rx_broadcast_cnt);
-    helper.setIntField(stats, "icmp", wake_reason_cnt.rx_wake_pkt_classification_info.icmp_pkt);
-    helper.setIntField(stats, "icmp6", wake_reason_cnt.rx_wake_pkt_classification_info.icmp6_pkt);
-    helper.setIntField(stats, "icmp6Ra", wake_reason_cnt.rx_wake_pkt_classification_info.icmp6_ra);
-    helper.setIntField(stats, "icmp6Na", wake_reason_cnt.rx_wake_pkt_classification_info.icmp6_na);
-    helper.setIntField(stats, "icmp6Ns", wake_reason_cnt.rx_wake_pkt_classification_info.icmp6_ns);
-    helper.setIntField(stats, "ipv4RxMulticast",
-            wake_reason_cnt.rx_multicast_wake_pkt_info.ipv4_rx_multicast_addr_cnt);
-    helper.setIntField(stats, "ipv6Multicast",
-            wake_reason_cnt.rx_multicast_wake_pkt_info.ipv6_rx_multicast_addr_cnt);
-    helper.setIntField(stats, "otherRxMulticast",
-            wake_reason_cnt.rx_multicast_wake_pkt_info.other_rx_multicast_addr_cnt);
-    helper.setIntArrayRegion(cmd_wake_arr, 0, wake_reason_cnt.cmd_event_wake_cnt_used,
-            wake_reason_cnt.cmd_event_wake_cnt);
-    helper.setIntArrayRegion(local_wake_arr, 0, wake_reason_cnt.driver_fw_local_wake_cnt_used,
-            wake_reason_cnt.driver_fw_local_wake_cnt);
-    helper.setObjectField(stats, "cmdEventWakeCntArray", "[I", cmd_wake_arr);
-    helper.setObjectField(stats, "driverFWLocalWakeCntArray", "[I", local_wake_arr);
-    return stats.detach();
-}
 
 static jbyteArray android_net_wifi_readKernelLog(JNIEnv *env, jclass cls) {
     JNIHelper helper(env);
@@ -2510,15 +72,6 @@
     return result.detach();
 }
 
-static jint android_net_wifi_configure_nd_offload(JNIEnv *env, jclass cls,
-        jint iface, jboolean enable) {
-    JNIHelper helper(env);
-    return hal_fn.wifi_configure_nd_offload(
-            getIfaceHandle(helper, cls, iface),
-            static_cast<int>(enable));
-}
-
-
 // ----------------------------------------------------------------------------
 
 /*
@@ -2526,112 +79,7 @@
  */
 static JNINativeMethod gWifiMethods[] = {
     /* name, signature, funcPtr */
-
-    { "loadDriverNative", "()Z",  (void *)android_net_wifi_loadDriver },
-    { "isDriverLoadedNative", "()Z",  (void *)android_net_wifi_isDriverLoaded },
-    { "unloadDriverNative", "()Z",  (void *)android_net_wifi_unloadDriver },
-    { "startSupplicantNative", "(Z)Z",  (void *)android_net_wifi_startSupplicant },
-    { "killSupplicantNative", "(Z)Z",  (void *)android_net_wifi_killSupplicant },
-    { "connectToSupplicantNative", "()Z", (void *)android_net_wifi_connectToSupplicant },
-    { "closeSupplicantConnectionNative", "()V",
-            (void *)android_net_wifi_closeSupplicantConnection },
-    { "waitForEventNative", "()Ljava/lang/String;", (void*)android_net_wifi_waitForEvent },
-    { "doBooleanCommandNative", "(Ljava/lang/String;)Z", (void*)android_net_wifi_doBooleanCommand },
-    { "doIntCommandNative", "(Ljava/lang/String;)I", (void*)android_net_wifi_doIntCommand },
-    { "doStringCommandNative", "(Ljava/lang/String;)Ljava/lang/String;",
-            (void*) android_net_wifi_doStringCommand },
-    { "startHalNative", "()Z", (void*) android_net_wifi_startHal },
-    { "stopHalNative", "()V", (void*) android_net_wifi_stopHal },
-    { "waitForHalEventNative", "()V", (void*) android_net_wifi_waitForHalEvents },
-    { "getInterfacesNative", "()I", (void*) android_net_wifi_getInterfaces},
-    { "getInterfaceNameNative", "(I)Ljava/lang/String;", (void*) android_net_wifi_getInterfaceName},
-    { "getScanCapabilitiesNative", "(ILcom/android/server/wifi/WifiNative$ScanCapabilities;)Z",
-            (void *) android_net_wifi_getScanCapabilities},
-    { "startScanNative", "(IILcom/android/server/wifi/WifiNative$ScanSettings;)Z",
-            (void*) android_net_wifi_startScan},
-    { "stopScanNative", "(II)Z", (void*) android_net_wifi_stopScan},
-    { "getScanResultsNative", "(IZ)[Landroid/net/wifi/WifiScanner$ScanData;",
-            (void *) android_net_wifi_getScanResults},
-    { "setHotlistNative", "(IILandroid/net/wifi/WifiScanner$HotlistSettings;)Z",
-            (void*) android_net_wifi_setHotlist},
-    { "resetHotlistNative", "(II)Z", (void*) android_net_wifi_resetHotlist},
-    { "trackSignificantWifiChangeNative", "(IILandroid/net/wifi/WifiScanner$WifiChangeSettings;)Z",
-            (void*) android_net_wifi_trackSignificantWifiChange},
-    { "untrackSignificantWifiChangeNative", "(II)Z",
-            (void*) android_net_wifi_untrackSignificantWifiChange},
-    { "getWifiLinkLayerStatsNative", "(I)Landroid/net/wifi/WifiLinkLayerStats;",
-            (void*) android_net_wifi_getLinkLayerStats},
-    { "setWifiLinkLayerStatsNative", "(II)V",
-            (void*) android_net_wifi_setLinkLayerStats},
-    { "getSupportedFeatureSetNative", "(I)I",
-            (void*) android_net_wifi_getSupportedFeatures},
-    { "requestRangeNative", "(II[Landroid/net/wifi/RttManager$RttParams;)Z",
-            (void*) android_net_wifi_requestRange},
-    { "cancelRangeRequestNative", "(II[Landroid/net/wifi/RttManager$RttParams;)Z",
-            (void*) android_net_wifi_cancelRange},
-    { "enableRttResponderNative",
-        "(IIILcom/android/server/wifi/WifiNative$WifiChannelInfo;)Landroid/net/wifi/RttManager$ResponderConfig;",
-            (void*) android_net_wifi_enableResponder},
-    { "disableRttResponderNative", "(II)Z",
-            (void*) android_net_wifi_disableResponder},
-
-    { "setScanningMacOuiNative", "(I[B)Z",  (void*) android_net_wifi_setScanningMacOui},
-    { "getChannelsForBandNative", "(II)[I", (void*) android_net_wifi_getValidChannels},
-    { "setDfsFlagNative",         "(IZ)Z",  (void*) android_net_wifi_setDfsFlag},
-    { "setInterfaceUpNative", "(Z)Z",  (void*) android_net_wifi_set_interface_up},
-    { "getRttCapabilitiesNative", "(I)Landroid/net/wifi/RttManager$RttCapabilities;",
-            (void*) android_net_wifi_get_rtt_capabilities},
-    { "getApfCapabilitiesNative", "(I)Landroid/net/apf/ApfCapabilities;",
-            (void*) android_net_wifi_get_apf_capabilities},
-    { "installPacketFilterNative", "(I[B)Z", (void*) android_net_wifi_install_packet_filter},
-    {"setCountryCodeHalNative", "(ILjava/lang/String;)Z",
-            (void*) android_net_wifi_set_Country_Code_Hal},
-    { "setPnoListNative", "(IILcom/android/server/wifi/WifiNative$PnoSettings;)Z",
-            (void*) android_net_wifi_setPnoListNative},
-    { "resetPnoListNative", "(II)Z", (void*) android_net_wifi_resetPnoListNative},
-    {"enableDisableTdlsNative", "(IZLjava/lang/String;)Z",
-            (void*) android_net_wifi_enable_disable_tdls},
-    {"getTdlsStatusNative", "(ILjava/lang/String;)Lcom/android/server/wifi/WifiNative$TdlsStatus;",
-            (void*) android_net_wifi_get_tdls_status},
-    {"getTdlsCapabilitiesNative", "(I)Lcom/android/server/wifi/WifiNative$TdlsCapabilities;",
-            (void*) android_net_wifi_get_tdls_capabilities},
-    {"getSupportedLoggerFeatureSetNative","(I)I",
-            (void*) android_net_wifi_get_supported_logger_feature},
-    {"getDriverVersionNative", "(I)Ljava/lang/String;",
-            (void*) android_net_wifi_get_driver_version},
-    {"getFirmwareVersionNative", "(I)Ljava/lang/String;",
-            (void*) android_net_wifi_get_firmware_version},
-    {"getRingBufferStatusNative", "(I)[Lcom/android/server/wifi/WifiNative$RingBufferStatus;",
-            (void*) android_net_wifi_get_ring_buffer_status},
-    {"startLoggingRingBufferNative", "(IIIIILjava/lang/String;)Z",
-            (void*) android_net_wifi_start_logging_ring_buffer},
-    {"getRingBufferDataNative", "(ILjava/lang/String;)Z",
-            (void*) android_net_wifi_get_ring_buffer_data},
-    {"getFwMemoryDumpNative","(I)Z", (void*) android_net_wifi_get_fw_memory_dump},
-    {"getDriverStateDumpNative","(I)[B", (void*) android_net_wifi_get_driver_state_dump},
-    { "setBssidBlacklistNative", "(II[Ljava/lang/String;)Z",
-            (void*)android_net_wifi_setBssidBlacklist},
-    {"setLoggingEventHandlerNative", "(II)Z", (void *) android_net_wifi_set_log_handler},
-    {"resetLogHandlerNative", "(II)Z", (void *) android_net_wifi_reset_log_handler},
-    {"startPktFateMonitoringNative", "(I)I", (void*) android_net_wifi_start_pkt_fate_monitoring},
-    {"getTxPktFatesNative", "(I[Lcom/android/server/wifi/WifiNative$TxFateReport;)I",
-            (void*) android_net_wifi_get_tx_pkt_fates},
-    {"getRxPktFatesNative", "(I[Lcom/android/server/wifi/WifiNative$RxFateReport;)I",
-            (void*) android_net_wifi_get_rx_pkt_fates},
-    { "startSendingOffloadedPacketNative", "(II[B[B[BI)I",
-             (void*)android_net_wifi_start_sending_offloaded_packet},
-    { "stopSendingOffloadedPacketNative", "(II)I",
-             (void*)android_net_wifi_stop_sending_offloaded_packet},
-    {"startRssiMonitoringNative", "(IIBB)I",
-            (void*)android_net_wifi_start_rssi_monitoring_native},
-    {"stopRssiMonitoringNative", "(II)I",
-            (void*)android_net_wifi_stop_rssi_monitoring_native},
-    { "getWlanWakeReasonCountNative", "(I)Landroid/net/wifi/WifiWakeReasonAndCounts;",
-            (void*) android_net_wifi_get_wlan_wake_reason_count},
-    {"isGetChannelsForBandSupportedNative", "()Z",
-            (void*)android_net_wifi_is_get_channels_for_band_supported},
     {"readKernelLogNative", "()[B", (void*)android_net_wifi_readKernelLog},
-    {"configureNeighborDiscoveryOffload", "(IZ)I", (void*)android_net_wifi_configure_nd_offload},
 };
 
 /* User to register native functions */
diff --git a/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp b/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
deleted file mode 100644
index cae441a..0000000
--- a/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
+++ /dev/null
@@ -1,523 +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.
- */
-
-#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 "jni_helper.h"
-
-namespace android {
-
-static jclass mCls;                             /* saved WifiNanNative object */
-static JavaVM *mVM = NULL;                      /* saved JVM pointer */
-
-wifi_handle getWifiHandle(JNIHelper &helper, jclass cls);
-wifi_interface_handle getIfaceHandle(JNIHelper &helper, jclass cls, jint index);
-
-extern wifi_hal_fn hal_fn;
-
-// Start NAN functions
-
-static void OnNanNotifyResponse(transaction_id id, NanResponseMsg* msg) {
-  ALOGD(
-      "OnNanNotifyResponse: transaction_id=%d, status=%d, value=%d, response_type=%d",
-      id, msg->status, msg->value, msg->response_type);
-
-  JNIHelper helper(mVM);
-  switch (msg->response_type) {
-    case NAN_RESPONSE_PUBLISH:
-      helper.reportEvent(mCls, "onNanNotifyResponsePublishSubscribe",
-                         "(SIIII)V", (short) id, (int) msg->response_type,
-                         (int) msg->status, (int) msg->value,
-                         msg->body.publish_response.publish_id);
-      break;
-    case NAN_RESPONSE_SUBSCRIBE:
-      helper.reportEvent(mCls, "onNanNotifyResponsePublishSubscribe",
-                         "(SIIII)V", (short) id, (int) msg->response_type,
-                         (int) msg->status, (int) msg->value,
-                         msg->body.subscribe_response.subscribe_id);
-      break;
-    case NAN_GET_CAPABILITIES: {
-      JNIObject<jobject> data = helper.createObject(
-          "com/android/server/wifi/nan/WifiNanNative$Capabilities");
-      if (data == NULL) {
-        ALOGE(
-            "Error in allocating WifiNanNative.Capabilities OnNanNotifyResponse");
-        return;
-      }
-
-      helper.setIntField(
-          data, "maxConcurrentNanClusters",
-          (int) msg->body.nan_capabilities.max_concurrent_nan_clusters);
-      helper.setIntField(data, "maxPublishes",
-                         (int) msg->body.nan_capabilities.max_publishes);
-      helper.setIntField(data, "maxSubscribes",
-                         (int) msg->body.nan_capabilities.max_subscribes);
-      helper.setIntField(data, "maxServiceNameLen",
-                         (int) msg->body.nan_capabilities.max_service_name_len);
-      helper.setIntField(data, "maxMatchFilterLen",
-                         (int) msg->body.nan_capabilities.max_match_filter_len);
-      helper.setIntField(
-          data, "maxTotalMatchFilterLen",
-          (int) msg->body.nan_capabilities.max_total_match_filter_len);
-      helper.setIntField(
-          data, "maxServiceSpecificInfoLen",
-          (int) msg->body.nan_capabilities.max_service_specific_info_len);
-      helper.setIntField(data, "maxVsaDataLen",
-                         (int) msg->body.nan_capabilities.max_vsa_data_len);
-      helper.setIntField(data, "maxMeshDataLen",
-                         (int) msg->body.nan_capabilities.max_mesh_data_len);
-      helper.setIntField(data, "maxNdiInterfaces",
-                         (int) msg->body.nan_capabilities.max_ndi_interfaces);
-      helper.setIntField(data, "maxNdpSessions",
-                         (int) msg->body.nan_capabilities.max_ndp_sessions);
-      helper.setIntField(data, "maxAppInfoLen",
-                         (int) msg->body.nan_capabilities.max_app_info_len);
-
-      helper.reportEvent(
-          mCls, "onNanNotifyResponseCapabilities",
-          "(SIILcom/android/server/wifi/nan/WifiNanNative$Capabilities;)V",
-          (short) id, (int) msg->status, (int) msg->value, data.get());
-      break;
-    }
-    default:
-      helper.reportEvent(mCls, "onNanNotifyResponse", "(SIII)V", (short) id,
-                         (int) msg->response_type, (int) msg->status,
-                         (int) msg->value);
-      break;
-  }
-}
-
-static void OnNanEventPublishTerminated(NanPublishTerminatedInd* event) {
-    ALOGD("OnNanEventPublishTerminated");
-
-    JNIHelper helper(mVM);
-    helper.reportEvent(mCls, "onPublishTerminated", "(II)V",
-                       event->publish_id, event->reason);
-}
-
-static void OnNanEventMatch(NanMatchInd* event) {
-    ALOGD("OnNanEventMatch");
-
-    JNIHelper helper(mVM);
-
-    JNIObject<jbyteArray> macBytes = helper.newByteArray(6);
-    helper.setByteArrayRegion(macBytes, 0, 6, (jbyte *) event->addr);
-
-    JNIObject<jbyteArray> ssiBytes = helper.newByteArray(event->service_specific_info_len);
-    helper.setByteArrayRegion(ssiBytes, 0, event->service_specific_info_len,
-                              (jbyte *) event->service_specific_info);
-
-    JNIObject<jbyteArray> mfBytes = helper.newByteArray(event->sdf_match_filter_len);
-    helper.setByteArrayRegion(mfBytes, 0, event->sdf_match_filter_len,
-                              (jbyte *) event->sdf_match_filter);
-
-    helper.reportEvent(mCls, "onMatchEvent", "(II[B[BI[BI)V",
-                       (int) event->publish_subscribe_id,
-                       (int) event->requestor_instance_id,
-                       macBytes.get(),
-                       ssiBytes.get(), event->service_specific_info_len,
-                       mfBytes.get(), event->sdf_match_filter_len);
-}
-
-static void OnNanEventMatchExpired(NanMatchExpiredInd* event) {
-    ALOGD("OnNanEventMatchExpired");
-}
-
-static void OnNanEventSubscribeTerminated(NanSubscribeTerminatedInd* event) {
-    ALOGD("OnNanEventSubscribeTerminated");
-
-    JNIHelper helper(mVM);
-    helper.reportEvent(mCls, "onSubscribeTerminated", "(II)V",
-                       event->subscribe_id, event->reason);
-}
-
-static void OnNanEventFollowup(NanFollowupInd* event) {
-    ALOGD("OnNanEventFollowup");
-
-    JNIHelper helper(mVM);
-
-    JNIObject<jbyteArray> macBytes = helper.newByteArray(6);
-    helper.setByteArrayRegion(macBytes, 0, 6, (jbyte *) event->addr);
-
-    JNIObject<jbyteArray> msgBytes = helper.newByteArray(event->service_specific_info_len);
-    helper.setByteArrayRegion(msgBytes, 0, event->service_specific_info_len, (jbyte *) event->service_specific_info);
-
-    helper.reportEvent(mCls, "onFollowupEvent", "(II[B[BI)V",
-                       (int) event->publish_subscribe_id,
-                       (int) event->requestor_instance_id,
-                       macBytes.get(),
-                       msgBytes.get(),
-                       (int) event->service_specific_info_len);
-}
-
-static void OnNanEventDiscEngEvent(NanDiscEngEventInd* event) {
-    ALOGD("OnNanEventDiscEngEvent called: event_type=%d", event->event_type);
-
-    JNIHelper helper(mVM);
-
-    JNIObject<jbyteArray> macBytes = helper.newByteArray(6);
-    if (event->event_type == NAN_EVENT_ID_DISC_MAC_ADDR) {
-        helper.setByteArrayRegion(macBytes, 0, 6, (jbyte *) event->data.mac_addr.addr);
-    } else {
-        helper.setByteArrayRegion(macBytes, 0, 6, (jbyte *) event->data.cluster.addr);
-    }
-
-    helper.reportEvent(mCls, "onDiscoveryEngineEvent", "(I[B)V",
-                       (int) event->event_type, macBytes.get());
-}
-
-static void OnNanEventDisabled(NanDisabledInd* event) {
-    ALOGD("OnNanEventDisabled called: reason=%d", event->reason);
-
-    JNIHelper helper(mVM);
-
-    helper.reportEvent(mCls, "onDisabledEvent", "(I)V", (int) event->reason);
-}
-
-static void OnNanEventTca(NanTCAInd* event) {
-    ALOGD("OnNanEventTca");
-}
-
-static void OnNanEventBeaconSdfPayload(NanBeaconSdfPayloadInd* event) {
-    ALOGD("OnNanEventSdfPayload");
-}
-
-static jint android_net_wifi_nan_register_handler(JNIEnv *env, jclass cls,
-                                                  jclass wifi_native_cls,
-                                                  jint iface) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-    ALOGD("android_net_wifi_nan_register_handler handle=%p", handle);
-
-    NanCallbackHandler handlers;
-    handlers.NotifyResponse = OnNanNotifyResponse;
-    handlers.EventPublishTerminated = OnNanEventPublishTerminated;
-    handlers.EventMatch = OnNanEventMatch;
-    handlers.EventMatchExpired = OnNanEventMatchExpired;
-    handlers.EventSubscribeTerminated = OnNanEventSubscribeTerminated;
-    handlers.EventFollowup = OnNanEventFollowup;
-    handlers.EventDiscEngEvent = OnNanEventDiscEngEvent;
-    handlers.EventDisabled = OnNanEventDisabled;
-    handlers.EventTca = OnNanEventTca;
-    handlers.EventBeaconSdfPayload = OnNanEventBeaconSdfPayload;
-
-    if (mVM == NULL) {
-        env->GetJavaVM(&mVM);
-        mCls = (jclass) env->NewGlobalRef(cls);
-    }
-
-    return hal_fn.wifi_nan_register_handler(handle, handlers);
-}
-
-static jint android_net_wifi_nan_enable_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_enable_request handle=%p, id=%d",
-          handle, transaction_id);
-
-    NanEnableRequest msg;
-    memset(&msg, 0, sizeof(NanEnableRequest));
-
-    /* configurable settings */
-    msg.config_support_5g = 1;
-    msg.support_5g_val = helper.getBoolField(config_request, "mSupport5gBand");
-    msg.master_pref = helper.getIntField(config_request, "mMasterPreference");
-    msg.cluster_low = helper.getIntField(config_request, "mClusterLow");
-    msg.cluster_high = helper.getIntField(config_request, "mClusterHigh");
-
-    return hal_fn.wifi_nan_enable_request(transaction_id, handle, &msg);
-}
-
-static jint android_net_wifi_nan_get_capabilities(JNIEnv *env, jclass cls,
-                                                  jshort transaction_id,
-                                                  jclass wifi_native_cls,
-                                                  jint iface) {
-  JNIHelper helper(env);
-  wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-  ALOGD("android_net_wifi_nan_get_capabilities handle=%p, id=%d", handle,
-        transaction_id);
-
-  return hal_fn.wifi_nan_get_capabilities(transaction_id, handle);
-}
-
-static jint android_net_wifi_nan_disable_request(JNIEnv *env, jclass cls,
-                                                 jshort transaction_id,
-                                                 jclass wifi_native_cls,
-                                                 jint iface) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-    ALOGD("android_net_wifi_nan_disable_request handle=%p, id=%d",
-          handle, transaction_id);
-
-    return hal_fn.wifi_nan_disable_request(transaction_id, handle);
-}
-
-static jint android_net_wifi_nan_publish(JNIEnv *env, jclass cls,
-                                         jshort transaction_id,
-                                         jint publish_id,
-                                         jclass wifi_native_cls,
-                                         jint iface,
-                                         jobject publish_data,
-                                         jobject publish_settings) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-    ALOGD("android_net_wifi_nan_publish handle=%p, id=%d", handle, transaction_id);
-
-    NanPublishRequest msg;
-    memset(&msg, 0, sizeof(NanPublishRequest));
-
-    /* hard-coded settings - TBD: move to configurable */
-    msg.period = 500;
-    msg.publish_match_indicator = NAN_MATCH_ALG_MATCH_ONCE;
-    msg.rssi_threshold_flag = 0;
-    msg.connmap = 0;
-
-    /* configurable settings */
-    msg.publish_id = publish_id;
-
-    JNIObject<jstring> objStr1 = helper.getStringField(publish_data, "mServiceName");
-    if (objStr1 == NULL) {
-        ALOGE("Error accessing mServiceName field");
-        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_specific_info_len = helper.getIntField(publish_data, "mServiceSpecificInfoLength");
-    if (msg.service_specific_info_len != 0) {
-        helper.getByteArrayField(publish_data, "mServiceSpecificInfo",
-                             msg.service_specific_info, msg.service_specific_info_len);
-    }
-
-
-    msg.tx_match_filter_len = helper.getIntField(publish_data, "mTxFilterLength");
-    if (msg.tx_match_filter_len != 0) {
-        helper.getByteArrayField(publish_data, "mTxFilter",
-                             msg.tx_match_filter, msg.tx_match_filter_len);
-    }
-
-    msg.rx_match_filter_len = helper.getIntField(publish_data, "mRxFilterLength");
-    if (msg.rx_match_filter_len != 0) {
-        helper.getByteArrayField(publish_data, "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.tx_type = NAN_TX_TYPE_BROADCAST;
-    if (msg.publish_type != NAN_PUBLISH_TYPE_UNSOLICITED)
-      msg.tx_type = NAN_TX_TYPE_UNICAST;
-
-    return hal_fn.wifi_nan_publish_request(transaction_id, handle, &msg);
-}
-
-static jint android_net_wifi_nan_subscribe(JNIEnv *env, jclass cls,
-                                           jshort transaction_id,
-                                           jint subscribe_id,
-                                           jclass wifi_native_cls,
-                                           jint iface,
-                                           jobject subscribe_data,
-                                           jobject subscribe_settings) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-    ALOGD("android_net_wifi_nan_subscribe handle=%p, id=%d", handle, transaction_id);
-
-    NanSubscribeRequest msg;
-    memset(&msg, 0, sizeof(NanSubscribeRequest));
-
-    /* hard-coded settings - TBD: move to configurable */
-    msg.period = 500;
-    msg.serviceResponseFilter = NAN_SRF_ATTR_PARTIAL_MAC_ADDR;
-    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;
-
-    /* configurable settings */
-    msg.subscribe_id = subscribe_id;
-
-    JNIObject<jstring> objStr1 = helper.getStringField(subscribe_data, "mServiceName");
-    if (objStr1 == NULL) {
-        ALOGE("Error accessing mServiceName field");
-        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_specific_info_len = helper.getIntField(subscribe_data, "mServiceSpecificInfoLength");
-    if (msg.service_specific_info_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mServiceSpecificInfo",
-                             msg.service_specific_info, msg.service_specific_info_len);
-    }
-
-    msg.tx_match_filter_len = helper.getIntField(subscribe_data, "mTxFilterLength");
-    if (msg.tx_match_filter_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mTxFilter",
-                             msg.tx_match_filter, msg.tx_match_filter_len);
-    }
-
-    msg.rx_match_filter_len = helper.getIntField(subscribe_data, "mRxFilterLength");
-    if (msg.rx_match_filter_len != 0) {
-        helper.getByteArrayField(subscribe_data, "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");
-
-    return hal_fn.wifi_nan_subscribe_request(transaction_id, handle, &msg);
-}
-
-static jint android_net_wifi_nan_send_message(JNIEnv *env, jclass cls,
-                                              jshort transaction_id,
-                                              jclass wifi_native_cls,
-                                              jint iface,
-                                              jint pub_sub_id,
-                                              jint req_instance_id,
-                                              jbyteArray dest,
-                                              jbyteArray message,
-                                              jint message_length) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-    ALOGD("android_net_wifi_nan_send_message handle=%p, id=%d", handle, transaction_id);
-
-    NanTransmitFollowupRequest msg;
-    memset(&msg, 0, sizeof(NanTransmitFollowupRequest));
-
-    /* hard-coded settings - TBD: move to configurable */
-    msg.publish_subscribe_id = pub_sub_id;
-    msg.requestor_instance_id = req_instance_id;
-    msg.priority = NAN_TX_PRIORITY_NORMAL;
-    msg.dw_or_faw = NAN_TRANSMIT_IN_DW;
-
-    /* configurable settings */
-    msg.service_specific_info_len = message_length;
-
-    ScopedBytesRO messageBytes(env, message);
-    memcpy(msg.service_specific_info, (byte*) messageBytes.get(), message_length);
-
-    ScopedBytesRO destBytes(env, dest);
-    memcpy(msg.addr, (byte*) destBytes.get(), 6);
-
-    return hal_fn.wifi_nan_transmit_followup_request(transaction_id, handle, &msg);
-}
-
-static jint android_net_wifi_nan_stop_publish(JNIEnv *env, jclass cls,
-                                              jshort transaction_id,
-                                              jclass wifi_native_cls,
-                                              jint iface,
-                                              jint pub_sub_id) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-    ALOGD("android_net_wifi_nan_stop_publish handle=%p, id=%d", handle, transaction_id);
-
-    NanPublishCancelRequest msg;
-    memset(&msg, 0, sizeof(NanPublishCancelRequest));
-
-    msg.publish_id = pub_sub_id;
-
-    return hal_fn.wifi_nan_publish_cancel_request(transaction_id, handle, &msg);
-}
-
-static jint android_net_wifi_nan_stop_subscribe(JNIEnv *env, jclass cls,
-                                              jshort transaction_id,
-                                              jclass wifi_native_cls,
-                                              jint iface,
-                                              jint pub_sub_id) {
-    JNIHelper helper(env);
-    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
-
-    ALOGD("android_net_wifi_nan_stop_subscribe handle=%p, id=%d", handle, transaction_id);
-
-    NanSubscribeCancelRequest msg;
-    memset(&msg, 0, sizeof(NanSubscribeCancelRequest));
-
-    msg.subscribe_id = pub_sub_id;
-
-    return hal_fn.wifi_nan_subscribe_cancel_request(transaction_id, handle, &msg);
-}
-
-// ----------------------------------------------------------------------------
-
-/*
- * JNI registration.
- */
-
-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 },
-};
-
-/* User to register native functions */
-extern "C"
-jint Java_com_android_server_wifi_nan_WifiNanNative_registerNanNatives(JNIEnv* env, jclass clazz) {
-    return jniRegisterNativeMethods(env,
-            "com/android/server/wifi/nan/WifiNanNative", gWifiNanMethods, NELEM(gWifiNanMethods));
-}
-
-}; // namespace android
diff --git a/service/jni/jni_helper.cpp b/service/jni/jni_helper.cpp
index c9b4edd..150f694 100644
--- a/service/jni/jni_helper.cpp
+++ b/service/jni/jni_helper.cpp
@@ -16,15 +16,13 @@
 
 #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.h"
-#include "wifi_hal.h"
 #include "jni_helper.h"
 
 namespace android {
@@ -52,628 +50,19 @@
     }
 }
 
-jobject JNIHelper::newGlobalRef(jobject obj) {
-    return mEnv->NewGlobalRef(obj);
-}
-
-void JNIHelper::deleteGlobalRef(jobject obj) {
-    mEnv->DeleteGlobalRef(obj);
-}
-
 jobject JNIHelper::newLocalRef(jobject obj) {
-    return mEnv->NewLocalRef(obj);
+      return mEnv->NewLocalRef(obj);
 }
 
 void JNIHelper::deleteLocalRef(jobject obj) {
-    mEnv->DeleteLocalRef(obj);
-}
-
-void JNIHelper::throwException(const char *message, int line)
-{
-    ALOGE("error at line %d: %s", line, message);
-
-    const char *className = "java/lang/Exception";
-
-    jclass exClass = mEnv->FindClass(className );
-    if ( exClass == NULL ) {
-        ALOGE("Could not find exception class to throw error");
-        ALOGE("error at line %d: %s", line, message);
-        return;
-    }
-
-    mEnv->ThrowNew(exClass, message);
-}
-
-jboolean JNIHelper::getBoolField(jobject obj, const char *name)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, "Z");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return 0;
-    }
-
-    return mEnv->GetBooleanField(obj, field);
-}
-
-jint JNIHelper::getIntField(jobject obj, const char *name)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, "I");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return 0;
-    }
-
-    return mEnv->GetIntField(obj, field);
-}
-
-jbyte JNIHelper::getByteField(jobject obj, const char *name)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, "B");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return 0;
-    }
-
-    return mEnv->GetByteField(obj, field);
-}
-
-jlong JNIHelper::getLongField(jobject obj, const char *name)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, "J");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return 0;
-    }
-
-    return mEnv->GetLongField(obj, field);
-}
-
-JNIObject<jstring> JNIHelper::getStringField(jobject obj, const char *name)
-{
-    JNIObject<jobject> m = getObjectField(obj, name, "Ljava/lang/String;");
-    if (m == NULL) {
-        THROW(*this, "Error in accessing field");
-        return JNIObject<jstring>(*this, NULL);
-    }
-
-    return JNIObject<jstring>(*this, (jstring)m.detach());
-}
-
-bool JNIHelper::getStringFieldValue(jobject obj, const char *name, char *buf, int size)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, "Ljava/lang/String;");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return 0;
-    }
-
-    JNIObject<jobject> value(*this, mEnv->GetObjectField(obj, field));
-    JNIObject<jstring> string(*this, (jstring)value.clone());
-    ScopedUtfChars chars(mEnv, string);
-
-    const char *utf = chars.c_str();
-    if (utf == NULL) {
-        THROW(*this, "Error in accessing value");
-        return false;
-    }
-
-    if (*utf != 0 && size < 1) {
-        return false;
-    }
-
-    strncpy(buf, utf, size);
-    if (size > 0) {
-        buf[size - 1] = 0;
-    }
-
-    return true;
-}
-
-jlong JNIHelper::getStaticLongField(jobject obj, const char *name)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    return getStaticLongField(cls, name);
-}
-
-jlong JNIHelper::getStaticLongField(jclass cls, const char *name)
-{
-    jfieldID field = mEnv->GetStaticFieldID(cls, name, "J");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return 0;
-    }
-    //ALOGE("getStaticLongField %s %p", name, cls);
-    return mEnv->GetStaticLongField(cls, field);
-}
-
-JNIObject<jobject> JNIHelper::getObjectField(jobject obj, const char *name, const char *type)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, type);
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return JNIObject<jobject>(*this, NULL);
-    }
-
-    return JNIObject<jobject>(*this, mEnv->GetObjectField(obj, field));
-}
-
-JNIObject<jobjectArray> JNIHelper::getArrayField(jobject obj, const char *name, const char *type)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, type);
-    if (field == 0) {
-        THROW(*this, "Error in accessing field");
-        return JNIObject<jobjectArray>(*this, NULL);
-    }
-
-    return JNIObject<jobjectArray>(*this, (jobjectArray)mEnv->GetObjectField(obj, field));
-}
-
-jlong JNIHelper::getLongArrayField(jobject obj, const char *name, int index)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, "[J");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field definition");
-        return 0;
-    }
-
-    JNIObject<jlongArray> array(*this, (jlongArray)mEnv->GetObjectField(obj, field));
-    if (array == NULL) {
-        THROW(*this, "Error in accessing array");
-        return 0;
-    }
-
-    jlong *elem = mEnv->GetLongArrayElements(array, 0);
-    if (elem == NULL) {
-        THROW(*this, "Error in accessing index element");
-        return 0;
-    }
-
-    jlong value = elem[index];
-    mEnv->ReleaseLongArrayElements(array, elem, 0);
-    return value;
-}
-
-void JNIHelper::getByteArrayField(jobject obj, const char *name, byte* buf, int 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;
-    }
-
-    JNIObject<jbyteArray> array(*this, (jbyteArray)mEnv->GetObjectField(obj, field));
-    if (array == NULL) {
-        THROW(*this, "Error in accessing array");
-        return;
-    }
-
-    jbyte *elem = mEnv->GetByteArrayElements(array, 0);
-    if (elem == NULL) {
-        THROW(*this, "Error in accessing index element");
-        return;
-    }
-
-    memcpy(buf, elem, size);
-    mEnv->ReleaseByteArrayElements(array, elem, 0);
-}
-
-jlong JNIHelper::getStaticLongArrayField(jobject obj, const char *name, int index)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    return getStaticLongArrayField(cls, name, index);
-}
-
-jlong JNIHelper::getStaticLongArrayField(jclass cls, const char *name, int index)
-{
-    jfieldID field = mEnv->GetStaticFieldID(cls, name, "[J");
-    if (field == 0) {
-        THROW(*this, "Error in accessing field definition");
-        return 0;
-    }
-
-    JNIObject<jlongArray> array(*this, (jlongArray)mEnv->GetStaticObjectField(cls, field));
-    jlong *elem = mEnv->GetLongArrayElements(array, 0);
-    if (elem == NULL) {
-        THROW(*this, "Error in accessing index element");
-        return 0;
-    }
-
-    jlong value = elem[index];
-    mEnv->ReleaseLongArrayElements(array, elem, 0);
-    return value;
-}
-
-JNIObject<jobject> JNIHelper::getObjectArrayField(jobject obj, const char *name, const char *type,
-int index)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    jfieldID field = mEnv->GetFieldID(cls, name, type);
-    if (field == 0) {
-        THROW(*this, "Error in accessing field definition");
-        return JNIObject<jobject>(*this, NULL);
-    }
-
-    JNIObject<jobjectArray> array(*this, (jobjectArray)mEnv->GetObjectField(obj, field));
-    JNIObject<jobject> elem(*this, mEnv->GetObjectArrayElement(array, index));
-    if (elem.isNull()) {
-        THROW(*this, "Error in accessing index element");
-        return JNIObject<jobject>(*this, NULL);
-    }
-    return elem;
-}
-
-void JNIHelper::setIntField(jobject obj, const char *name, jint value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing class");
-        return;
-    }
-
-    jfieldID field = mEnv->GetFieldID(cls, name, "I");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetIntField(obj, field, value);
-}
-
-void JNIHelper::setByteField(jobject obj, const char *name, jbyte value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing class");
-        return;
-    }
-
-    jfieldID field = mEnv->GetFieldID(cls, name, "B");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetByteField(obj, field, value);
-}
-
-void JNIHelper::setBooleanField(jobject obj, const char *name, jboolean value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing class");
-        return;
-    }
-
-    jfieldID field = mEnv->GetFieldID(cls, name, "Z");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetBooleanField(obj, field, value);
-}
-
-void JNIHelper::setLongField(jobject obj, const char *name, jlong value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing class");
-        return;
-    }
-
-    jfieldID field = mEnv->GetFieldID(cls, name, "J");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetLongField(obj, field, value);
-}
-
-void JNIHelper::setStaticLongField(jobject obj, const char *name, jlong value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing class");
-        return;
-    }
-
-    setStaticLongField(cls, name, value);
-}
-
-void JNIHelper::setStaticLongField(jclass cls, const char *name, jlong value)
-{
-    jfieldID field = mEnv->GetStaticFieldID(cls, name, "J");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetStaticLongField(cls, field, value);
-}
-
-void JNIHelper::setLongArrayField(jobject obj, const char *name, jlongArray value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    jfieldID field = mEnv->GetFieldID(cls, name, "[J");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetObjectField(obj, field, value);
-}
-
-void JNIHelper::setStaticLongArrayField(jobject obj, const char *name, jlongArray value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    setStaticLongArrayField(cls, name, value);
-}
-
-void JNIHelper::setStaticLongArrayField(jclass cls, const char *name, jlongArray value)
-{
-    jfieldID field = mEnv->GetStaticFieldID(cls, name, "[J");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetStaticObjectField(cls, field, value);
-    ALOGD("array field set");
-}
-
-void JNIHelper::setLongArrayElement(jobject obj, const char *name, int index, jlong value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    jfieldID field = mEnv->GetFieldID(cls, name, "[J");
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    JNIObject<jlongArray> array(*this, (jlongArray)mEnv->GetObjectField(obj, field));
-    if (array == NULL) {
-        THROW(*this, "Error in accessing array");
-        return;
-    }
-
-    jlong *elem = mEnv->GetLongArrayElements(array, NULL);
-    if (elem == NULL) {
-        THROW(*this, "Error in accessing index element");
-        return;
-    }
-
-    elem[index] = value;
-    mEnv->ReleaseLongArrayElements(array, elem, 0);
-}
-
-void JNIHelper::setObjectField(jobject obj, const char *name, const char *type, jobject value)
-{
-    JNIObject<jclass> cls(*this, mEnv->GetObjectClass(obj));
-    if (cls == NULL) {
-        THROW(*this, "Error in accessing class");
-        return;
-    }
-
-    jfieldID field = mEnv->GetFieldID(cls, name, type);
-    if (field == NULL) {
-        THROW(*this, "Error in accessing field");
-        return;
-    }
-
-    mEnv->SetObjectField(obj, field, value);
-}
-
-jboolean JNIHelper::setStringField(jobject obj, const char *name, const char *value)
-{
-    JNIObject<jstring> str(*this, mEnv->NewStringUTF(value));
-
-    if (mEnv->ExceptionCheck()) {
-        mEnv->ExceptionDescribe();
-        mEnv->ExceptionClear();
-        return false;
-    }
-
-    if (str == NULL) {
-        THROW(*this, "Error creating string");
-        return false;
-    }
-
-    setObjectField(obj, name, "Ljava/lang/String;", str);
-    return true;
-}
-
-void JNIHelper::reportEvent(jclass cls, const char *method, const char *signature, ...)
-{
-    va_list params;
-    va_start(params, signature);
-
-    jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
-    if (methodID == 0) {
-        ALOGE("Error in getting method ID");
-        return;
-    }
-
-    mEnv->CallStaticVoidMethodV(cls, methodID, params);
-    if (mEnv->ExceptionCheck()) {
-        mEnv->ExceptionDescribe();
-        mEnv->ExceptionClear();
-    }
-
-    va_end(params);
-}
-
-void JNIHelper::callMethod(jobject obj, const char *method, const char *signature, ...)
-{
-    va_list params;
-    va_start(params, signature);
-
-    jclass cls = mEnv->GetObjectClass(obj);
-    jmethodID methodID = mEnv->GetMethodID(cls, method, signature);
-    if (methodID == 0) {
-        ALOGE("Error in getting method ID");
-        return;
-    }
-
-    mEnv->CallVoidMethodV(obj, methodID, params);
-    if (mEnv->ExceptionCheck()) {
-        mEnv->ExceptionDescribe();
-        mEnv->ExceptionClear();
-    }
-
-    va_end(params);
-}
-
-jboolean JNIHelper::callStaticMethod(jclass cls, const char *method, const char *signature, ...)
-{
-    va_list params;
-    va_start(params, signature);
-
-    jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
-    if (methodID == 0) {
-        ALOGE("Error in getting method ID");
-        return false;
-    }
-
-    jboolean result = mEnv->CallStaticBooleanMethodV(cls, methodID, params);
-    if (mEnv->ExceptionCheck()) {
-        mEnv->ExceptionDescribe();
-        mEnv->ExceptionClear();
-        return false;
-    }
-
-    va_end(params);
-    return result;
-}
-
-JNIObject<jobject> JNIHelper::createObject(const char *className) {
-    return createObjectWithArgs(className, "()V");
-}
-
-JNIObject<jobject> JNIHelper::createObjectWithArgs(
-    const char *className, const char *signature, ...)
-{
-    va_list params;
-    va_start(params, signature);
-
-    JNIObject<jclass> cls(*this, mEnv->FindClass(className));
-    if (cls == NULL) {
-        ALOGE("Error in finding class %s", className);
-        return JNIObject<jobject>(*this, NULL);
-    }
-
-    jmethodID constructor = mEnv->GetMethodID(cls, "<init>", signature);
-    if (constructor == 0) {
-        ALOGE("Error in constructor ID for %s", className);
-        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);
-        return JNIObject<jobject>(*this, NULL);
-    }
-
-    va_end(params);
-    return obj;
-}
-
-JNIObject<jobjectArray> JNIHelper::createObjectArray(const char *className, int num)
-{
-    JNIObject<jclass> cls(*this, mEnv->FindClass(className));
-    if (cls == NULL) {
-        ALOGE("Error in finding class %s", className);
-        return JNIObject<jobjectArray>(*this, NULL);
-    }
-
-    JNIObject<jobject> array(*this, mEnv->NewObjectArray(num, cls.get(), NULL));
-    if (array.get() == NULL) {
-        ALOGE("Error in creating array of class %s", className);
-        return JNIObject<jobjectArray>(*this, NULL);
-    }
-
-    return JNIObject<jobjectArray>(*this, (jobjectArray)array.detach());
-}
-
-JNIObject<jobject> JNIHelper::getObjectArrayElement(jobjectArray array, int index)
-{
-    return JNIObject<jobject>(*this, mEnv->GetObjectArrayElement(array, index));
-}
-
-JNIObject<jobject> JNIHelper::getObjectArrayElement(jobject array, int index)
-{
-    return getObjectArrayElement((jobjectArray)array, index);
-}
-
-int JNIHelper::getArrayLength(jarray array) {
-    return mEnv->GetArrayLength(array);
-}
-
-JNIObject<jobjectArray> JNIHelper::newObjectArray(int num, const char *className, jobject val) {
-    JNIObject<jclass> cls(*this, mEnv->FindClass(className));
-    if (cls == NULL) {
-        ALOGE("Error in finding class %s", className);
-        return JNIObject<jobjectArray>(*this, NULL);
-    }
-
-    return JNIObject<jobjectArray>(*this, mEnv->NewObjectArray(num, cls, val));
+      mEnv->DeleteLocalRef(obj);
 }
 
 JNIObject<jbyteArray> JNIHelper::newByteArray(int num) {
     return JNIObject<jbyteArray>(*this, mEnv->NewByteArray(num));
 }
 
-JNIObject<jintArray> JNIHelper::newIntArray(int num) {
-    return JNIObject<jintArray>(*this, mEnv->NewIntArray(num));
-}
-
-JNIObject<jlongArray> JNIHelper::newLongArray(int num) {
-    return JNIObject<jlongArray>(*this, mEnv->NewLongArray(num));
-}
-
-JNIObject<jstring> JNIHelper::newStringUTF(const char *utf) {
-    return JNIObject<jstring>(*this, mEnv->NewStringUTF(utf));
-}
-
-void JNIHelper::setObjectArrayElement(jobjectArray array, int index, jobject obj) {
-    mEnv->SetObjectArrayElement(array, index, obj);
-}
-
 void JNIHelper::setByteArrayRegion(jbyteArray array, int from, int to, const jbyte *bytes) {
     mEnv->SetByteArrayRegion(array, from, to, bytes);
 }
-
-void JNIHelper::setIntArrayRegion(jintArray array, int from, int to, const jint *ints) {
-    mEnv->SetIntArrayRegion(array, from, to, ints);
-}
-
-void JNIHelper::setLongArrayRegion(jlongArray array, int from, int to, const jlong *longs) {
-    mEnv->SetLongArrayRegion(array, from, to, longs);
-}
-
 }; // namespace android
-
-
diff --git a/service/jni/jni_helper.h b/service/jni/jni_helper.h
index bb65f1c..febb901 100644
--- a/service/jni/jni_helper.h
+++ b/service/jni/jni_helper.h
@@ -60,7 +60,7 @@
 
 private:
     template<typename T2>
-    JNIObject(const JNIObject<T2>& rhs);
+    JNIObject(const JNIObject<T2>& rhs);  // NOLINT(implicit)
 };
 
 class JNIHelper {
@@ -68,111 +68,52 @@
     JNIEnv *mEnv;
 
 public :
-    JNIHelper(JavaVM *vm);
-    JNIHelper(JNIEnv *env);
+    explicit JNIHelper(JavaVM *vm);
+    explicit JNIHelper(JNIEnv *env);
     ~JNIHelper();
 
-    void throwException(const char *message, int line);
-
-    /* helpers to deal with members */
-    jboolean getBoolField(jobject obj, const char *name);
-    jint getIntField(jobject obj, const char *name);
-    jlong getLongField(jobject obj, const char *name);
-    JNIObject<jstring> getStringField(jobject obj, const char *name);
-    bool getStringFieldValue(jobject obj, const char *name, char *buf, int size);
-    JNIObject<jobject> getObjectField(jobject obj, const char *name, const char *type);
-    JNIObject<jobjectArray> getArrayField(jobject obj, const char *name, const char *type);
-    void getByteArrayField(jobject obj, const char *name, byte* buf, int size);
-    jlong getLongArrayField(jobject obj, const char *name, int index);
-    JNIObject<jobject> getObjectArrayField(
-            jobject obj, const char *name, const char *type, int index);
-    void setIntField(jobject obj, const char *name, jint value);
-    void setByteField(jobject obj, const char *name, jbyte value);
-    jbyte getByteField(jobject obj, const char *name);
-    void setBooleanField(jobject obj, const char *name, jboolean value);
-    void setLongField(jobject obj, const char *name, jlong value);
-    void setLongArrayField(jobject obj, const char *name, jlongArray value);
-    void setLongArrayElement(jobject obj, const char *name, int index, jlong value);
-    jboolean setStringField(jobject obj, const char *name, const char *value);
-    void reportEvent(jclass cls, const char *method, const char *signature, ...);
-    JNIObject<jobject> createObject(const char *className);
-    JNIObject<jobject> createObjectWithArgs(const char *className, const char *signature, ...);
-    JNIObject<jobjectArray> createObjectArray(const char *className, int size);
-    void setObjectField(jobject obj, const char *name, const char *type, jobject value);
-    void callMethod(jobject obj, const char *method, const char *signature, ...);
-
     /* helpers to deal with static members */
-    jlong getStaticLongField(jobject obj, const char *name);
-    jlong getStaticLongField(jclass cls, const char *name);
-    void setStaticLongField(jobject obj, const char *name, jlong value);
-    void setStaticLongField(jclass cls, const char *name, jlong value);
-    jlong getStaticLongArrayField(jobject obj, const char *name, int index);
-    jlong getStaticLongArrayField(jclass cls, const char *name, int index);
-    void setStaticLongArrayField(jobject obj, const char *name, jlongArray value);
-    void setStaticLongArrayField(jclass obj, const char *name, jlongArray value);
-    jboolean callStaticMethod(jclass cls, const char *method, const char *signature, ...);
-
-    JNIObject<jobject> getObjectArrayElement(jobjectArray array, int index);
-    JNIObject<jobject> getObjectArrayElement(jobject array, int index);
-    int getArrayLength(jarray array);
-    JNIObject<jobjectArray> newObjectArray(int num, const char *className, jobject val);
     JNIObject<jbyteArray> newByteArray(int num);
-    JNIObject<jintArray> newIntArray(int num);
-    JNIObject<jlongArray> newLongArray(int num);
-    JNIObject<jstring> newStringUTF(const char *utf);
-    void setObjectArrayElement(jobjectArray array, int index, jobject obj);
     void setByteArrayRegion(jbyteArray array, int from, int to, const jbyte *bytes);
-    void setIntArrayRegion(jintArray array, int from, int to, const jint *ints);
-    void setLongArrayRegion(jlongArray array, int from, int to, const jlong *longs);
-
-    jobject newGlobalRef(jobject obj);
-    void deleteGlobalRef(jobject obj);
 
 private:
     /* Jni wrappers */
-    friend class JNIObject<jobject>;
-    friend class JNIObject<jstring>;
-    friend class JNIObject<jobjectArray>;
-    friend class JNIObject<jclass>;
-    friend class JNIObject<jlongArray>;
     friend class JNIObject<jbyteArray>;
-    friend class JNIObject<jintArray>;
     jobject newLocalRef(jobject obj);
     void deleteLocalRef(jobject obj);
 };
 
 template<typename T>
 JNIObject<T>::JNIObject(JNIHelper &helper, T obj)
-    : mHelper(helper), mObj(obj)
+      : mHelper(helper), mObj(obj)
 { }
 
 template<typename T>
 JNIObject<T>::JNIObject(const JNIObject<T>& rhs)
-    : mHelper(rhs.mHelper), mObj(NULL)
+      : mHelper(rhs.mHelper), mObj(NULL)
 {
-    mObj = (T)mHelper.newLocalRef(rhs.mObj);
+      mObj = (T)mHelper.newLocalRef(rhs.mObj);
 }
 
 template<typename T>
 JNIObject<T>::~JNIObject() {
-    release();
+      release();
 }
 
 template<typename T>
 void JNIObject<T>::release()
 {
-    if (mObj != NULL) {
-        mHelper.deleteLocalRef(mObj);
-        mObj = NULL;
-    }
+      if (mObj != NULL) {
+          mHelper.deleteLocalRef(mObj);
+          mObj = NULL;
+      }
 }
 
 template<typename T>
 T JNIObject<T>::clone()
 {
-    return mHelper.newLocalRef(mObj);
+      return mHelper.newLocalRef(mObj);
 }
-
 }
 
 #define THROW(env, message)      (env).throwException(message, __LINE__)
diff --git a/service/jni/wifi_hal_stub.h b/service/jni/wifi_hal_stub.h
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/service/proto/wifi.proto b/service/proto/wifi.proto
deleted file mode 100644
index 8128ec1..0000000
--- a/service/proto/wifi.proto
+++ /dev/null
@@ -1,412 +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.
- */
-
-syntax = "proto2";
-
-package clearcut.connectivity;
-
-option java_package = "com.android.server.wifi";
-option java_outer_classname = "WifiMetricsProto";
-
-// The information about the Wifi events.
-message WifiLog {
-
-  // Session information that gets logged for every Wifi connection.
-  repeated ConnectionEvent connection_event = 1;
-
-  // Number of saved networks in the user profile.
-  optional int32 num_saved_networks = 2;
-
-  // Number of open networks in the saved networks.
-  optional int32 num_open_networks = 3;
-
-  // Number of personal networks.
-  optional int32 num_personal_networks = 4;
-
-  // Number of enterprise networks.
-  optional int32 num_enterprise_networks = 5;
-
-  // Does the user have location setting enabled.
-  optional bool is_location_enabled = 6;
-
-  // Does the user have scanning enabled.
-  optional bool is_scanning_always_enabled = 7;
-
-  // Number of times user toggled wifi using the settings menu.
-  optional int32 num_wifi_toggled_via_settings = 8;
-
-  // Number of times user toggled wifi using the airplane menu.
-  optional int32 num_wifi_toggled_via_airplane = 9;
-
-  // Number of networks added by the user.
-  optional int32 num_networks_added_by_user = 10;
-
-  // Number of networks added by applications.
-  optional int32 num_networks_added_by_apps = 11;
-
-  // Number scans that returned empty results.
-  optional int32 num_empty_scan_results = 12;
-
-  // Number scans that returned at least one result.
-  optional int32 num_non_empty_scan_results = 13;
-
-  // Number of scans that were one time.
-  optional int32 num_oneshot_scans = 14;
-
-  // Number of repeated background scans that were scheduled to the chip.
-  optional int32 num_background_scans = 15;
-
-  // Error codes that a scan can result in.
-  enum ScanReturnCode {
-
-    // Return Code is unknown.
-    SCAN_UNKNOWN = 0;
-
-    // Scan was successful.
-    SCAN_SUCCESS = 1;
-
-    // Scan was successfully started, but was interrupted.
-    SCAN_FAILURE_INTERRUPTED = 2;
-
-    //  Scan failed to start because of invalid configuration
-    //  (bad channel, etc).
-    SCAN_FAILURE_INVALID_CONFIGURATION = 3;
-
-    // Could not start a scan because wifi is disabled.
-    FAILURE_WIFI_DISABLED = 4;
-
-  }
-
-  // Mapping of error codes to the number of times that scans resulted
-  // in that error.
-  repeated ScanReturnEntry scan_return_entries = 16;
-
-  message ScanReturnEntry {
-
-     // Return code of the scan.
-     optional ScanReturnCode scan_return_code = 1;
-
-     // Number of entries that were found in the scan.
-     optional int32 scan_results_count = 2;
-  }
-
-  // State of the Wifi.
-  enum WifiState {
-
-    // State is unknown.
-    WIFI_UNKNOWN = 0;
-
-    // Wifi is disabled.
-    WIFI_DISABLED = 1;
-
-    // Wifi is enabled.
-    WIFI_DISCONNECTED = 2;
-
-    // Wifi is enabled and associated with an AP.
-    WIFI_ASSOCIATED = 3;
-  }
-
-  // Mapping of system state to the number of times that scans were requested in
-  // that state
-  repeated WifiSystemStateEntry wifi_system_state_entries = 17;
-
-  message WifiSystemStateEntry {
-
-     // Current WiFi state.
-     optional WifiState wifi_state = 1;
-
-     // Count of scans in state.
-     optional int32 wifi_state_count = 2;
-
-     // Is screen on.
-     optional bool is_screen_on = 3;
-  }
-
-  // Mapping of Error/Success codes to the number of background scans that resulted in it
-  repeated ScanReturnEntry background_scan_return_entries = 18;
-
-  // Mapping of system state to the number of times that Background scans were requested in that
-  // state
-  repeated WifiSystemStateEntry background_scan_request_state = 19;
-
-  // Total number of times the Watchdog of Last Resort triggered, resetting the wifi stack
-  optional int32 num_last_resort_watchdog_triggers = 20;
-
-  // Total number of networks over bad association threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_association_networks_total = 21;
-
-  // Total number of networks over bad authentication threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_authentication_networks_total = 22;
-
-  // Total number of networks over bad dhcp threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_dhcp_networks_total = 23;
-
-  // Total number of networks over bad other threshold when watchdog triggered
-  optional int32 num_last_resort_watchdog_bad_other_networks_total = 24;
-
-  // Total count of networks seen when watchdog triggered
-  optional int32 num_last_resort_watchdog_available_networks_total = 25;
-
-  // Total count of triggers with atleast one bad association network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_association = 26;
-
-  // Total count of triggers with atleast one bad authentication network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_authentication = 27;
-
-  // Total count of triggers with atleast one bad dhcp network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_dhcp = 28;
-
-  // Total count of triggers with atleast one bad other network
-  optional int32 num_last_resort_watchdog_triggers_with_bad_other = 29;
-
-  // Count of times connectivity watchdog confirmed pno is working
-  optional int32 num_connectivity_watchdog_pno_good = 30;
-
-  // Count of times connectivity watchdog found pno not working
-  optional int32 num_connectivity_watchdog_pno_bad = 31;
-
-  // Count of times connectivity watchdog confirmed background scan is working
-  optional int32 num_connectivity_watchdog_background_good = 32;
-
-  // Count of times connectivity watchdog found background scan not working
-  optional int32 num_connectivity_watchdog_background_bad = 33;
-
-  // The time duration represented by this wifi log, from start to end of capture
-  optional int32 record_duration_sec = 34;
-
-  // Counts the occurrences of each individual RSSI poll level
-  repeated RssiPollCount rssi_poll_rssi_count = 35;
-
-  // Total number of times WiFi connected immediately after a Last Resort Watchdog trigger,
-  // without new networks becoming available.
-  optional int32 num_last_resort_watchdog_successes = 36;
-
-  // Counts the occurrences of each alert reason.
-  repeated AlertReasonCount alert_reason_count = 47;
-
-  // Total number of saved hidden networks
-  optional int32 num_hidden_networks = 37;
-
-  // Total number of saved passpoint / hotspot 2.0 networks
-  optional int32 num_passpoint_networks = 38;
-
-  // Total number of scan results
-  optional int32 num_total_scan_results = 39;
-
-  // Total number of scan results for open networks
-  optional int32 num_open_network_scan_results = 40;
-
-  // Total number of scan results for personal networks
-  optional int32 num_personal_network_scan_results = 41;
-
-  // Total number of scan results for enterprise networks
-  optional int32 num_enterprise_network_scan_results = 42;
-
-  // Total number of scan results for hidden networks
-  optional int32 num_hidden_network_scan_results = 43;
-
-  // Total number of scan results for hotspot 2.0 r1 networks
-  optional int32 num_hotspot2_r1_network_scan_results = 44;
-
-  // Total number of scan results for hotspot 2.0 r2 networks
-  optional int32 num_hotspot2_r2_network_scan_results = 45;
-
-  // Total number of scans handled by framework (oneshot or otherwise)
-  optional int32 num_scans = 46;
-
-  // Counts the occurrences of each Wifi score
-  repeated WifiScoreCount wifi_score_count = 48;
-}
-
-// Information that gets logged for every WiFi connection.
-message RouterFingerPrint {
-
-  enum RoamType {
-
-    // Type is unknown.
-    ROAM_TYPE_UNKNOWN = 0;
-
-    // No roaming - usually happens on a single band (2.4 GHz) router.
-    ROAM_TYPE_NONE = 1;
-
-    // Enterprise router.
-    ROAM_TYPE_ENTERPRISE = 2;
-
-    // DBDC => Dual Band Dual Concurrent essentially a router that
-    // supports both 2.4 GHz and 5 GHz bands.
-    ROAM_TYPE_DBDC = 3;
-  }
-
-  enum Auth {
-
-    // Auth is unknown.
-    AUTH_UNKNOWN = 0;
-
-    // No authentication.
-    AUTH_OPEN = 1;
-
-    // If the router uses a personal authentication.
-    AUTH_PERSONAL = 2;
-
-    // If the router is setup for enterprise authentication.
-    AUTH_ENTERPRISE = 3;
-  }
-
-  enum RouterTechnology {
-
-    // Router is unknown.
-    ROUTER_TECH_UNKNOWN = 0;
-
-    // Router Channel A.
-    ROUTER_TECH_A = 1;
-
-    // Router Channel B.
-    ROUTER_TECH_B = 2;
-
-    // Router Channel G.
-    ROUTER_TECH_G = 3;
-
-    // Router Channel N.
-    ROUTER_TECH_N = 4;
-
-    // Router Channel AC.
-    ROUTER_TECH_AC = 5;
-
-    // When the channel is not one of the above.
-    ROUTER_TECH_OTHER = 6;
-  }
-
-  optional RoamType roam_type = 1;
-
-  // Channel on which the connection takes place.
-  optional int32 channel_info = 2;
-
-  // DTIM setting of the router.
-  optional int32 dtim = 3;
-
-  // Authentication scheme of the router.
-  optional Auth authentication = 4;
-
-  // If the router is hidden.
-  optional bool hidden = 5;
-
-  // Channel information.
-  optional RouterTechnology router_technology = 6;
-
-  // whether ipv6 is supported.
-  optional bool supports_ipv6 = 7;
-
-  // If the router is a passpoint / hotspot 2.0 network
-  optional bool passpoint = 8;
-}
-
-message ConnectionEvent {
-
-  // Roam Type.
-  enum RoamType {
-
-    // Type is unknown.
-    ROAM_UNKNOWN = 0;
-
-    // No roaming.
-    ROAM_NONE  = 1;
-
-    // DBDC roaming.
-    ROAM_DBDC = 2;
-
-    // Enterprise roaming.
-    ROAM_ENTERPRISE = 3;
-
-    // User selected roaming.
-    ROAM_USER_SELECTED = 4;
-
-    // Unrelated.
-    ROAM_UNRELATED = 5;
-  }
-
-  // Connectivity Level Failure.
-  enum ConnectivityLevelFailure {
-
-    // Failure is unknown.
-    HLF_UNKNOWN = 0;
-
-    // No failure.
-    HLF_NONE = 1;
-
-    // DHCP failure.
-    HLF_DHCP = 2;
-
-    // No internet connection.
-    HLF_NO_INTERNET = 3;
-
-    // No internet connection.
-    HLF_UNWANTED = 4;
-  }
-
-  // Start time of the connection.
-  optional int64 start_time_millis = 1;// [(datapol.semantic_type) = ST_TIMESTAMP];
-
-  // Duration to connect.
-  optional int32 duration_taken_to_connect_millis = 2;
-
-  // Router information.
-  optional RouterFingerPrint router_fingerprint = 3;
-
-  // RSSI at the start of the connection.
-  optional int32 signal_strength = 4;
-
-  // Roam Type.
-  optional RoamType roam_type = 5;
-
-  // Result of the connection.
-  optional int32 connection_result = 6;
-
-  // Reasons for level 2 failure (needs to be coordinated with wpa-supplicant).
-  optional int32 level_2_failure_code = 7;
-
-  // Failures that happen at the connectivity layer.
-  optional ConnectivityLevelFailure connectivity_level_failure_code = 8;
-
-  // Has bug report been taken.
-  optional bool automatic_bug_report_taken = 9;
-}
-
-// Number of occurrences of a specific RSSI poll rssi value
-message RssiPollCount {
-  // RSSI
-  optional int32 rssi = 1;
-
-  // Number of RSSI polls with 'rssi'
-  optional int32 count = 2;
-}
-
-// Number of occurrences of a specific alert reason value
-message AlertReasonCount {
-  // Alert reason
-  optional int32 reason = 1;
-
-  // Number of alerts with |reason|.
-  optional int32 count = 2;
-}
-
-// Counts the number of instances of a specific Wifi Score calculated by WifiScoreReport
-message WifiScoreCount {
-  // Wifi Score
-  optional int32 score = 1;
-
-  // Number of Wifi score reports with this score
-  optional int32 count = 2;
-}
diff --git a/service/wifi-events.rc b/service/wifi-events.rc
new file mode 100644
index 0000000..b4b0934
--- /dev/null
+++ b/service/wifi-events.rc
@@ -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.
+#
+
+on fs
+   setprop sys.wifitracing.started 0
+
+on property:sys.boot_completed=1 && property:sys.wifitracing.started=0
+   # Create trace buffer, and set basic configuration.
+   mkdir /sys/kernel/debug/tracing/instances/wifi 711
+   restorecon_recursive /sys/kernel/debug/tracing/instances/wifi
+   write /sys/kernel/debug/tracing/instances/wifi/tracing_on 0
+   write /sys/kernel/debug/tracing/instances/wifi/buffer_size_kb 1
+   write /sys/kernel/debug/tracing/instances/wifi/trace_options disable_on_free
+
+   # Enable cfg80211 events for connection and key management events.
+   # - Events are not actually logged until WifiService writes "1" to
+   #   /sys/kernel/debug/tracing/instances/wifi/tracing_on.
+   # - WifiService is responsible for turning tracing off and on.
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/cfg80211_gtk_rekey_notify/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/rdev_add_key/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/rdev_assoc/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/rdev_auth/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/rdev_connect/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/rdev_set_default_key/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/rdev_set_default_mgmt_key/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/cfg80211/rdev_set_rekey_data/enable 1
+
+   # Enable datapath events for Wifi.
+   # - Events are not actually logged until WifiService writes "1" to
+   #   /sys/kernel/debug/tracing/instances/wifi/tracing_on.
+   # - WifiService will ensure that tracing is turned back off,
+   #   when a connection attempt ends (whether in success or failure)
+   write /sys/kernel/debug/tracing/instances/wifi/events/net/filter name==${wifi.interface:-wlan0}
+   write /sys/kernel/debug/tracing/instances/wifi/events/net/net_dev_queue/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/net/net_dev_xmit/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/net/netif_rx/enable 1
+   write /sys/kernel/debug/tracing/instances/wifi/events/net/netif_receive_skb/enable 1
+
+   # Set DAC to allow system_server to enable/disable, and read wifi trace
+   # events.
+   chown system /sys/kernel/debug/tracing/instances/wifi/tracing_on
+   chown system /sys/kernel/debug/tracing/instances/wifi/free_buffer
+   chown system /sys/kernel/debug/tracing/instances/wifi/trace
+   chmod 200 /sys/kernel/debug/tracing/instances/wifi/tracing_on
+   chmod 400 /sys/kernel/debug/tracing/instances/wifi/free_buffer
+   chmod 600 /sys/kernel/debug/tracing/instances/wifi/trace
+   setprop sys.wifitracing.started 1
+
+on property:sys.boot_completed=1 && property:wifi.interface=* && sys.wifitracing.started=1
+   # Override default value.
+   write /sys/kernel/debug/tracing/instances/wifi/events/net/filter name==${wifi.interface}
diff --git a/tests/wifitests/Android.mk b/tests/wifitests/Android.mk
index 4904bda..94f3435 100644
--- a/tests/wifitests/Android.mk
+++ b/tests/wifitests/Android.mk
@@ -14,62 +14,13 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-# Make mock HAL library
-# ============================================================
-
-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 += \
-	$(JNI_H_INCLUDE) \
-	$(LOCAL_PATH)/../../service/jni \
-	$(call include-path-for, libhardware)/hardware \
-	$(call include-path-for, libhardware_legacy)/hardware_legacy \
-	packages/apps/Test/connectivity/sl4n/rapidjson/include \
-	libcore/include
-
-LOCAL_SRC_FILES := \
-	jni/wifi_hal_mock.cpp
-
-ifdef INCLUDE_NAN_FEATURE
-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
-
-include $(BUILD_SHARED_LIBRARY)
-
 # Make test APK
 # ============================================================
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
 
-RESOURCE_FILES := $(call all-named-files-under, R.java, $(intermediates.COMMON))
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files) \
-	$RESOURCE_FILES
-
-ifndef INCLUDE_NAN_FEATURE
-LOCAL_SRC_FILES := $(filter-out $(call all-java-files-under, \
-          src/com/android/server/wifi/nan),$(LOCAL_SRC_FILES))
-endif
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
 # Provide jack a list of classes to exclude form code coverage
 # This list is generated from the java source files in this module
@@ -93,11 +44,14 @@
 # These patterns will match all classes in this module and their inner classes.
 jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
 
-jacoco_include := com.android.server.wifi.*,android.net.wifi.*
+jacoco_include := com.android.server.wifi.*
 
 LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include)
 LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
 
+LOCAL_DX_FLAGS := --multi-dex
+LOCAL_JACK_FLAGS := --multi-dex native
+
 # wifi-service and services must be included here so that the latest changes
 # will be used when tests. Otherwise the tests would run against the installed
 # system.
@@ -105,7 +59,8 @@
 # since neither is declared a static java library.
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-test \
-	mockito-target \
+	mockito-target-minus-junit4 \
+	frameworks-base-testutils \
 	services \
 	wifi-service \
 
@@ -113,30 +68,50 @@
 	android.test.runner \
 	wifi-service \
 	services \
+	android.hidl.manager-V1.0-java
 
 # 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-system \
+	libui \
+	libunwind \
+	libutils \
+	libvndksupport \
 
 ifdef WPA_SUPPLICANT_VERSION
 LOCAL_JNI_SHARED_LIBRARIES += libwpa_client
 endif
 
 LOCAL_PACKAGE_NAME := FrameworksWifiTests
-LOCAL_JNI_SHARED_LIBRARIES += libwifi-hal-mock
 
 include $(BUILD_PACKAGE)
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/coverage.sh b/tests/wifitests/coverage.sh
index 3480541..bf50dee 100755
--- a/tests/wifitests/coverage.sh
+++ b/tests/wifitests/coverage.sh
@@ -56,5 +56,4 @@
   --coverage-file $COVERAGE_OUTPUT_FILE \
   --report-type $REPORT_TYPE \
   --source-dir $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/tests/wifitests/src \
-  --source-dir $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/service/java \
-  --source-dir $ANDROID_BUILD_TOP/frameworks/base/wifi/java
+  --source-dir $ANDROID_BUILD_TOP/frameworks/opt/net/wifi/service/java
diff --git a/tests/wifitests/jni/wifi_hal_mock.cpp b/tests/wifitests/jni/wifi_hal_mock.cpp
deleted file mode 100644
index 06b2d23..0000000
--- a/tests/wifitests/jni/wifi_hal_mock.cpp
+++ /dev/null
@@ -1,196 +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 "JniConstants.h"
-#include <ScopedUtfChars.h>
-#include <ScopedBytes.h>
-#include <utils/misc.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/Log.h>
-#include <utils/String16.h>
-#include <ctype.h>
-#include <sys/socket.h>
-#include <linux/if.h>
-#include "wifi.h"
-#include "wifi_hal.h"
-#include "jni_helper.h"
-#include "wifi_hal_mock.h"
-#include <sstream>
-#include <rapidjson/document.h>
-#include <rapidjson/stringbuffer.h>
-#include <rapidjson/writer.h>
-
-namespace android {
-
-jobject mock_mObj; /* saved HalMock object (not class!) */
-JavaVM* mock_mVM = NULL; /* saved JVM pointer */
-
-/* Variable and function declared and defined in:
- *  com_android_server_wifi_nan_WifiNanNative.cpp
- */
-extern wifi_hal_fn hal_fn;
-extern "C" jint Java_com_android_server_wifi_WifiNative_registerNatives(
-    JNIEnv* env, jclass clazz);
-
-namespace hal_json_tags {
-static constexpr const char* const type_tag = "type";
-static constexpr const char* const value_tag = "value";
-
-static constexpr const char* const type_int_tag = "int";
-static constexpr const char* const type_byte_array_tag = "byte_array";
-}
-
-HalMockJsonWriter::HalMockJsonWriter()
-    : allocator(doc.GetAllocator()) {
-  doc.SetObject();
-}
-
-void HalMockJsonWriter::put_int(const char* name, int x) {
-  rapidjson::Value object(rapidjson::kObjectType);
-  object.AddMember(
-      rapidjson::Value(hal_json_tags::type_tag,
-                       strlen(hal_json_tags::type_tag)),
-      rapidjson::Value(hal_json_tags::type_int_tag,
-                       strlen(hal_json_tags::type_int_tag)),
-      allocator);
-  object.AddMember(
-      rapidjson::Value(hal_json_tags::value_tag,
-                       strlen(hal_json_tags::value_tag)),
-      rapidjson::Value(x), allocator);
-  doc.AddMember(rapidjson::Value(name, strlen(name)), object, allocator);
-}
-
-void HalMockJsonWriter::put_byte_array(const char* name, u8* byte_array,
-                                       int array_length) {
-  rapidjson::Value object(rapidjson::kObjectType);
-  object.AddMember(
-      rapidjson::Value(hal_json_tags::type_tag,
-                       strlen(hal_json_tags::type_tag)),
-      rapidjson::Value(hal_json_tags::type_byte_array_tag,
-                       strlen(hal_json_tags::type_byte_array_tag)),
-      allocator);
-
-  rapidjson::Value array(rapidjson::kArrayType);
-  for (int i = 0; i < array_length; ++i) {
-    array.PushBack((int) byte_array[i], allocator);
-  }
-
-  object.AddMember(
-      rapidjson::Value(hal_json_tags::value_tag,
-                       strlen(hal_json_tags::value_tag)),
-      array, allocator);
-  doc.AddMember(rapidjson::Value(name, strlen(name)), object, allocator);
-}
-
-std::string HalMockJsonWriter::to_string() {
-  rapidjson::StringBuffer strbuf;
-  rapidjson::Writer < rapidjson::StringBuffer > writer(strbuf);
-  doc.Accept(writer);
-  return strbuf.GetString();
-}
-
-HalMockJsonReader::HalMockJsonReader(const char* str) {
-  doc.Parse(str);
-  assert(doc.IsObject());
-}
-
-int HalMockJsonReader::get_int(const char* key, bool* error) {
-  if (!doc.HasMember(key)) {
-    *error = true;
-    ALOGE("get_int: can't find %s key", key);
-    return 0;
-  }
-  const rapidjson::Value& element = doc[key];
-  if (!element.HasMember(hal_json_tags::value_tag)) {
-    *error = true;
-    ALOGE("get_int: can't find the 'value' sub-key for %s key", key);
-    return 0;
-  }
-  const rapidjson::Value& value = element[hal_json_tags::value_tag];
-  if (!value.IsInt()) {
-    *error = true;
-    ALOGE("get_int: the value isn't an 'int' for the %s key", key);
-    return 0;
-  }
-  return value.GetInt();
-}
-
-void HalMockJsonReader::get_byte_array(const char* key, bool* error, u8* array,
-                                       unsigned int max_array_size) {
-  if (!doc.HasMember(key)) {
-    *error = true;
-    ALOGE("get_byte_array: can't find %s key", key);
-    return;
-  }
-  const rapidjson::Value& element = doc[key];
-  if (!element.HasMember(hal_json_tags::value_tag)) {
-    *error = true;
-    ALOGE("get_byte_array: can't find the 'value' sub-key for %s key", key);
-    return;
-  }
-  const rapidjson::Value& value = element[hal_json_tags::value_tag];
-  if (!value.IsArray()) {
-    *error = true;
-    ALOGE("get_byte_array: the value isn't an 'array' for the %s key", key);
-    return;
-  }
-
-  if (value.Size() > max_array_size) {
-    *error = true;
-    ALOGE("get_byte_array: size of array (%d) is larger than maximum "
-          "allocated (%d)",
-          value.Size(), max_array_size);
-    return;
-  }
-
-  for (unsigned int i = 0; i < value.Size(); ++i) {
-    const rapidjson::Value& item = value[i];
-    if (!item.IsInt()) {
-      *error = true;
-      ALOGE("get_byte_array: the value isn't an 'int' for the %s[%d] key", key,
-            i);
-      return;
-    }
-    array[i] = item.GetInt();
-  }
-}
-
-
-int init_wifi_hal_func_table_mock(wifi_hal_fn *fn) {
-  if (fn == NULL) {
-    return -1;
-  }
-
-  /* TODO: add other Wi-Fi HAL registrations here - once you have mocks */
-
-  return 0;
-}
-
-extern "C" jint Java_com_android_server_wifi_HalMockUtils_initHalMock(
-    JNIEnv* env, jclass clazz) {
-  env->GetJavaVM(&mock_mVM);
-
-  Java_com_android_server_wifi_WifiNative_registerNatives(env, clazz);
-  return init_wifi_hal_func_table_mock(&hal_fn);
-}
-
-extern "C" void Java_com_android_server_wifi_HalMockUtils_setHalMockObject(
-    JNIEnv* env, jclass clazz, jobject hal_mock_object) {
-  mock_mObj = (jobject) env->NewGlobalRef(hal_mock_object);
-}
-
-}  // namespace android
diff --git a/tests/wifitests/jni/wifi_hal_mock.h b/tests/wifitests/jni/wifi_hal_mock.h
deleted file mode 100644
index 2b9cad4..0000000
--- a/tests/wifitests/jni/wifi_hal_mock.h
+++ /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.
- */
-
-#ifndef __WIFI_HAL_MOCK_H__
-#define __WIFI_HAL_MOCK_H__
-
-#include "wifi_hal.h"
-
-#include <rapidjson/document.h>
-#include <rapidjson/stringbuffer.h>
-#include <rapidjson/writer.h>
-
-namespace android {
-
-class HalMockJsonWriter {
- public:
-  HalMockJsonWriter();
-
-  void put_int(const char* name, int x);
-
-  void put_byte_array(const char* name, u8* byte_array, int array_length);
-
-  std::string to_string();
-
- private:
-  rapidjson::Document doc;
-  rapidjson::Document::AllocatorType& allocator;
-};
-
-class HalMockJsonReader {
- public:
-  HalMockJsonReader(const char* str);
-
-  int get_int(const char* key, bool* error);
-
-  void get_byte_array(const char* key, bool* error, u8* array,
-                      unsigned int max_array_size);
- private:
-  rapidjson::Document doc;
-};
-
-/* declare all HAL mock APIs here*/
-wifi_error wifi_nan_enable_request_mock(transaction_id id,
-                                        wifi_interface_handle iface,
-                                        NanEnableRequest* msg);
-wifi_error wifi_nan_disable_request_mock(transaction_id id,
-                                         wifi_interface_handle iface);
-wifi_error wifi_nan_publish_request_mock(transaction_id id,
-                                         wifi_interface_handle iface,
-                                         NanPublishRequest* msg);
-wifi_error wifi_nan_publish_cancel_request_mock(transaction_id id,
-                                                wifi_interface_handle iface,
-                                                NanPublishCancelRequest* msg);
-wifi_error wifi_nan_subscribe_request_mock(transaction_id id,
-                                           wifi_interface_handle iface,
-                                           NanSubscribeRequest* msg);
-wifi_error wifi_nan_subscribe_cancel_request_mock(
-    transaction_id id, wifi_interface_handle iface,
-    NanSubscribeCancelRequest* msg);
-wifi_error wifi_nan_transmit_followup_request_mock(
-    transaction_id id, wifi_interface_handle iface,
-    NanTransmitFollowupRequest* msg);
-wifi_error wifi_nan_stats_request_mock(transaction_id id,
-                                       wifi_interface_handle iface,
-                                       NanStatsRequest* msg);
-wifi_error wifi_nan_config_request_mock(transaction_id id,
-                                        wifi_interface_handle iface,
-                                        NanConfigRequest* msg);
-wifi_error wifi_nan_tca_request_mock(transaction_id id,
-                                     wifi_interface_handle iface,
-                                     NanTCARequest* msg);
-wifi_error wifi_nan_beacon_sdf_payload_request_mock(
-    transaction_id id, wifi_interface_handle iface,
-    NanBeaconSdfPayloadRequest* msg);
-wifi_error wifi_nan_register_handler_mock(wifi_interface_handle iface,
-                                          NanCallbackHandler handlers);
-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);
-
-}  // namespace android
-
-#endif //__WIFI_HAL_MOCK_H__
diff --git a/tests/wifitests/jni/wifi_nan_hal_mock.cpp b/tests/wifitests/jni/wifi_nan_hal_mock.cpp
deleted file mode 100644
index 022fadd..0000000
--- a/tests/wifitests/jni/wifi_nan_hal_mock.cpp
+++ /dev/null
@@ -1,582 +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 "JniConstants.h"
-#include <ScopedUtfChars.h>
-#include <ScopedBytes.h>
-#include <utils/misc.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/Log.h>
-#include <utils/String16.h>
-#include <ctype.h>
-#include <sys/socket.h>
-#include <linux/if.h>
-#include "wifi.h"
-#include "wifi_hal.h"
-#include "jni_helper.h"
-#include "wifi_hal_mock.h"
-#include <sstream>
-#include <rapidjson/document.h>
-#include <rapidjson/stringbuffer.h>
-#include <rapidjson/writer.h>
-
-namespace android {
-
-extern jobject mock_mObj; /* saved NanHalMock object */
-extern JavaVM* mock_mVM; /* saved JVM pointer */
-
-/* Variable and function declared and defined in:
- *  com_android_servier_wifi_nan_WifiNanNative.cpp
- */
-extern wifi_hal_fn hal_fn;
-extern "C" jint Java_com_android_server_wifi_nan_WifiNanNative_registerNanNatives(
-    JNIEnv* env, jclass clazz);
-
-static NanCallbackHandler mCallbackHandlers;
-
-wifi_error wifi_nan_enable_request_mock(transaction_id id,
-                                        wifi_interface_handle iface,
-                                        NanEnableRequest* msg) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_enable_request_mock");
-  HalMockJsonWriter jsonW;
-  jsonW.put_int("master_pref", msg->master_pref);
-  jsonW.put_int("cluster_low", msg->cluster_low);
-  jsonW.put_int("cluster_high", msg->cluster_high);
-  jsonW.put_int("config_support_5g", msg->config_support_5g);
-  jsonW.put_int("support_5g_val", msg->support_5g_val);
-  jsonW.put_int("config_sid_beacon", msg->config_sid_beacon);
-  jsonW.put_int("sid_beacon_val", msg->sid_beacon_val);
-  jsonW.put_int("config_2dot4g_rssi_close", msg->config_2dot4g_rssi_close);
-  jsonW.put_int("rssi_close_2dot4g_val", msg->rssi_close_2dot4g_val);
-  jsonW.put_int("config_2dot4g_rssi_middle", msg->config_2dot4g_rssi_middle);
-  jsonW.put_int("rssi_middle_2dot4g_val", msg->rssi_middle_2dot4g_val);
-  jsonW.put_int("config_2dot4g_rssi_proximity",
-                msg->config_2dot4g_rssi_proximity);
-  jsonW.put_int("rssi_proximity_2dot4g_val", msg->rssi_proximity_2dot4g_val);
-  jsonW.put_int("config_hop_count_limit", msg->config_hop_count_limit);
-  jsonW.put_int("hop_count_limit_val", msg->hop_count_limit_val);
-  jsonW.put_int("config_2dot4g_support", msg->config_2dot4g_support);
-  jsonW.put_int("support_2dot4g_val", msg->support_2dot4g_val);
-  jsonW.put_int("config_2dot4g_beacons", msg->config_2dot4g_beacons);
-  jsonW.put_int("beacon_2dot4g_val", msg->beacon_2dot4g_val);
-  jsonW.put_int("config_2dot4g_sdf", msg->config_2dot4g_sdf);
-  jsonW.put_int("sdf_2dot4g_val", msg->sdf_2dot4g_val);
-  jsonW.put_int("config_5g_beacons", msg->config_5g_beacons);
-  jsonW.put_int("beacon_5g_val", msg->beacon_5g_val);
-  jsonW.put_int("config_5g_sdf", msg->config_5g_sdf);
-  jsonW.put_int("sdf_5g_val", msg->sdf_5g_val);
-  jsonW.put_int("config_5g_rssi_close", msg->config_5g_rssi_close);
-  jsonW.put_int("rssi_close_5g_val", msg->rssi_close_5g_val);
-  jsonW.put_int("config_5g_rssi_middle", msg->config_5g_rssi_middle);
-  jsonW.put_int("rssi_middle_5g_val", msg->rssi_middle_5g_val);
-  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_oui", msg->config_oui);
-  jsonW.put_int("oui_val", msg->oui_val);
-  jsonW.put_int("config_intf_addr", msg->config_intf_addr);
-  jsonW.put_byte_array("intf_addr_val", msg->intf_addr_val, 6);
-  jsonW.put_int("config_cluster_attribute_val",
-                msg->config_cluster_attribute_val);
-  jsonW.put_int("config_scan_params", msg->config_scan_params);
-  jsonW.put_int("scan_params_val.dwell_time.0",
-                msg->scan_params_val.dwell_time[NAN_CHANNEL_24G_BAND]);
-  jsonW.put_int("scan_params_val.dwell_time.1",
-                msg->scan_params_val.dwell_time[NAN_CHANNEL_5G_BAND_LOW]);
-  jsonW.put_int("scan_params_val.dwell_time.2",
-                msg->scan_params_val.dwell_time[NAN_CHANNEL_5G_BAND_HIGH]);
-  jsonW.put_int("scan_params_val.scan_period.0",
-                msg->scan_params_val.scan_period[NAN_CHANNEL_24G_BAND]);
-  jsonW.put_int("scan_params_val.scan_period.0",
-                msg->scan_params_val.scan_period[NAN_CHANNEL_5G_BAND_LOW]);
-  jsonW.put_int("scan_params_val.scan_period.0",
-                msg->scan_params_val.scan_period[NAN_CHANNEL_5G_BAND_HIGH]);
-  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);
-  std::string str = jsonW.to_string();
-
-  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
-
-  helper.callMethod(mock_mObj, "enableHalMockNative", "(SLjava/lang/String;)V",
-                    (short) id, json_write_string.get());
-
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_disable_request_mock(transaction_id id,
-                                         wifi_interface_handle iface) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_disable_request_mock");
-  helper.callMethod(mock_mObj, "disableHalMockNative", "(S)V", (short) id);
-
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_publish_request_mock(transaction_id id,
-                                         wifi_interface_handle iface,
-                                         NanPublishRequest* msg) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_publish_request_mock");
-  HalMockJsonWriter jsonW;
-  jsonW.put_int("publish_id", msg->publish_id);
-  jsonW.put_int("ttl", msg->ttl);
-  jsonW.put_int("publish_type", msg->publish_type);
-  jsonW.put_int("tx_type", msg->tx_type);
-  jsonW.put_int("publish_count", msg->publish_count);
-  jsonW.put_int("service_name_len", msg->service_name_len);
-  jsonW.put_byte_array("service_name", msg->service_name,
-                       msg->service_name_len);
-  jsonW.put_int("publish_match_indicator", msg->publish_match_indicator);
-  jsonW.put_int("service_specific_info_len", msg->service_specific_info_len);
-  jsonW.put_byte_array("service_specific_info", msg->service_specific_info,
-                       msg->service_specific_info_len);
-  jsonW.put_int("rx_match_filter_len", msg->rx_match_filter_len);
-  jsonW.put_byte_array("rx_match_filter", msg->rx_match_filter,
-                       msg->rx_match_filter_len);
-  jsonW.put_int("tx_match_filter_len", msg->tx_match_filter_len);
-  jsonW.put_byte_array("tx_match_filter", msg->tx_match_filter,
-                       msg->tx_match_filter_len);
-  jsonW.put_int("rssi_threshold_flag", msg->rssi_threshold_flag);
-  jsonW.put_int("connmap", msg->connmap);
-  std::string str = jsonW.to_string();
-
-  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
-
-  helper.callMethod(mock_mObj, "publishHalMockNative", "(SLjava/lang/String;)V",
-                    (short) id, json_write_string.get());
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_publish_cancel_request_mock(transaction_id id,
-                                                wifi_interface_handle iface,
-                                                NanPublishCancelRequest* msg) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_publish_cancel_request_mock");
-  HalMockJsonWriter jsonW;
-  jsonW.put_int("publish_id", msg->publish_id);
-  std::string str = jsonW.to_string();
-
-  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
-
-  helper.callMethod(mock_mObj, "publishCancelHalMockNative",
-                    "(SLjava/lang/String;)V", (short) id,
-                    json_write_string.get());
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_subscribe_request_mock(transaction_id id,
-                                           wifi_interface_handle iface,
-                                           NanSubscribeRequest* msg) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_subscribe_request_mock");
-  HalMockJsonWriter jsonW;
-  jsonW.put_int("subscribe_id", msg->subscribe_id);
-  jsonW.put_int("ttl", msg->ttl);
-  jsonW.put_int("period", msg->period);
-  jsonW.put_int("subscribe_type", msg->subscribe_type);
-  jsonW.put_int("serviceResponseFilter", msg->serviceResponseFilter);
-  jsonW.put_int("serviceResponseInclude", msg->serviceResponseInclude);
-  jsonW.put_int("useServiceResponseFilter", msg->useServiceResponseFilter);
-  jsonW.put_int("ssiRequiredForMatchIndication",
-                msg->ssiRequiredForMatchIndication);
-  jsonW.put_int("subscribe_match_indicator", msg->subscribe_match_indicator);
-  jsonW.put_int("subscribe_count", msg->subscribe_count);
-  jsonW.put_int("service_name_len", msg->service_name_len);
-  jsonW.put_byte_array("service_name", msg->service_name,
-                       msg->service_name_len);
-  jsonW.put_int("service_specific_info_len", msg->service_name_len);
-  jsonW.put_byte_array("service_specific_info", msg->service_specific_info,
-                       msg->service_specific_info_len);
-  jsonW.put_int("rx_match_filter_len", msg->rx_match_filter_len);
-  jsonW.put_byte_array("rx_match_filter", msg->rx_match_filter,
-                       msg->rx_match_filter_len);
-  jsonW.put_int("tx_match_filter_len", msg->tx_match_filter_len);
-  jsonW.put_byte_array("tx_match_filter", msg->tx_match_filter,
-                       msg->tx_match_filter_len);
-  jsonW.put_int("rssi_threshold_flag", msg->rssi_threshold_flag);
-  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);
-  std::string str = jsonW.to_string();
-
-  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
-
-  helper.callMethod(mock_mObj, "subscribeHalMockNative",
-                    "(SLjava/lang/String;)V", (short) id,
-                    json_write_string.get());
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_subscribe_cancel_request_mock(
-    transaction_id id, wifi_interface_handle iface,
-    NanSubscribeCancelRequest* msg) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_subscribe_cancel_request_mock");
-  HalMockJsonWriter jsonW;
-  jsonW.put_int("subscribe_id", msg->subscribe_id);
-  std::string str = jsonW.to_string();
-
-  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
-
-  helper.callMethod(mock_mObj, "subscribeCancelHalMockNative",
-                    "(SLjava/lang/String;)V", (short) id,
-                    json_write_string.get());
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_transmit_followup_request_mock(
-    transaction_id id, wifi_interface_handle iface,
-    NanTransmitFollowupRequest* msg) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_transmit_followup_request_mock");
-  HalMockJsonWriter jsonW;
-  jsonW.put_int("publish_subscribe_id", msg->publish_subscribe_id);
-  jsonW.put_int("requestor_instance_id", msg->requestor_instance_id);
-  jsonW.put_byte_array("addr", msg->addr, 6);
-  jsonW.put_int("priority", msg->priority);
-  jsonW.put_int("dw_or_faw", msg->dw_or_faw);
-  jsonW.put_int("service_specific_info_len", msg->service_specific_info_len);
-  jsonW.put_byte_array("service_specific_info", msg->service_specific_info,
-                       msg->service_specific_info_len);
-
-  std::string str = jsonW.to_string();
-
-  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
-
-  helper.callMethod(mock_mObj, "transmitFollowupHalMockNative",
-                    "(SLjava/lang/String;)V", (short) id,
-                    json_write_string.get());
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_stats_request_mock(transaction_id id,
-                                       wifi_interface_handle iface,
-                                       NanStatsRequest* msg) {
-  ALOGD("wifi_nan_stats_request_mock");
-  return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_nan_config_request_mock(transaction_id id,
-                                        wifi_interface_handle iface,
-                                        NanConfigRequest* msg) {
-  ALOGD("wifi_nan_config_request_mock");
-  return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_nan_tca_request_mock(transaction_id id,
-                                     wifi_interface_handle iface,
-                                     NanTCARequest* msg) {
-  ALOGD("wifi_nan_tca_request_mock");
-  return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_nan_beacon_sdf_payload_request_mock(
-    transaction_id id, wifi_interface_handle iface,
-    NanBeaconSdfPayloadRequest* msg) {
-  ALOGD("wifi_nan_beacon_sdf_payload_request_mock");
-  return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_nan_register_handler_mock(wifi_interface_handle iface,
-                                          NanCallbackHandler handlers) {
-  ALOGD("wifi_nan_register_handler_mock");
-  mCallbackHandlers = handlers;
-  return WIFI_SUCCESS;
-}
-
-wifi_error wifi_nan_get_version_mock(wifi_handle handle, NanVersion* version) {
-  ALOGD("wifi_nan_get_version_mock");
-  return WIFI_ERROR_UNINITIALIZED;
-}
-
-wifi_error wifi_nan_get_capabilities_mock(transaction_id id,
-                                          wifi_interface_handle iface) {
-  JNIHelper helper(mock_mVM);
-
-  ALOGD("wifi_nan_get_capabilities_mock");
-
-  helper.callMethod(mock_mObj, "getCapabilitiesHalMockNative", "(S)V",
-                    (short) id);
-  return WIFI_SUCCESS;
-}
-
-// Callbacks
-
-extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callNotifyResponse(
-    JNIEnv* env, jclass clazz, jshort transaction_id,
-    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_callNotifyResponse: '%s'",
-        chars.c_str());
-
-  NanResponseMsg msg;
-  msg.status = (NanStatusType) jsonR.get_int("status", &error);
-  msg.value = jsonR.get_int("value", &error);
-  msg.response_type = (NanResponseType) jsonR.get_int("response_type", &error);
-  if (msg.response_type == NAN_RESPONSE_PUBLISH) {
-    msg.body.publish_response.publish_id = jsonR.get_int(
-        "body.publish_response.publish_id", &error);
-  } else if (msg.response_type == NAN_RESPONSE_SUBSCRIBE) {
-    msg.body.subscribe_response.subscribe_id = jsonR.get_int(
-        "body.subscribe_response.subscribe_id", &error);
-  } else if (msg.response_type == NAN_GET_CAPABILITIES) {
-    msg.body.nan_capabilities.max_concurrent_nan_clusters = jsonR.get_int(
-        "body.nan_capabilities.max_concurrent_nan_clusters", &error);
-    msg.body.nan_capabilities.max_publishes = jsonR.get_int(
-        "body.nan_capabilities.max_publishes", &error);
-    msg.body.nan_capabilities.max_subscribes = jsonR.get_int(
-        "body.nan_capabilities.max_subscribes", &error);
-    msg.body.nan_capabilities.max_service_name_len = jsonR.get_int(
-        "body.nan_capabilities.max_service_name_len", &error);
-    msg.body.nan_capabilities.max_match_filter_len = jsonR.get_int(
-        "body.nan_capabilities.max_match_filter_len", &error);
-    msg.body.nan_capabilities.max_total_match_filter_len = jsonR.get_int(
-        "body.nan_capabilities.max_total_match_filter_len", &error);
-    msg.body.nan_capabilities.max_service_specific_info_len = jsonR.get_int(
-        "body.nan_capabilities.max_service_specific_info_len", &error);
-    msg.body.nan_capabilities.max_vsa_data_len = jsonR.get_int(
-        "body.nan_capabilities.max_vsa_data_len", &error);
-    msg.body.nan_capabilities.max_mesh_data_len = jsonR.get_int(
-        "body.nan_capabilities.max_mesh_data_len", &error);
-    msg.body.nan_capabilities.max_ndi_interfaces = jsonR.get_int(
-        "body.nan_capabilities.max_ndi_interfaces", &error);
-    msg.body.nan_capabilities.max_ndp_sessions = jsonR.get_int(
-        "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);
-  }
-
-  if (error) {
-    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callNotifyResponse: "
-          "error parsing args");
-    return;
-  }
-
-  mCallbackHandlers.NotifyResponse(transaction_id, &msg);
-}
-
-extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callPublishTerminated(
-    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_callPublishTerminated: '%s'",
-      chars.c_str());
-
-  NanPublishTerminatedInd msg;
-  msg.publish_id = jsonR.get_int("publish_id", &error);
-  msg.reason = (NanStatusType) jsonR.get_int("reason", &error);
-
-  if (error) {
-    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callPublishTerminated: "
-          "error parsing args");
-    return;
-  }
-
-  mCallbackHandlers.EventPublishTerminated(&msg);
-}
-
-extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callSubscribeTerminated(
-    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_callSubscribeTerminated: '%s'",
-      chars.c_str());
-
-  NanSubscribeTerminatedInd msg;
-  msg.subscribe_id = jsonR.get_int("subscribe_id", &error);
-  msg.reason = (NanStatusType) jsonR.get_int("reason", &error);
-
-  if (error) {
-    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callSubscribeTerminated:"
-          " error parsing args");
-    return;
-  }
-
-  mCallbackHandlers.EventSubscribeTerminated(&msg);
-}
-
-extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callFollowup(
-    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_callFollowup: '%s'",
-        chars.c_str());
-
-  NanFollowupInd msg;
-  msg.publish_subscribe_id = jsonR.get_int("publish_subscribe_id", &error);
-  msg.requestor_instance_id = jsonR.get_int("requestor_instance_id", &error);
-  jsonR.get_byte_array("addr", &error, msg.addr, NAN_MAC_ADDR_LEN);
-  msg.dw_or_faw = jsonR.get_int("dw_or_faw", &error);
-  msg.service_specific_info_len = jsonR.get_int("service_specific_info_len",
-                                                &error);
-  jsonR.get_byte_array("service_specific_info", &error,
-                       msg.service_specific_info,
-                       NAN_MAX_SERVICE_SPECIFIC_INFO_LEN);
-
-  if (error) {
-    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callFollowup: "
-          "error parsing args");
-    return;
-  }
-
-  mCallbackHandlers.EventFollowup(&msg);
-}
-
-extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callMatch(
-    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_callMatch: '%s'",
-        chars.c_str());
-
-  NanMatchInd msg;
-  msg.publish_subscribe_id = jsonR.get_int("publish_subscribe_id", &error);
-  msg.requestor_instance_id = jsonR.get_int("requestor_instance_id", &error);
-  jsonR.get_byte_array("addr", &error, msg.addr, NAN_MAC_ADDR_LEN);
-  msg.service_specific_info_len = jsonR.get_int("service_specific_info_len",
-                                                &error);
-  jsonR.get_byte_array("service_specific_info", &error,
-                       msg.service_specific_info,
-                       NAN_MAX_SERVICE_SPECIFIC_INFO_LEN);
-  msg.sdf_match_filter_len = jsonR.get_int("sdf_match_filter_len", &error);
-  jsonR.get_byte_array("sdf_match_filter", &error, msg.sdf_match_filter,
-                       NAN_MAX_MATCH_FILTER_LEN);
-  /* a few more fields here - but not used (yet/never?) */
-
-  if (error) {
-    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callMatch: "
-          "error parsing args");
-    return;
-  }
-
-  mCallbackHandlers.EventMatch(&msg);
-}
-
-extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callDiscEngEvent(
-    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_callDiscEngEvent: '%s'",
-        chars.c_str());
-
-  NanDiscEngEventInd msg;
-  msg.event_type = (NanDiscEngEventType) jsonR.get_int("event_type", &error);
-  if (msg.event_type == NAN_EVENT_ID_DISC_MAC_ADDR) {
-    jsonR.get_byte_array("data", &error, msg.data.mac_addr.addr,
-                         NAN_MAC_ADDR_LEN);
-  } else {
-    jsonR.get_byte_array("data", &error, msg.data.cluster.addr,
-                         NAN_MAC_ADDR_LEN);
-  }
-
-  if (error) {
-    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callDiscEngEvent: "
-          "error parsing args");
-    return;
-  }
-
-  mCallbackHandlers.EventDiscEngEvent(&msg);
-}
-
-extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callDisabled(
-    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_callDisabled: '%s'",
-        chars.c_str());
-
-  NanDisabledInd msg;
-  msg.reason = (NanStatusType) jsonR.get_int("reason", &error);
-
-  if (error) {
-    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callDisabled: "
-          "error parsing args");
-    return;
-  }
-
-  mCallbackHandlers.EventDisabled(&msg);
-}
-
-// TODO: Not currently used: add as needed
-//void (*EventUnMatch) (NanUnmatchInd* event);
-//void (*EventTca) (NanTCAInd* event);
-//void (*EventBeaconSdfPayload) (NanBeaconSdfPayloadInd* event);
-
-int init_wifi_nan_hal_func_table_mock(wifi_hal_fn *fn) {
-  if (fn == NULL) {
-    return -1;
-  }
-
-  fn->wifi_nan_enable_request = wifi_nan_enable_request_mock;
-  fn->wifi_nan_disable_request = wifi_nan_disable_request_mock;
-  fn->wifi_nan_publish_request = wifi_nan_publish_request_mock;
-  fn->wifi_nan_publish_cancel_request =
-      wifi_nan_publish_cancel_request_mock;
-  fn->wifi_nan_subscribe_request = wifi_nan_subscribe_request_mock;
-  fn->wifi_nan_subscribe_cancel_request =
-      wifi_nan_subscribe_cancel_request_mock;
-  fn->wifi_nan_transmit_followup_request =
-      wifi_nan_transmit_followup_request_mock;
-  fn->wifi_nan_stats_request = wifi_nan_stats_request_mock;
-  fn->wifi_nan_config_request = wifi_nan_config_request_mock;
-  fn->wifi_nan_tca_request = wifi_nan_tca_request_mock;
-  fn->wifi_nan_beacon_sdf_payload_request =
-      wifi_nan_beacon_sdf_payload_request_mock;
-  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;
-
-  return 0;
-}
-
-extern "C" jint Java_com_android_server_wifi_nan_WifiNanHalMock_initNanHalMock(
-    JNIEnv* env, jclass clazz) {
-  Java_com_android_server_wifi_nan_WifiNanNative_registerNanNatives(env, clazz);
-  return init_wifi_nan_hal_func_table_mock(&hal_fn);
-}
-
-}// namespace android
diff --git a/tests/wifitests/runtests.sh b/tests/wifitests/runtests.sh
new file mode 100755
index 0000000..529a535
--- /dev/null
+++ b/tests/wifitests/runtests.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+if [[ "$1" == "--help" ]]; then
+  cat <<END
+Usage for $0
+
+	<no-args>			run all tests
+	-r				print raw results
+	-e class <class-name>		run all the tests in <class-name>
+	-e class <class-name>#<method>	run just the specified <method>
+
+Example:
+$ $0 -r -e class \\
+  com.android.server.wifi.WifiDiagnosticsTest#startLoggingRegistersLogEventHandler
+Run just the specified test, and show the raw output.
+
+For more options, see https://goo.gl/JxYjIw
+END
+  exit 0
+fi
+
+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 "$@" \
+  -e notAnnotation com.android.server.wifi.DisabledForUpdateToAnyMatcher \
+  'com.android.server.wifi.test/android.support.test.runner.AndroidJUnitRunner'
diff --git a/tests/wifitests/src/android/net/wifi/FakeKeys.java b/tests/wifitests/src/android/net/wifi/FakeKeys.java
deleted file mode 100644
index 8a1e7b4..0000000
--- a/tests/wifitests/src/android/net/wifi/FakeKeys.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package android.net.wifi;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-
-/**
- * A class containing test certificates.
- */
-public class FakeKeys {
-    private static final String CA_CERT0_STRING = "-----BEGIN CERTIFICATE-----\n" +
-            "MIIDKDCCAhCgAwIBAgIJAILlFdwzLVurMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
-            "BAMTB0VBUCBDQTEwHhcNMTYwMTEyMTE1MDE1WhcNMjYwMTA5MTE1MDE1WjASMRAw\n" +
-            "DgYDVQQDEwdFQVAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
-            "znAPUz26Msae4ws43czR41/J2QtrSIZUKmVUsVumDbYHrPNvTXKSMXAcewORDQYX\n" +
-            "RqvHvpn8CscB1+oGXZvHwxj4zV0WKoK2zeXkau3vcyl3HIKupJfq2TEACefVjj0t\n" +
-            "JW+X35PGWp9/H5zIUNVNVjS7Ums84IvKhRB8512PB9UyHagXYVX5GWpAcVpyfrlR\n" +
-            "FI9Qdhh+Pbk0uyktdbf/CdfgHOoebrTtwRljM0oDtX+2Cv6j0wBK7hD8pPvf1+uy\n" +
-            "GzczigAU/4Kw7eZqydf9B+5RupR+IZipX41xEiIrKRwqi517WWzXcjaG2cNbf451\n" +
-            "xpH5PnV3i1tq04jMGQUzFwIDAQABo4GAMH4wHQYDVR0OBBYEFIwX4vs8BiBcScod\n" +
-            "5noZHRM8E4+iMEIGA1UdIwQ7MDmAFIwX4vs8BiBcScod5noZHRM8E4+ioRakFDAS\n" +
-            "MRAwDgYDVQQDEwdFQVAgQ0ExggkAguUV3DMtW6swDAYDVR0TBAUwAwEB/zALBgNV\n" +
-            "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAFfQqOTA7Rv7K+luQ7pnas4BYwHE\n" +
-            "9GEP/uohv6KOy0TGQFbrRTjFoLVNB9BZ1ymMDZ0/TIwIUc7wi7a8t5mEqYH153wW\n" +
-            "aWooiSjyLLhuI4sNrNCOtisdBq2r2MFXt6h0mAQYOPv8R8K7/fgSxGFqzhyNmmVL\n" +
-            "1qBJldx34SpwsTALQVPb4hGwJzZfr1PcpEQx6xMnTl8xEWZE3Ms99uaUxbQqIwRu\n" +
-            "LgAOkNCmY2m89VhzaHJ1uV85AdM/tD+Ysmlnnjt9LRCejbBipjIGjOXrg1JP+lxV\n" +
-            "muM4vH+P/mlmxsPPz0d65b+EGmJZpoLkO/tdNNvCYzjJpTEWpEsO6NMhKYo=\n" +
-            "-----END CERTIFICATE-----\n";
-    public static final X509Certificate CA_CERT0 = loadCertificate(CA_CERT0_STRING);
-
-    private static final String CA_CERT1_STRING = "-----BEGIN CERTIFICATE-----\n" +
-            "MIIDKDCCAhCgAwIBAgIJAOM5SzKO2pzCMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
-            "BAMTB0VBUCBDQTAwHhcNMTYwMTEyMDAxMDQ3WhcNMjYwMTA5MDAxMDQ3WjASMRAw\n" +
-            "DgYDVQQDEwdFQVAgQ0EwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
-            "89ug+IEKVQXnJGKg5g4uVHg6J/8iRUxR5k2eH5o03hrJNMfN2D+cBe/wCiZcnWbI\n" +
-            "GbGZACWm2nQth2wy9Zgm2LOd3b4ocrHYls3XLq6Qb5Dd7a0JKU7pdGufiNVEkrmF\n" +
-            "EB+N64wgwH4COTvCiN4erp5kyJwkfqAl2xLkZo0C464c9XoyQOXbmYD9A8v10wZu\n" +
-            "jyNsEo7Nr2USyw+qhjWSbFbEirP77Tvx+7pJQJwdtk1V9Tn73T2dGF2WHYejei9S\n" +
-            "mcWpdIUqsu9etYH+zDmtu7I1xlkwiaVsNr2+D+qaCJyOYqrDTKVNK5nmbBPXDWZc\n" +
-            "NoDbTOoqquX7xONpq9M6jQIDAQABo4GAMH4wHQYDVR0OBBYEFAZ3A2S4qJZZwuNY\n" +
-            "wkJ6mAdc0gVdMEIGA1UdIwQ7MDmAFAZ3A2S4qJZZwuNYwkJ6mAdc0gVdoRakFDAS\n" +
-            "MRAwDgYDVQQDEwdFQVAgQ0EwggkA4zlLMo7anMIwDAYDVR0TBAUwAwEB/zALBgNV\n" +
-            "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAHmdMwEhtys4d0E+t7owBmoVR+lU\n" +
-            "hMCcRtWs8YKX5WIM2kTweT0h/O1xwE1mWmRv/IbDAEb8od4BjAQLhIcolStr2JaO\n" +
-            "9ZzyxjOnNzqeErh/1DHDbb/moPpqfeJ8YiEz7nH/YU56Q8iCPO7TsgS0sNNE7PfN\n" +
-            "IUsBW0yHRgpQ4OxWmiZG2YZWiECRzAC0ecPzo59N5iH4vLQIMTMYquiDeMPQnn1e\n" +
-            "NDGxG8gCtDKIaS6tMg3a28MvWB094pr2ETou8O1C8Ji0Y4hE8QJmSdT7I4+GZjgW\n" +
-            "g94DZ5RiL7sdp3vC48CXOmeT61YBIvhGUsE1rPhXqkpqQ3Z3C4TFF0jXZZc=\n" +
-            "-----END CERTIFICATE-----\n";
-    public static final X509Certificate CA_CERT1 = loadCertificate(CA_CERT1_STRING);
-
-
-    private static X509Certificate loadCertificate(String blob) {
-        try {
-            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
-            InputStream stream = new ByteArrayInputStream(blob.getBytes(StandardCharsets.UTF_8));
-
-            return (X509Certificate) certFactory.generateCertificate(stream);
-        } catch (Exception e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-}
diff --git a/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
deleted file mode 100644
index 75480b5..0000000
--- a/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ /dev/null
@@ -1,265 +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 android.net.wifi;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.net.wifi.WifiEnterpriseConfig.Eap;
-import android.net.wifi.WifiEnterpriseConfig.Phase2;
-import android.os.Parcel;
-import android.security.Credentials;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.security.cert.X509Certificate;
-
-
-/**
- * Unit tests for {@link android.net.wifi.WifiEnterpriseConfig}.
- */
-@SmallTest
-public class WifiEnterpriseConfigTest {
-    // Maintain a ground truth of the keystore uri prefix which is expected by wpa_supplicant.
-    public static final String KEYSTORE_URI = "keystore://";
-    public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
-    public static final String KEYSTORES_URI = "keystores://";
-
-    private WifiEnterpriseConfig mEnterpriseConfig;
-
-    @Before
-    public void setUp() throws Exception {
-        mEnterpriseConfig = new WifiEnterpriseConfig();
-    }
-
-    @Test
-    public void testSetGetSingleCaCertificate() {
-        X509Certificate cert0 = FakeKeys.CA_CERT0;
-        mEnterpriseConfig.setCaCertificate(cert0);
-        assertEquals(mEnterpriseConfig.getCaCertificate(), cert0);
-    }
-
-    @Test
-    public void testSetGetMultipleCaCertificates() {
-        X509Certificate cert0 = FakeKeys.CA_CERT0;
-        X509Certificate cert1 = FakeKeys.CA_CERT1;
-        mEnterpriseConfig.setCaCertificates(new X509Certificate[] {cert0, cert1});
-        X509Certificate[] result = mEnterpriseConfig.getCaCertificates();
-        assertEquals(result.length, 2);
-        assertTrue(result[0] == cert0 && result[1] == cert1);
-    }
-
-    @Test
-    public void testSaveSingleCaCertificateAlias() {
-        final String alias = "single_alias 0";
-        mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
-        assertEquals(getCaCertField(), CA_CERT_PREFIX + alias);
-    }
-
-    @Test
-    public void testLoadSingleCaCertificateAlias() {
-        final String alias = "single_alias 1";
-        setCaCertField(CA_CERT_PREFIX + alias);
-        String[] aliases = mEnterpriseConfig.getCaCertificateAliases();
-        assertEquals(aliases.length, 1);
-        assertEquals(aliases[0], alias);
-    }
-
-    @Test
-    public void testSaveMultipleCaCertificates() {
-        final String alias0 = "single_alias 0";
-        final String alias1 = "single_alias 1";
-        mEnterpriseConfig.setCaCertificateAliases(new String[] {alias0, alias1});
-        assertEquals(getCaCertField(), String.format("%s%s %s",
-                KEYSTORES_URI,
-                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias0),
-                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias1)));
-    }
-
-    @Test
-    public void testLoadMultipleCaCertificates() {
-        final String alias0 = "single_alias 0";
-        final String alias1 = "single_alias 1";
-        setCaCertField(String.format("%s%s %s",
-                KEYSTORES_URI,
-                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias0),
-                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias1)));
-        String[] aliases = mEnterpriseConfig.getCaCertificateAliases();
-        assertEquals(aliases.length, 2);
-        assertEquals(aliases[0], alias0);
-        assertEquals(aliases[1], alias1);
-    }
-
-    private String getCaCertField() {
-        return mEnterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, "");
-    }
-
-    private void setCaCertField(String value) {
-        mEnterpriseConfig.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, value);
-    }
-
-    // Retrieves the value for a specific key supplied to wpa_supplicant.
-    private class SupplicantConfigExtractor implements WifiEnterpriseConfig.SupplicantSaver {
-        private String mValue = null;
-        private String mKey;
-
-        SupplicantConfigExtractor(String key) {
-            mKey = key;
-        }
-
-        @Override
-        public boolean saveValue(String key, String value) {
-            if (key.equals(mKey)) {
-                mValue = value;
-            }
-            return true;
-        }
-
-        public String getValue() {
-            return mValue;
-        }
-    }
-
-    private String getSupplicantEapMethod() {
-        SupplicantConfigExtractor entryExtractor = new SupplicantConfigExtractor(
-                WifiEnterpriseConfig.EAP_KEY);
-        mEnterpriseConfig.saveToSupplicant(entryExtractor);
-        return entryExtractor.getValue();
-    }
-
-    private String getSupplicantPhase2Method() {
-        SupplicantConfigExtractor entryExtractor = new SupplicantConfigExtractor(
-                WifiEnterpriseConfig.PHASE2_KEY);
-        mEnterpriseConfig.saveToSupplicant(entryExtractor);
-        return entryExtractor.getValue();
-    }
-
-    /** Verifies the default value for EAP outer and inner methods */
-    @Test
-    public void eapInnerDefault() {
-        assertEquals(null, getSupplicantEapMethod());
-        assertEquals(null, getSupplicantPhase2Method());
-    }
-
-    /** Verifies that the EAP inner method is reset when we switch to TLS */
-    @Test
-    public void eapPhase2MethodForTls() {
-        // Initially select an EAP method that supports an phase2.
-        mEnterpriseConfig.setEapMethod(Eap.PEAP);
-        mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
-        assertEquals("PEAP", getSupplicantEapMethod());
-        assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
-
-        // Change the EAP method to another type which supports a phase2.
-        mEnterpriseConfig.setEapMethod(Eap.TTLS);
-        assertEquals("TTLS", getSupplicantEapMethod());
-        assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
-
-        // Change the EAP method to TLS which does not support a phase2.
-        mEnterpriseConfig.setEapMethod(Eap.TLS);
-        assertEquals(null, getSupplicantPhase2Method());
-    }
-
-    /** Verfies that the EAP inner method is reset when we switch phase2 to NONE */
-    @Test
-    public void eapPhase2None() {
-        // Initially select an EAP method that supports an phase2.
-        mEnterpriseConfig.setEapMethod(Eap.PEAP);
-        mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
-        assertEquals("PEAP", getSupplicantEapMethod());
-        assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
-
-        // Change the phase2 method to NONE and ensure the value is cleared.
-        mEnterpriseConfig.setPhase2Method(Phase2.NONE);
-        assertEquals(null, getSupplicantPhase2Method());
-    }
-
-    /** Verfies that the correct "autheap" parameter is supplied for TTLS/GTC. */
-    @Test
-    public void peapGtcToTtls() {
-        mEnterpriseConfig.setEapMethod(Eap.PEAP);
-        mEnterpriseConfig.setPhase2Method(Phase2.GTC);
-        assertEquals("PEAP", getSupplicantEapMethod());
-        assertEquals("\"auth=GTC\"", getSupplicantPhase2Method());
-
-        mEnterpriseConfig.setEapMethod(Eap.TTLS);
-        assertEquals("TTLS", getSupplicantEapMethod());
-        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
-    }
-
-    /** Verfies that the correct "auth" parameter is supplied for PEAP/GTC. */
-    @Test
-    public void ttlsGtcToPeap() {
-        mEnterpriseConfig.setEapMethod(Eap.TTLS);
-        mEnterpriseConfig.setPhase2Method(Phase2.GTC);
-        assertEquals("TTLS", getSupplicantEapMethod());
-        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
-
-        mEnterpriseConfig.setEapMethod(Eap.PEAP);
-        assertEquals("PEAP", getSupplicantEapMethod());
-        assertEquals("\"auth=GTC\"", getSupplicantPhase2Method());
-    }
-
-    /** Verfies that the copy constructor preseves the inner method information. */
-    @Test
-    public void copyConstructor() {
-        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
-        enterpriseConfig.setEapMethod(Eap.TTLS);
-        enterpriseConfig.setPhase2Method(Phase2.GTC);
-        mEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
-        assertEquals("TTLS", getSupplicantEapMethod());
-        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
-    }
-
-    /** Verfies that parceling a WifiEnterpriseConfig preseves method information. */
-    @Test
-    public void parcelConstructor() {
-        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
-        enterpriseConfig.setEapMethod(Eap.TTLS);
-        enterpriseConfig.setPhase2Method(Phase2.GTC);
-        Parcel parcel = Parcel.obtain();
-        enterpriseConfig.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);  // Allow parcel to be read from the beginning.
-        mEnterpriseConfig = WifiEnterpriseConfig.CREATOR.createFromParcel(parcel);
-        assertEquals("TTLS", getSupplicantEapMethod());
-        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
-    }
-
-    /** Verifies proper operation of the getKeyId() method. */
-    @Test
-    public void getKeyId() {
-        assertEquals("NULL", mEnterpriseConfig.getKeyId(null));
-        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
-        enterpriseConfig.setEapMethod(Eap.TTLS);
-        enterpriseConfig.setPhase2Method(Phase2.GTC);
-        assertEquals("TTLS_GTC", mEnterpriseConfig.getKeyId(enterpriseConfig));
-        mEnterpriseConfig.setEapMethod(Eap.PEAP);
-        mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
-        assertEquals("PEAP_MSCHAPV2", mEnterpriseConfig.getKeyId(enterpriseConfig));
-    }
-
-    /** Verifies that passwords are not displayed in toString. */
-    @Test
-    public void passwordNotInToString() {
-        String password = "supersecret";
-        mEnterpriseConfig.setPassword(password);
-        assertFalse(mEnterpriseConfig.toString().contains(password));
-    }
-}
diff --git a/tests/wifitests/src/android/net/wifi/WifiScannerTest.java b/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
deleted file mode 100644
index 5034c2a..0000000
--- a/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
+++ /dev/null
@@ -1,111 +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 android.net.wifi;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.validateMockitoUsage;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.net.wifi.WifiScanner.BssidInfo;
-import android.net.wifi.WifiScanner.BssidListener;
-import android.os.Handler;
-import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.wifi.BidirectionalAsyncChannelServer;
-import com.android.server.wifi.MockLooper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Unit tests for {@link android.net.wifi.WifiScanner}.
- */
-@SmallTest
-public class WifiScannerTest {
-    @Mock
-    private Context mContext;
-    @Mock
-    private IWifiScanner mService;
-    @Mock
-    private BssidListener mBssidListener;
-
-    private WifiScanner mWifiScanner;
-    private MockLooper mLooper;
-    private Handler mHandler;
-
-    /**
-     * Setup before tests.
-     */
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mLooper = new MockLooper();
-        mHandler = mock(Handler.class);
-        BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
-                mContext, mLooper.getLooper(), mHandler);
-        when(mService.getMessenger()).thenReturn(server.getMessenger());
-        mWifiScanner = new WifiScanner(mContext, mService, mLooper.getLooper());
-        mLooper.dispatchAll();
-    }
-
-    /**
-     * Clean up after tests.
-     */
-    @After
-    public void cleanup() {
-        validateMockitoUsage();
-    }
-
-    private void verifySetHotlistMessage(Handler handler) {
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(handler, atLeastOnce()).handleMessage(messageCaptor.capture());
-        assertEquals("message.what is not CMD_SET_HOTLIST",
-                WifiScanner.CMD_SET_HOTLIST,
-                messageCaptor.getValue().what);
-    }
-
-    /**
-     * Test duplicate listeners for bssid tracking.
-     */
-    @Test
-    public void testStartTrackingBssidsDuplicateListeners() throws Exception {
-        BssidInfo[] bssids = new BssidInfo[] {
-                new BssidInfo()
-        };
-
-        // First start tracking succeeds.
-        mWifiScanner.startTrackingBssids(bssids, -100, mBssidListener);
-        mLooper.dispatchAll();
-        verifySetHotlistMessage(mHandler);
-
-        // Second start tracking should fail.
-        mWifiScanner.startTrackingBssids(bssids, -100, mBssidListener);
-        mLooper.dispatchAll();
-        verify(mBssidListener).onFailure(eq(WifiScanner.REASON_DUPLICATE_REQEUST), anyString());
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/AnqpCacheTest.java b/tests/wifitests/src/com/android/server/wifi/AnqpCacheTest.java
deleted file mode 100644
index 1a96831..0000000
--- a/tests/wifitests/src/com/android/server/wifi/AnqpCacheTest.java
+++ /dev/null
@@ -1,236 +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 static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiSsid;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import com.android.server.wifi.anqp.ANQPElement;
-import com.android.server.wifi.anqp.Constants;
-import com.android.server.wifi.hotspot2.ANQPData;
-import com.android.server.wifi.hotspot2.AnqpCache;
-import com.android.server.wifi.hotspot2.NetworkDetail;
-
-import org.junit.Test;
-import org.mockito.Mock;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-
-/**
- * Unit tests for {@link com.android.server.wifi.hotspot2.AnqpCache}.
- */
-@SmallTest
-public class AnqpCacheTest {
-
-    private static final String TAG = "AnqpCacheTest";
-
-    private static class NetworkDescription {
-        ScanDetail[] mScanDetails;
-        static int[] sChannels = new int[]{2412, 2437, 2462, 5180, 5220, 5745, 5825};
-        static int[] sRSSIs = new int[]{ -50, -80, -60, -80, -55, -90, -75};
-
-        NetworkDescription(String ssid, String bssidPrefix) {
-            WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
-            mScanDetails = new ScanDetail[sChannels.length];
-            for (int i = 0; i < sChannels.length; i++) {
-                String bssid = String.format("%s:%02x", bssidPrefix, i);
-                ScanResult.InformationElement[] ie = new ScanResult.InformationElement[1];
-                ie[0] = ScanResults.generateSsidIe(ssid);
-                List<String> anqpLines = new ArrayList<String>();
-                NetworkDetail nd = new NetworkDetail(bssid, ie,
-                        new ArrayList<String>(), sChannels[i]);
-                mScanDetails[i] = new ScanDetail(nd, wifiSsid,
-                        bssid, "", sRSSIs[i], sChannels[i], Long.MAX_VALUE, ie, anqpLines);
-            }
-        }
-    }
-
-    private static final String ATT_SSID         = "att_wifi";
-    private static final String ATT_BSSID_PREFIX = "aa:44:bb:55:cc";
-    private static final String TWC_SSID         = "TWCWIFI";
-    private static final String TWC_BSSID_PREFIX = "11:aa:22:bb:33";
-
-    private static ScanDetail[] getAttWifiNetworkDescription() {
-        NetworkDescription network = new NetworkDescription(ATT_SSID, ATT_BSSID_PREFIX);
-        return network.mScanDetails;
-    }
-
-    private static ScanDetail[] getTwcWifiNetworkDescription() {
-        NetworkDescription network = new NetworkDescription(TWC_SSID, TWC_BSSID_PREFIX);
-        return network.mScanDetails;
-    }
-
-    private static List<Constants.ANQPElementType> buildQueryList() {
-        List<Constants.ANQPElementType> list = Arrays.asList(
-                Constants.ANQPElementType.class.getEnumConstants());
-        return list;
-    }
-
-    private static Map<Constants.ANQPElementType, ANQPElement> buildAnqpResult() {
-        Map<Constants.ANQPElementType, ANQPElement> elements = new HashMap<>();
-        List<Constants.ANQPElementType> list = Arrays.asList(
-                Constants.ANQPElementType.class.getEnumConstants());
-        for (final Constants.ANQPElementType type : list) {
-            ANQPElement element = new ANQPElement(type) {
-                @Override
-                public Constants.ANQPElementType getID() {
-                    return super.getID();
-                }
-            };
-            elements.put(type, element);
-        }
-
-        return elements;
-    }
-
-    private void advanceTimeAndTrimCache(long howManyMillis) {
-        mCurrentTimeMillis += howManyMillis;
-        Log.d(TAG, "Time set to " + mCurrentTimeMillis);
-        when(mClock.currentTimeMillis()).thenReturn(mCurrentTimeMillis);
-        mCache.clear(false, true);
-    }
-
-    public AnqpCacheTest() {}
-
-    private static final long SECOND_MS = 1000;
-    private static final long MINUTE_MS = 60 * SECOND_MS;
-
-    @Mock Clock mClock;
-    long mCurrentTimeMillis = 1000000000;
-    AnqpCache mCache;
-
-    /** verify that ANQP data is cached per the (rather abstract) spec */
-    @Test
-    public void basicAddQueryAndExpiry() {
-        initMocks(this);
-
-        AnqpCache cache = mCache = new AnqpCache(mClock);
-        advanceTimeAndTrimCache(0);
-
-        List<Constants.ANQPElementType> queryList = buildQueryList();
-
-        ScanDetail[] attScanDetails = getAttWifiNetworkDescription();
-        ScanDetail[] twcScanDetails = getTwcWifiNetworkDescription();
-
-        /* query att network at time 0 */
-        for (ScanDetail scanDetail : attScanDetails) {
-            cache.initiate(scanDetail.getNetworkDetail(), queryList);
-        }
-
-        /* verify that no data can be returned */
-        for (ScanDetail scanDetail : attScanDetails) {
-            ANQPData data = cache.getEntry(scanDetail.getNetworkDetail());
-            assertNull(data);
-        }
-
-        /* update ANQP results after 1 min */
-        advanceTimeAndTrimCache(1 * MINUTE_MS);
-
-        Map<Constants.ANQPElementType, ANQPElement> anqpResults = buildAnqpResult();
-
-        for (ScanDetail scanDetail : attScanDetails) {
-            cache.update(scanDetail.getNetworkDetail(), anqpResults);
-        }
-
-        /* check ANQP results after another 1 min */
-        advanceTimeAndTrimCache(1 * MINUTE_MS);
-
-        for (ScanDetail scanDetail : attScanDetails) {
-            ANQPData data = cache.getEntry(scanDetail.getNetworkDetail());
-            assertNotNull(data);
-            NetworkDetail nd = data.getNetwork();
-            Map<Constants.ANQPElementType, ANQPElement> anqp = data.getANQPElements();
-            assertEquals(scanDetail.getBSSIDString(), nd.getBSSIDString());
-            assertEquals(anqpResults.size(), anqp.size());
-        }
-
-        /* query ANQP results for twcwifi after another 10 min */
-        advanceTimeAndTrimCache(10 * MINUTE_MS);
-
-        for (ScanDetail scanDetail : twcScanDetails) {
-            cache.initiate(scanDetail.getNetworkDetail(), queryList);
-        }
-
-        /* update ANQP results for twcwifi after another 10 min */
-        advanceTimeAndTrimCache(1 * MINUTE_MS);
-
-        for (ScanDetail scanDetail : twcScanDetails) {
-            cache.update(scanDetail.getNetworkDetail(), anqpResults);
-        }
-
-        /* check all results after 1 minute */
-        advanceTimeAndTrimCache(1 * MINUTE_MS);
-
-        for (ScanDetail scanDetail : attScanDetails) {
-            ANQPData data = cache.getEntry(scanDetail.getNetworkDetail());
-            assertNull(data);
-        }
-
-        for (ScanDetail scanDetail : twcScanDetails) {
-            ANQPData data = cache.getEntry(scanDetail.getNetworkDetail());
-            assertNotNull(data);
-            NetworkDetail nd = data.getNetwork();
-            Map<Constants.ANQPElementType, ANQPElement> anqp = data.getANQPElements();
-            assertEquals(scanDetail.getBSSIDString(), nd.getBSSIDString());
-            assertEquals(anqpResults.size(), anqp.size());
-        }
-    }
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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/ByteBufferReaderTest.java b/tests/wifitests/src/com/android/server/wifi/ByteBufferReaderTest.java
new file mode 100644
index 0000000..16cd648
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/ByteBufferReaderTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.ByteBufferReader}.
+ */
+@SmallTest
+public class ByteBufferReaderTest {
+    /**
+     * Verify that BufferUnderflowException will be thrown when reading an integer from a buffer
+     * that contained less data than needed.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void readIntegerWithBufferUnderflow() throws Exception {
+        byte[] data = new byte[1];
+        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+        ByteBufferReader.readInteger(buffer, buffer.order(), 2);
+    }
+
+    /**
+     * Verify that IllegalArgumentException will be thrown when reading an integer that exceeds
+     * the maximum integer size.
+     *
+     * @throws Exception
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void readIntegerExceedingMaximumLength() throws Exception {
+        int length = ByteBufferReader.MAXIMUM_INTEGER_SIZE + 1;
+        byte[] data = new byte[length];
+        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+        ByteBufferReader.readInteger(buffer, buffer.order(), length);
+    }
+
+    /**
+     * Verify that IllegalArgumentException will be thrown when reading an integer with size
+     * less than the minimum.
+     *
+     * @throws Exception
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void readIntegerLessThanMinimumLength() throws Exception {
+        int length = ByteBufferReader.MINIMUM_INTEGER_SIZE - 1;
+        byte[] data = new byte[length];
+        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+        ByteBufferReader.readInteger(buffer, buffer.order(), length);
+    }
+
+    /**
+     * Verify that the expected integer value is returned when reading an integer with minimum
+     * integer size.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void readIntegerWithMinimumSize() throws Exception {
+        byte[] data = new byte[] {0x1};
+        ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+        assertEquals(1, ByteBufferReader.readInteger(buffer, buffer.order(),
+                ByteBufferReader.MINIMUM_INTEGER_SIZE));
+    }
+
+    /**
+     * Verify that the expected integer value is returned when reading an integer with maximum
+     * integer size.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void readIntegerWithMaximumSize() throws Exception {
+        byte[] data = new byte[] {0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11};
+
+        // Little Endian parsing.
+        ByteBuffer leBuffer = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
+        long leValue = 0x110000000000001fL;
+        assertEquals(leValue, ByteBufferReader.readInteger(leBuffer, leBuffer.order(),
+                ByteBufferReader.MAXIMUM_INTEGER_SIZE));
+
+        // Big Endian parsing.
+        ByteBuffer beBuffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
+        long beValue = 0x1f00000000000011L;
+        assertEquals(beValue, ByteBufferReader.readInteger(beBuffer, beBuffer.order(),
+                ByteBufferReader.MAXIMUM_INTEGER_SIZE));
+    }
+
+    /**
+     * Verify that NegativeArraySizeException will be thrown when attempting to read a string with
+     * negative size.
+     *
+     * @throws Exception
+     */
+    @Test(expected = NegativeArraySizeException.class)
+    public void readStringWithNegativeSize() throws Exception {
+        ByteBufferReader.readString(ByteBuffer.wrap(new byte[10]), -1, StandardCharsets.US_ASCII);
+    }
+
+    /**
+     * Verify that an empty String will be returned when reading a string with zero size.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void readStringWithZeroSize() throws Exception {
+        String str = ByteBufferReader.readString(
+                ByteBuffer.wrap(new byte[10]), 0, StandardCharsets.US_ASCII);
+        assertTrue(str.isEmpty());
+    }
+
+    /**
+     * Verify that the expected string value is returned when reading a string from a buffer that
+     * contained a valid string.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void readString() throws Exception {
+        String expectedValue = "Hello World";
+        ByteBuffer buffer = ByteBuffer.wrap(expectedValue.getBytes(StandardCharsets.US_ASCII));
+        String actualValue = ByteBufferReader.readString(
+                buffer, buffer.remaining(), StandardCharsets.US_ASCII);
+        assertEquals(expectedValue, actualValue);
+    }
+
+    /**
+     * Verify that the expected string value is returned when reading a buffer that contained the
+     * size of the string and the string value.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void readStringWithByteLength() throws Exception {
+        String expectedValue = "Hello World";
+        ByteBuffer buffer = ByteBuffer.allocate(expectedValue.length() + 1);
+        buffer.put((byte) expectedValue.length());
+        buffer.put(expectedValue.getBytes(StandardCharsets.US_ASCII));
+        // Rewind the buffer's position to the beginning for reading.
+        buffer.position(0);
+        String actualValue =
+                ByteBufferReader.readStringWithByteLength(buffer, StandardCharsets.US_ASCII);
+        assertEquals(expectedValue, actualValue);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
index 328feaf..6827d95 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;
@@ -91,18 +90,15 @@
     }
 
     public void switchUser(int newUserId) {
-        Set<WifiConfiguration> hiddenConfigurations = new HashSet<>();
-        for (WifiConfiguration config : mConfigs.valuesForAllUsers()) {
-            if (WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                    USER_PROFILES.get(mCurrentUserId))
-                    && !WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                            USER_PROFILES.get(newUserId))) {
-                hiddenConfigurations.add(config);
-            }
-        }
-
         mCurrentUserId = newUserId;
-        assertEquals(hiddenConfigurations, new HashSet<>(mConfigs.handleUserSwitch(newUserId)));
+        mConfigs.setNewUser(newUserId);
+        mConfigs.clear();
+    }
+
+    public void addNetworks(List<WifiConfiguration> configs) {
+        for (WifiConfiguration config : configs) {
+            assertNull(mConfigs.put(config));
+        }
     }
 
     public void verifyGetters(List<WifiConfiguration> configs) {
@@ -125,8 +121,6 @@
             }
 
             assertEquals(config, mConfigs.getForAllUsers(config.networkId));
-            assertEquals(config,
-                    mConfigs.getByConfigKeyIDForAllUsers(config.configKey().hashCode()));
         }
 
         // Verify that *ForCurrentUser() methods can be used to access network configurations
@@ -178,16 +172,15 @@
      */
     @Test
     public void testGettersAndHandleUserSwitch() {
-        for (WifiConfiguration config : CONFIGS) {
-            assertNull(mConfigs.put(config));
-        }
-
+        addNetworks(CONFIGS);
         verifyGetters(CONFIGS);
 
         switchUser(10);
+        addNetworks(CONFIGS);
         verifyGetters(CONFIGS);
 
         switchUser(11);
+        addNetworks(CONFIGS);
         verifyGetters(CONFIGS);
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java
new file mode 100644
index 0000000..0db93f5
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+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.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.DeletedEphemeralSsidsStoreData}.
+ */
+@SmallTest
+public class DeletedEphemeralSsidsStoreDataTest {
+    private static final String TEST_SSID1 = "SSID 1";
+    private static final String TEST_SSID2 = "SSID 2";
+    private static final String TEST_SSID_LIST_XML_STRING =
+            "<set name=\"SSIDList\">\n"
+            + "<string>" + TEST_SSID1 + "</string>\n"
+            + "<string>" + TEST_SSID2 + "</string>\n"
+            + "</set>\n";
+    private static final byte[] TEST_SSID_LIST_XML_BYTES =
+            TEST_SSID_LIST_XML_STRING.getBytes(StandardCharsets.UTF_8);
+    private DeletedEphemeralSsidsStoreData mDeletedEphemeralSsidsStoreData;
+
+    @Before
+    public void setUp() throws Exception {
+        mDeletedEphemeralSsidsStoreData = new DeletedEphemeralSsidsStoreData();
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     *
+     * @param shared Flag indicating serializing shared or user configurations
+     * @return byte[] of the XML data
+     * @throws Exception
+     */
+    private byte[] serializeData(boolean shared) throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mDeletedEphemeralSsidsStoreData.serializeData(out, shared);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     *
+     * @param data XML data to parse from
+     * @param shared Flag indicating parsing of shared or user configurations
+     * @return SSID list
+     * @throws Exception
+     */
+    private Set<String> deserializeData(byte[] data, boolean shared) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mDeletedEphemeralSsidsStoreData.deserializeData(in, in.getDepth(), shared);
+        return mDeletedEphemeralSsidsStoreData.getSsidList();
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to serialize SSID list
+     * to the share store, since the deleted ephemeral SSID list should never be persist
+     * to the share store.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void serializeShareData() throws Exception {
+        serializeData(true /* shared */);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to parse SSID list
+     * from the share store, since the deleted ephemeral SSID list should never be persist
+     * to the share store.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void deserializeShareData() throws Exception {
+        deserializeData(new byte[0], true /* shared */);
+    }
+
+    /**
+     * Verify that serializing the user store data without any configuration doesn't cause any
+     * crash and no data should be serialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeEmptyConfigs() throws Exception {
+        assertEquals(0, serializeData(false /* shared */).length);
+    }
+
+    /**
+     * Verify that parsing an empty data doesn't cause any crash and no configuration should
+     * be deserialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeEmptyData() throws Exception {
+        assertTrue(deserializeData(new byte[0], false /* shared */).isEmpty());
+    }
+
+    /**
+     * Verify that DeletedEphemeralSsidsStoreData does not support share data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void supportShareData() throws Exception {
+        assertFalse(mDeletedEphemeralSsidsStoreData.supportShareData());
+    }
+
+    /**
+     * Verify that user store SSID list is serialized correctly, matches the predefined test
+     * XML data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeSsidList() throws Exception {
+        Set<String> ssidList = new HashSet<>();
+        ssidList.add(TEST_SSID1);
+        ssidList.add(TEST_SSID2);
+        mDeletedEphemeralSsidsStoreData.setSsidList(ssidList);
+        byte[] actualData = serializeData(false /* shared */);
+        assertTrue(Arrays.equals(TEST_SSID_LIST_XML_BYTES, actualData));
+    }
+
+    /**
+     * Verify that user store SSID list is deserialized correctly using the predefined test XML
+     * data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeSsidList() throws Exception {
+        Set<String> ssidList = new HashSet<>();
+        ssidList.add(TEST_SSID1);
+        ssidList.add(TEST_SSID2);
+        assertEquals(ssidList, deserializeData(TEST_SSID_LIST_XML_BYTES, false /* shared */));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/DisabledForUpdateToAnyMatcher.java b/tests/wifitests/src/com/android/server/wifi/DisabledForUpdateToAnyMatcher.java
new file mode 100644
index 0000000..6f1df47
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/DisabledForUpdateToAnyMatcher.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisabledForUpdateToAnyMatcher {
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/FakeKeys.java b/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
new file mode 100644
index 0000000..99ce196
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/FakeKeys.java
@@ -0,0 +1,221 @@
+package com.android.server.wifi;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+/**
+ * A class containing test certificates.
+ */
+public class FakeKeys {
+    private static final String CA_CERT0_STRING = "-----BEGIN CERTIFICATE-----\n" +
+            "MIIDKDCCAhCgAwIBAgIJAILlFdwzLVurMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
+            "BAMTB0VBUCBDQTEwHhcNMTYwMTEyMTE1MDE1WhcNMjYwMTA5MTE1MDE1WjASMRAw\n" +
+            "DgYDVQQDEwdFQVAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
+            "znAPUz26Msae4ws43czR41/J2QtrSIZUKmVUsVumDbYHrPNvTXKSMXAcewORDQYX\n" +
+            "RqvHvpn8CscB1+oGXZvHwxj4zV0WKoK2zeXkau3vcyl3HIKupJfq2TEACefVjj0t\n" +
+            "JW+X35PGWp9/H5zIUNVNVjS7Ums84IvKhRB8512PB9UyHagXYVX5GWpAcVpyfrlR\n" +
+            "FI9Qdhh+Pbk0uyktdbf/CdfgHOoebrTtwRljM0oDtX+2Cv6j0wBK7hD8pPvf1+uy\n" +
+            "GzczigAU/4Kw7eZqydf9B+5RupR+IZipX41xEiIrKRwqi517WWzXcjaG2cNbf451\n" +
+            "xpH5PnV3i1tq04jMGQUzFwIDAQABo4GAMH4wHQYDVR0OBBYEFIwX4vs8BiBcScod\n" +
+            "5noZHRM8E4+iMEIGA1UdIwQ7MDmAFIwX4vs8BiBcScod5noZHRM8E4+ioRakFDAS\n" +
+            "MRAwDgYDVQQDEwdFQVAgQ0ExggkAguUV3DMtW6swDAYDVR0TBAUwAwEB/zALBgNV\n" +
+            "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAFfQqOTA7Rv7K+luQ7pnas4BYwHE\n" +
+            "9GEP/uohv6KOy0TGQFbrRTjFoLVNB9BZ1ymMDZ0/TIwIUc7wi7a8t5mEqYH153wW\n" +
+            "aWooiSjyLLhuI4sNrNCOtisdBq2r2MFXt6h0mAQYOPv8R8K7/fgSxGFqzhyNmmVL\n" +
+            "1qBJldx34SpwsTALQVPb4hGwJzZfr1PcpEQx6xMnTl8xEWZE3Ms99uaUxbQqIwRu\n" +
+            "LgAOkNCmY2m89VhzaHJ1uV85AdM/tD+Ysmlnnjt9LRCejbBipjIGjOXrg1JP+lxV\n" +
+            "muM4vH+P/mlmxsPPz0d65b+EGmJZpoLkO/tdNNvCYzjJpTEWpEsO6NMhKYo=\n" +
+            "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_CERT0 = loadCertificate(CA_CERT0_STRING);
+
+    private static final String CA_CERT1_STRING = "-----BEGIN CERTIFICATE-----\n" +
+            "MIIDKDCCAhCgAwIBAgIJAOM5SzKO2pzCMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV\n" +
+            "BAMTB0VBUCBDQTAwHhcNMTYwMTEyMDAxMDQ3WhcNMjYwMTA5MDAxMDQ3WjASMRAw\n" +
+            "DgYDVQQDEwdFQVAgQ0EwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n" +
+            "89ug+IEKVQXnJGKg5g4uVHg6J/8iRUxR5k2eH5o03hrJNMfN2D+cBe/wCiZcnWbI\n" +
+            "GbGZACWm2nQth2wy9Zgm2LOd3b4ocrHYls3XLq6Qb5Dd7a0JKU7pdGufiNVEkrmF\n" +
+            "EB+N64wgwH4COTvCiN4erp5kyJwkfqAl2xLkZo0C464c9XoyQOXbmYD9A8v10wZu\n" +
+            "jyNsEo7Nr2USyw+qhjWSbFbEirP77Tvx+7pJQJwdtk1V9Tn73T2dGF2WHYejei9S\n" +
+            "mcWpdIUqsu9etYH+zDmtu7I1xlkwiaVsNr2+D+qaCJyOYqrDTKVNK5nmbBPXDWZc\n" +
+            "NoDbTOoqquX7xONpq9M6jQIDAQABo4GAMH4wHQYDVR0OBBYEFAZ3A2S4qJZZwuNY\n" +
+            "wkJ6mAdc0gVdMEIGA1UdIwQ7MDmAFAZ3A2S4qJZZwuNYwkJ6mAdc0gVdoRakFDAS\n" +
+            "MRAwDgYDVQQDEwdFQVAgQ0EwggkA4zlLMo7anMIwDAYDVR0TBAUwAwEB/zALBgNV\n" +
+            "HQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAHmdMwEhtys4d0E+t7owBmoVR+lU\n" +
+            "hMCcRtWs8YKX5WIM2kTweT0h/O1xwE1mWmRv/IbDAEb8od4BjAQLhIcolStr2JaO\n" +
+            "9ZzyxjOnNzqeErh/1DHDbb/moPpqfeJ8YiEz7nH/YU56Q8iCPO7TsgS0sNNE7PfN\n" +
+            "IUsBW0yHRgpQ4OxWmiZG2YZWiECRzAC0ecPzo59N5iH4vLQIMTMYquiDeMPQnn1e\n" +
+            "NDGxG8gCtDKIaS6tMg3a28MvWB094pr2ETou8O1C8Ji0Y4hE8QJmSdT7I4+GZjgW\n" +
+            "g94DZ5RiL7sdp3vC48CXOmeT61YBIvhGUsE1rPhXqkpqQ3Z3C4TFF0jXZZc=\n" +
+            "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CA_CERT1 = loadCertificate(CA_CERT1_STRING);
+
+    private static final String CLIENT_CERT_STR = "-----BEGIN CERTIFICATE-----\n" +
+            "MIIE/DCCAuQCAQEwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxCzAJBgNV\n" +
+            "BAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdUZXN0aW5n\n" +
+            "MB4XDTE2MDkzMDIwNTQyOFoXDTE3MDkzMDIwNTQyOFowRDELMAkGA1UEBhMCVVMx\n" +
+            "CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdU\n" +
+            "ZXN0aW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnpmcbuaeHfnJ\n" +
+            "k+2QNvxmdVFTawyFMNk0USCq5sexscwmxbewG/Rb8YnixwJWS44v2XkSujB67z5C\n" +
+            "s2qudFEhRXKdEuC6idbAuA97KjipHh0AAniWMsyv61fvbgsUC0b0canx3LiDq81p\n" +
+            "y28NNGmAvoazLZUZ4AhBRiwYZY6FKk723gmZoGbEIeG7J1dlXPusc1662rIjz4eU\n" +
+            "zlmmlvqyHfNqnNk8L14Vug6Xh+lOEGN85xhu1YHAEKGrS89kZxs5rum/cZU8KH2V\n" +
+            "v6eKnY03kxjiVLQtnLpm/7VUEoCMGHyruRj+p3my4+DgqMsmsH52RZCBsjyGlpbU\n" +
+            "NOwOTIX6xh+Rqloduz4AnrMYYIiIw2s8g+2zJM7VbcVKx0fGS26BKdrxgrXWfmNE\n" +
+            "nR0/REQ5AxDGw0jfTUvtdTkXAf+K4MDjcNLEZ+MA4rHfAfQWZtUR5BkHCQYxNpJk\n" +
+            "pA0gyk+BpKdC4WdzI14NSWsu5sRCmBCFqH6BTOSEq/V1cNorBxNwLSSTwFFqUDqx\n" +
+            "Y5nQLXygkJf9WHZWtSKeSjtOYgilz7UKzC2s3CsjmIyGFe+SwpuHJnuE4Uc8Z5Cb\n" +
+            "bjNGHPzqL6XnmzZHJp7RF8kBdKdjGC7dCUltzOfICZeKlzOOq+Kw42T/nXjuXvpb\n" +
+            "nkXNxg741Nwd6RecykXJbseFwm3EYxkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEA\n" +
+            "Ga1mGwI9aXkL2fTPXO9YkAPzoGeX8aeuVYSQaSkNq+5vnogYCyAt3YDHjRG+ewTT\n" +
+            "WbnPA991xRAPac+biJeXWmwvgGj0YuT7e79phAiGkTTnbAjFHGfYnBy/tI/v7btO\n" +
+            "hRNElA5yTJ1m2fVbBEKXzMR83jrT9iyI+YLRN86zUZIaC86xxSbqnrdWN2jOK6MX\n" +
+            "dS8Arp9tPQjC/4gW+2Ilxv68jiYh+5auWHQZVjppWVY//iu4mAbkq1pTwQEhZ8F8\n" +
+            "Zrmh9DHh60hLFcfSuhIAwf/NMzppwdkjy1ruKVrpijhGKGp4OWu8nvOUgHSzxc7F\n" +
+            "PwpVZ5N2Ku4L8MLO6BG2VasRJK7l17TzDXlfLZHJjkuryOFxVaQKt8ZNFgTOaCXS\n" +
+            "E+gpTLksKU7riYckoiP4+H1sn9qcis0e8s4o/uf1UVc8GSdDw61ReGM5oZEDm1u8\n" +
+            "H9x20QU6igLqzyBpqvCKv7JNgU1uB2PAODHH78zJiUfnKd1y+o+J1iWzaGj3EFji\n" +
+            "T8AXksbTP733FeFXfggXju2dyBH+Z1S5BBTEOd1brWgXlHSAZGm97MKZ94r6/tkX\n" +
+            "qfv3fCos0DKz0oV7qBxYS8wiYhzrRVxG6ITAoH8uuUVVQaZF+G4nJ2jEqNbfuKyX\n" +
+            "ATQsVNjNNlDA0J33GobPMjT326wa4YAWMx8PI5PJZ3g=\n" +
+            "-----END CERTIFICATE-----\n";
+    public static final X509Certificate CLIENT_CERT = loadCertificate(CLIENT_CERT_STR);
+
+    private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
+            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
+            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
+            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
+            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
+            (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
+            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
+            (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
+            (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
+            (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
+            (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
+            (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
+            (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
+            (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
+            (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
+            (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
+            (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
+            (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
+            (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
+            (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
+            (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
+            (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
+            (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
+            (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
+            (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
+            (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
+            (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
+            (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
+            (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
+            (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
+            (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
+            (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
+            (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
+            (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
+            (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
+            (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
+            (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
+            (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
+            (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
+            (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
+            (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
+            (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
+            (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
+            (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
+            (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
+            (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
+            (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
+            (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
+            (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
+            (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
+            (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
+            (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
+            (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
+            (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
+            (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
+            (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
+            (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
+            (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
+            (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
+            (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
+            (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
+            (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
+            (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
+            (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
+            (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
+            (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
+            (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
+            (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
+            (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
+            (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
+            (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
+            (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
+            (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
+            (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
+            (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
+            (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
+            (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
+            (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
+            (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
+            (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
+            (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
+            (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
+            (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
+            (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
+            (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
+            (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
+            (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
+            (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
+            (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
+            (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
+            (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
+            (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
+            (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
+            (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
+            (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
+            (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
+            (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
+            (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
+            (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
+            (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
+            (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
+            (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
+            (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
+            (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
+            (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
+            (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
+            (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
+    };
+    public static final PrivateKey RSA_KEY1 = loadPrivateRSAKey(FAKE_RSA_KEY_1);
+
+    private static X509Certificate loadCertificate(String blob) {
+        try {
+            final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            InputStream stream = new ByteArrayInputStream(blob.getBytes(StandardCharsets.UTF_8));
+
+            return (X509Certificate) certFactory.generateCertificate(stream);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static PrivateKey loadPrivateRSAKey(byte[] fakeKey) {
+        try {
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePrivate(new PKCS8EncodedKeySpec(fakeKey));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
new file mode 100644
index 0000000..3e203a6
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
@@ -0,0 +1,1635 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static com.android.server.wifi.HalDeviceManager.START_HAL_RETRY_TIMES;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+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.when;
+
+import android.app.test.MockAnswerUtil;
+import android.hardware.wifi.V1_0.IWifi;
+import android.hardware.wifi.V1_0.IWifiApIface;
+import android.hardware.wifi.V1_0.IWifiChip;
+import android.hardware.wifi.V1_0.IWifiChipEventCallback;
+import android.hardware.wifi.V1_0.IWifiEventCallback;
+import android.hardware.wifi.V1_0.IWifiIface;
+import android.hardware.wifi.V1_0.IWifiNanIface;
+import android.hardware.wifi.V1_0.IWifiP2pIface;
+import android.hardware.wifi.V1_0.IWifiStaIface;
+import android.hardware.wifi.V1_0.IfaceType;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IHwBinder;
+import android.os.test.TestLooper;
+import android.util.Log;
+
+import org.hamcrest.core.IsNull;
+import org.junit.After;
+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.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Unit test harness for HalDeviceManagerTest.
+ */
+public class HalDeviceManagerTest {
+    private HalDeviceManager mDut;
+    @Mock IServiceManager mServiceManagerMock;
+    @Mock IWifi mWifiMock;
+    @Mock HalDeviceManager.ManagerStatusListener mManagerStatusListenerMock;
+    private TestLooper mTestLooper;
+    private ArgumentCaptor<IHwBinder.DeathRecipient> mDeathRecipientCaptor =
+            ArgumentCaptor.forClass(IHwBinder.DeathRecipient.class);
+    private ArgumentCaptor<IServiceNotification.Stub> mServiceNotificationCaptor =
+            ArgumentCaptor.forClass(IServiceNotification.Stub.class);
+    private ArgumentCaptor<IWifiEventCallback> mWifiEventCallbackCaptor = ArgumentCaptor.forClass(
+            IWifiEventCallback.class);
+    private InOrder mInOrder;
+    @Rule public ErrorCollector collector = new ErrorCollector();
+    private WifiStatus mStatusOk;
+    private WifiStatus mStatusFail;
+
+    private class HalDeviceManagerSpy extends HalDeviceManager {
+        @Override
+        protected IWifi getWifiServiceMockable() {
+            return mWifiMock;
+        }
+
+        @Override
+        protected IServiceManager getServiceManagerMockable() {
+            return mServiceManagerMock;
+        }
+    }
+
+    @Before
+    public void before() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTestLooper = new TestLooper();
+
+        // initialize dummy status objects
+        mStatusOk = getStatus(WifiStatusCode.SUCCESS);
+        mStatusFail = getStatus(WifiStatusCode.ERROR_UNKNOWN);
+
+        when(mServiceManagerMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong())).thenReturn(true);
+        when(mServiceManagerMock.registerForNotifications(anyString(), anyString(),
+                any(IServiceNotification.Stub.class))).thenReturn(true);
+        when(mWifiMock.linkToDeath(any(IHwBinder.DeathRecipient.class), anyLong())).thenReturn(
+                true);
+        when(mWifiMock.registerEventCallback(any(IWifiEventCallback.class))).thenReturn(mStatusOk);
+        when(mWifiMock.start()).thenReturn(mStatusOk);
+        when(mWifiMock.stop()).thenReturn(mStatusOk);
+
+        mDut = new HalDeviceManagerSpy();
+    }
+
+    /**
+     * Print out the dump of the device manager after each test. Not used in test validation
+     * (internal state) - but can help in debugging failed tests.
+     */
+    @After
+    public void after() throws Exception {
+        dumpDut("after: ");
+    }
+
+    /**
+     * Test basic startup flow:
+     * - IServiceManager registrations
+     * - IWifi registrations
+     * - IWifi startup delayed
+     * - Start Wi-Fi -> onStart
+     * - Stop Wi-Fi -> onStop
+     */
+    @Test
+    public void testStartStopFlow() throws Exception {
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // act: stop Wi-Fi
+        mDut.stop();
+        mTestLooper.dispatchAll();
+
+        // verify: onStop called
+        mInOrder.verify(mWifiMock).stop();
+        mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock);
+    }
+
+    /**
+     * Validate that multiple callback registrations are called and that duplicate ones are
+     * only called once.
+     */
+    @Test
+    public void testMultipleCallbackRegistrations() throws Exception {
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+
+        // register another 2 callbacks - one of them twice
+        HalDeviceManager.ManagerStatusListener callback1 = mock(
+                HalDeviceManager.ManagerStatusListener.class);
+        HalDeviceManager.ManagerStatusListener callback2 = mock(
+                HalDeviceManager.ManagerStatusListener.class);
+        mDut.registerStatusListener(callback2, mTestLooper.getLooper());
+        mDut.registerStatusListener(callback1, mTestLooper.getLooper());
+        mDut.registerStatusListener(callback2, mTestLooper.getLooper());
+
+        // startup
+        executeAndValidateStartupSequence();
+
+        // verify
+        verify(callback1).onStatusChanged();
+        verify(callback2).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, callback1, callback2);
+    }
+
+    /**
+     * Validate IWifi death listener and registration flow.
+     */
+    @Test
+    public void testWifiDeathAndRegistration() throws Exception {
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // act: IWifi service death
+        mDeathRecipientCaptor.getValue().serviceDied(0);
+        mTestLooper.dispatchAll();
+
+        // verify: getting onStop
+        mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
+
+        // act: service startup
+        mServiceNotificationCaptor.getValue().onRegistration(IWifi.kInterfaceName, "", false);
+
+        // verify: initialization of IWifi
+        mInOrder.verify(mWifiMock).linkToDeath(mDeathRecipientCaptor.capture(), anyLong());
+        mInOrder.verify(mWifiMock).registerEventCallback(mWifiEventCallbackCaptor.capture());
+
+        // act: start
+        collector.checkThat(mDut.start(), equalTo(true));
+        mWifiEventCallbackCaptor.getValue().onStart();
+        mTestLooper.dispatchAll();
+
+        // verify: service and callback calls
+        mInOrder.verify(mWifiMock).start();
+        mInOrder.verify(mManagerStatusListenerMock, times(3)).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock);
+    }
+
+    /**
+     * Validate IWifi onFailure causes notification
+     */
+    @Test
+    public void testWifiFail() throws Exception {
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // act: IWifi failure
+        mWifiEventCallbackCaptor.getValue().onFailure(mStatusFail);
+        mTestLooper.dispatchAll();
+
+        // verify: getting onStop
+        mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
+
+        // act: start again
+        collector.checkThat(mDut.start(), equalTo(true));
+        mWifiEventCallbackCaptor.getValue().onStart();
+        mTestLooper.dispatchAll();
+
+        // verify: service and callback calls
+        mInOrder.verify(mWifiMock).start();
+        mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock);
+    }
+
+    /**
+     * Validate creation of STA interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateStaInterfaceNoInitMode() throws Exception {
+        final String name = "sta0";
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiStaIface iface = (IWifiStaIface) validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                name, // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: remove interface
+        mDut.removeIface(iface);
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        mInOrder.verify(chipMock.chip).removeStaIface(name);
+        verify(idl).onDestroyed();
+        verify(iafrl).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate creation of AP interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateApInterfaceNoInitMode() throws Exception {
+        final String name = "ap0";
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.AP, // ifaceTypeToCreate
+                name, // ifaceName
+                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: remove interface
+        mDut.removeIface(iface);
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        mInOrder.verify(chipMock.chip).removeApIface(name);
+        verify(idl).onDestroyed();
+        verify(iafrl).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate creation of P2P interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateP2pInterfaceNoInitMode() throws Exception {
+        final String name = "p2p0";
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiP2pIface iface = (IWifiP2pIface) validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.P2P, // ifaceTypeToCreate
+                name, // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: remove interface
+        mDut.removeIface(iface);
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        mInOrder.verify(chipMock.chip).removeP2pIface(name);
+        verify(idl).onDestroyed();
+        verify(iafrl).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate creation of NAN interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateNanInterfaceNoInitMode() throws Exception {
+        final String name = "nan0";
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiNanIface iface = (IWifiNanIface) validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.NAN, // ifaceTypeToCreate
+                name, // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: remove interface
+        mDut.removeIface(iface);
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        mInOrder.verify(chipMock.chip).removeNanIface(name);
+        verify(idl).onDestroyed();
+        verify(iafrl).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate creation of AP interface when in STA mode - but with no interface created. Expect
+     * a change in chip mode.
+     */
+    @Test
+    public void testCreateApWithStaModeUp() throws Exception {
+        final String name = "ap0";
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                name, // ifaceName
+                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: stop Wi-Fi
+        mDut.stop();
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        verify(idl).onDestroyed();
+        verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate creation of AP interface when in AP mode - but with no interface created. Expect
+     * no change in chip mode.
+     */
+    @Test
+    public void testCreateApWithApModeUp() throws Exception {
+        final String name = "ap0";
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.AP_CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                name, // ifaceName
+                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: stop Wi-Fi
+        mDut.stop();
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        verify(idl).onDestroyed();
+        verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate AP up/down creation of AP interface when a STA already created. Expect:
+     * - STA created
+     * - P2P created
+     * - When AP requested:
+     *   - STA & P2P torn down
+     *   - AP created
+     * - P2P creation refused
+     * - Request STA: will tear down AP
+     * - When AP destroyed:
+     *   - Get p2p available listener callback
+     *   - Can create P2P when requested
+     * - Create P2P
+     * - Request NAN: will get refused
+     * - Tear down P2P:
+     *    - should get nan available listener callback
+     *    - Can create NAN when requested
+     */
+    @Test
+    public void testCreateSameAndDiffPriorities() throws Exception {
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener2 = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener apDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener p2pDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener p2pDestroyedListener2 = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener nanDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        // Request STA
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                staAvailListener // availableListener
+        );
+        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
+
+        // register additional InterfaceDestroyedListeners - including a duplicate (verify that
+        // only called once!)
+        mDut.registerDestroyedListener(staIface, staDestroyedListener2, mTestLooper.getLooper());
+        mDut.registerDestroyedListener(staIface, staDestroyedListener, mTestLooper.getLooper());
+
+        // Request P2P
+        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                p2pDestroyedListener, // destroyedListener
+                p2pAvailListener // availableListener
+        );
+        collector.checkThat("allocated P2P interface", p2pIface, IsNull.notNullValue());
+
+        // Request AP
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                "ap0", // ifaceName
+                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
+                new IWifiIface[]{staIface, p2pIface}, // tearDownList
+                apDestroyedListener, // destroyedListener
+                apAvailListener, // availableListener
+                // destroyedInterfacesDestroyedListeners...
+                staDestroyedListener, staDestroyedListener2, p2pDestroyedListener
+        );
+        collector.checkThat("allocated AP interface", apIface, IsNull.notNullValue());
+
+        // Request P2P: expect failure
+        p2pIface = mDut.createP2pIface(p2pDestroyedListener, mTestLooper.getLooper());
+        collector.checkThat("P2P can't be created", p2pIface, IsNull.nullValue());
+
+        // Request STA: expect success
+        staIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.AP_CHIP_MODE_ID, // chipModeId
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                staAvailListener, // availableListener
+                apDestroyedListener // destroyedInterfacesDestroyedListeners...
+        );
+        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
+
+        mTestLooper.dispatchAll();
+        verify(apDestroyedListener).onDestroyed();
+
+        // Request P2P: expect success now
+        p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                p2pDestroyedListener2, // destroyedListener
+                p2pAvailListener // availableListener
+        );
+
+        // Request NAN: should fail
+        IWifiIface nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mTestLooper.getLooper());
+        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
+
+        // Tear down P2P
+        mDut.removeIface(p2pIface);
+        mTestLooper.dispatchAll();
+
+        verify(chipMock.chip, times(2)).removeP2pIface("p2p0");
+        verify(p2pDestroyedListener2).onDestroyed();
+
+        // Should now be able to request and get NAN
+        nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                nanAvailListener // availableListener
+        );
+        collector.checkThat("allocated NAN interface", nanIface, IsNull.notNullValue());
+
+        // available callback verification
+        verify(staAvailListener).onAvailableForRequest();
+        verify(apAvailListener, times(4)).onAvailableForRequest();
+        verify(p2pAvailListener, times(3)).onAvailableForRequest();
+        verify(nanAvailListener).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
+                staDestroyedListener2, apDestroyedListener, apAvailListener, p2pDestroyedListener,
+                nanDestroyedListener, nanAvailListener, p2pDestroyedListener2);
+    }
+
+    /**
+     * Validate P2P and NAN interactions. Expect:
+     * - STA created
+     * - NAN created
+     * - When P2P requested:
+     *   - NAN torn down
+     *   - P2P created
+     * - NAN creation refused
+     * - When P2P destroyed:
+     *   - get nan available listener
+     *   - Can create NAN when requested
+     */
+    @Test
+    public void testP2pAndNanInteractions() throws Exception {
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener nanDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener p2pDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = null;
+
+        // Request STA
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                staAvailListener // availableListener
+        );
+
+        // Request NAN
+        IWifiIface nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                nanAvailListener // availableListener
+        );
+
+        // Request P2P
+        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                new IWifiIface[]{nanIface}, // tearDownList
+                p2pDestroyedListener, // destroyedListener
+                p2pAvailListener, // availableListener
+                nanDestroyedListener // destroyedInterfacesDestroyedListeners...
+        );
+
+        // Request NAN: expect failure
+        nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mTestLooper.getLooper());
+        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
+
+        // Destroy P2P interface
+        boolean status = mDut.removeIface(p2pIface);
+        mInOrder.verify(chipMock.chip).removeP2pIface("p2p0");
+        collector.checkThat("P2P removal success", status, equalTo(true));
+
+        mTestLooper.dispatchAll();
+        verify(p2pDestroyedListener).onDestroyed();
+        verify(nanAvailListener).onAvailableForRequest();
+
+        // Request NAN: expect success now
+        nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                nanAvailListener // availableListener
+        );
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
+                nanDestroyedListener, nanAvailListener, p2pDestroyedListener);
+    }
+
+    /**
+     * Validates that when (for some reason) the cache is out-of-sync with the actual chip status
+     * then Wi-Fi is shut-down.
+     */
+    @Test
+    public void testCacheMismatchError() throws Exception {
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener nanDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        // Request STA
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                staAvailListener // availableListener
+        );
+
+        // Request NAN
+        IWifiIface nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                nanAvailListener // availableListener
+        );
+
+        // fiddle with the "chip" by removing the STA
+        chipMock.interfaceNames.get(IfaceType.STA).remove("sta0");
+
+        // now try to request another NAN
+        nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mTestLooper.getLooper());
+        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
+
+        // verify that Wi-Fi is shut-down: should also get all onDestroyed messages that are
+        // registered (even if they seem out-of-sync to chip)
+        mTestLooper.dispatchAll();
+        verify(mWifiMock, times(2)).stop();
+        verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
+        verify(staDestroyedListener).onDestroyed();
+        verify(nanDestroyedListener).onDestroyed();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
+                nanDestroyedListener, nanAvailListener);
+    }
+
+    /**
+     * Validates that trying to allocate a STA and then another STA fails. Only one STA at a time
+     * is permitted (by baseline chip).
+     */
+    @Test
+    public void testDuplicateStaRequests() throws Exception {
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener1 = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener1 = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener2 = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+
+        // get STA interface
+        IWifiIface staIface1 = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener1, // destroyedListener
+                staAvailListener1 // availableListener
+        );
+        collector.checkThat("STA created", staIface1, IsNull.notNullValue());
+
+        // get STA interface again
+        IWifiIface staIface2 = mDut.createStaIface(staDestroyedListener2, mTestLooper.getLooper());
+        collector.checkThat("STA created", staIface2, IsNull.nullValue());
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener1,
+                staAvailListener1, staDestroyedListener2);
+    }
+
+    /**
+     * Validates that a duplicate registration of the same InterfaceAvailableForRequestListener
+     * listener will result in a single callback.
+     *
+     * Also validates that get an immediate call on registration if available.
+     */
+    @Test
+    public void testDuplicateAvailableRegistrations() throws Exception {
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        // get STA interface
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                null, // destroyedListener
+                null // availableListener
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // act: register the same listener twice
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
+                mTestLooper.getLooper());
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
+                mTestLooper.getLooper());
+        mTestLooper.dispatchAll();
+
+        // remove STA interface -> should trigger callbacks
+        mDut.removeIface(staIface);
+        mTestLooper.dispatchAll();
+
+        // verify: only a single trigger
+        verify(staAvailListener).onAvailableForRequest();
+
+        verifyNoMoreInteractions(staAvailListener);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for all chips.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesAll() throws Exception {
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes();
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for a specific chip.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesOneChip() throws Exception {
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes(chipMock.chip);
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    /**
+     * Validate that when no chip info is found an empty list is returned.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesError() throws Exception {
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes();
+
+        // verify results
+        assertEquals(0, results.size());
+    }
+
+    /**
+     * Test start HAL can retry upon failure.
+     */
+    @Test
+    public void testStartHalRetryUponNotAvailableFailure() throws Exception {
+        // Override the stubbing for mWifiMock in before().
+        when(mWifiMock.start())
+            .thenReturn(getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE))
+            .thenReturn(mStatusOk);
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence(2, true);
+    }
+
+    /**
+     * Test start HAL fails after multiple retry failures.
+     */
+    @Test
+    public void testStartHalRetryFailUponMultipleNotAvailableFailures() throws Exception {
+        // Override the stubbing for mWifiMock in before().
+        when(mWifiMock.start()).thenReturn(getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE));
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence(START_HAL_RETRY_TIMES + 1, false);
+    }
+
+    /**
+     * Test start HAL fails after multiple retry failures.
+     */
+    @Test
+    public void testStartHalRetryFailUponTrueFailure() throws Exception {
+        // Override the stubbing for mWifiMock in before().
+        when(mWifiMock.start()).thenReturn(getStatus(WifiStatusCode.ERROR_UNKNOWN));
+
+        BaselineChip chipMock = new BaselineChip();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence(1, false);
+    }
+
+    /**
+     * Validate that isSupported() returns true when IServiceManager finds the vendor HAL daemon in
+     * the VINTF.
+     */
+    @Test
+    public void testIsSupportedTrue() throws Exception {
+        when(mServiceManagerMock.getTransport(
+                eq(IWifi.kInterfaceName), eq(HalDeviceManager.HAL_INSTANCE_NAME)))
+                .thenReturn(IServiceManager.Transport.HWBINDER);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock);
+        executeAndValidateInitializationSequence();
+        assertTrue(mDut.isSupported());
+    }
+
+    /**
+     * Validate that isSupported() returns true when IServiceManager finds the vendor HAL daemon in
+     * the VINTF.
+     */
+    @Test
+    public void testIsSupportedFalse() throws Exception {
+        when(mServiceManagerMock.getTransport(
+                eq(IWifi.kInterfaceName), eq(HalDeviceManager.HAL_INSTANCE_NAME)))
+                .thenReturn(IServiceManager.Transport.EMPTY);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock);
+        executeAndValidateInitializationSequence();
+        assertFalse(mDut.isSupported());
+    }
+
+    // utilities
+    private void dumpDut(String prefix) {
+        StringWriter sw = new StringWriter();
+        mDut.dump(null, new PrintWriter(sw), null);
+        Log.e("HalDeviceManager", prefix + sw.toString());
+    }
+
+    private void executeAndValidateInitializationSequence() throws Exception {
+        // act:
+        mDut.initialize();
+
+        // verify: service manager initialization sequence
+        mInOrder.verify(mServiceManagerMock).linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong());
+        mInOrder.verify(mServiceManagerMock).registerForNotifications(eq(IWifi.kInterfaceName),
+                eq(""), mServiceNotificationCaptor.capture());
+
+        // act: get the service started (which happens even when service was already up)
+        mServiceNotificationCaptor.getValue().onRegistration(IWifi.kInterfaceName, "", true);
+
+        // verify: wifi initialization sequence
+        mInOrder.verify(mWifiMock).linkToDeath(mDeathRecipientCaptor.capture(), anyLong());
+        mInOrder.verify(mWifiMock).registerEventCallback(mWifiEventCallbackCaptor.capture());
+        collector.checkThat("isReady is true", mDut.isReady(), equalTo(true));
+    }
+
+    private void executeAndValidateStartupSequence()throws Exception {
+        executeAndValidateStartupSequence(1, true);
+    }
+
+    private void executeAndValidateStartupSequence(int numAttempts, boolean success)
+            throws Exception {
+        // act: register listener & start Wi-Fi
+        mDut.registerStatusListener(mManagerStatusListenerMock, mTestLooper.getLooper());
+        collector.checkThat(mDut.start(), equalTo(success));
+
+        // verify
+        mInOrder.verify(mWifiMock, times(numAttempts)).start();
+
+        if (success) {
+            // act: trigger onStart callback of IWifiEventCallback
+            mWifiEventCallbackCaptor.getValue().onStart();
+            mTestLooper.dispatchAll();
+
+            // verify: onStart called on registered listener
+            mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
+        }
+    }
+
+    private IWifiIface validateInterfaceSequence(ChipMockBase chipMock,
+            boolean chipModeValid, int chipModeId,
+            int ifaceTypeToCreate, String ifaceName, int finalChipMode, IWifiIface[] tearDownList,
+            HalDeviceManager.InterfaceDestroyedListener destroyedListener,
+            HalDeviceManager.InterfaceAvailableForRequestListener availableListener,
+            HalDeviceManager.InterfaceDestroyedListener... destroyedInterfacesDestroyedListeners)
+            throws Exception {
+        // configure chip mode response
+        chipMock.chipModeValid = chipModeValid;
+        chipMock.chipModeId = chipModeId;
+
+        IWifiIface iface = null;
+
+        // configure: interface to be created
+        // act: request the interface
+        switch (ifaceTypeToCreate) {
+            case IfaceType.STA:
+                iface = mock(IWifiStaIface.class);
+                doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
+                        any(IWifiIface.getNameCallback.class));
+                doAnswer(new GetTypeAnswer(IfaceType.STA)).when(iface).getType(
+                        any(IWifiIface.getTypeCallback.class));
+                doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
+                        chipMock.chip).createStaIface(any(IWifiChip.createStaIfaceCallback.class));
+
+                mDut.createStaIface(destroyedListener, mTestLooper.getLooper());
+                break;
+            case IfaceType.AP:
+                iface = mock(IWifiApIface.class);
+                doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
+                        any(IWifiIface.getNameCallback.class));
+                doAnswer(new GetTypeAnswer(IfaceType.AP)).when(iface).getType(
+                        any(IWifiIface.getTypeCallback.class));
+                doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
+                        chipMock.chip).createApIface(any(IWifiChip.createApIfaceCallback.class));
+
+                mDut.createApIface(destroyedListener, mTestLooper.getLooper());
+                break;
+            case IfaceType.P2P:
+                iface = mock(IWifiP2pIface.class);
+                doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
+                        any(IWifiIface.getNameCallback.class));
+                doAnswer(new GetTypeAnswer(IfaceType.P2P)).when(iface).getType(
+                        any(IWifiIface.getTypeCallback.class));
+                doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
+                        chipMock.chip).createP2pIface(any(IWifiChip.createP2pIfaceCallback.class));
+
+                mDut.createP2pIface(destroyedListener, mTestLooper.getLooper());
+                break;
+            case IfaceType.NAN:
+                iface = mock(IWifiNanIface.class);
+                doAnswer(new GetNameAnswer(ifaceName)).when(iface).getName(
+                        any(IWifiIface.getNameCallback.class));
+                doAnswer(new GetTypeAnswer(IfaceType.NAN)).when(iface).getType(
+                        any(IWifiIface.getTypeCallback.class));
+                doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
+                        chipMock.chip).createNanIface(any(IWifiChip.createNanIfaceCallback.class));
+
+                mDut.createNanIface(destroyedListener, mTestLooper.getLooper());
+                break;
+        }
+        if (availableListener != null) {
+            mDut.registerInterfaceAvailableForRequestListener(ifaceTypeToCreate, availableListener,
+                    mTestLooper.getLooper());
+        }
+
+        // validate: optional tear down of interfaces
+        if (tearDownList != null) {
+            for (IWifiIface tearDownIface: tearDownList) {
+                switch (getType(tearDownIface)) {
+                    case IfaceType.STA:
+                        mInOrder.verify(chipMock.chip).removeStaIface(getName(tearDownIface));
+                        break;
+                    case IfaceType.AP:
+                        mInOrder.verify(chipMock.chip).removeApIface(getName(tearDownIface));
+                        break;
+                    case IfaceType.P2P:
+                        mInOrder.verify(chipMock.chip).removeP2pIface(getName(tearDownIface));
+                        break;
+                    case IfaceType.NAN:
+                        mInOrder.verify(chipMock.chip).removeNanIface(getName(tearDownIface));
+                        break;
+                }
+            }
+        }
+
+        // validate: optional switch to the requested mode
+        if (!chipModeValid || chipModeId != finalChipMode) {
+            mInOrder.verify(chipMock.chip).configureChip(finalChipMode);
+        } else {
+            mInOrder.verify(chipMock.chip, times(0)).configureChip(anyInt());
+        }
+
+        // validate: create interface
+        switch (ifaceTypeToCreate) {
+            case IfaceType.STA:
+                mInOrder.verify(chipMock.chip).createStaIface(
+                        any(IWifiChip.createStaIfaceCallback.class));
+                break;
+            case IfaceType.AP:
+                mInOrder.verify(chipMock.chip).createApIface(
+                        any(IWifiChip.createApIfaceCallback.class));
+                break;
+            case IfaceType.P2P:
+                mInOrder.verify(chipMock.chip).createP2pIface(
+                        any(IWifiChip.createP2pIfaceCallback.class));
+                break;
+            case IfaceType.NAN:
+                mInOrder.verify(chipMock.chip).createNanIface(
+                        any(IWifiChip.createNanIfaceCallback.class));
+                break;
+        }
+
+        // verify: callbacks on deleted interfaces
+        mTestLooper.dispatchAll();
+        for (int i = 0; i < destroyedInterfacesDestroyedListeners.length; ++i) {
+            verify(destroyedInterfacesDestroyedListeners[i]).onDestroyed();
+        }
+
+        return iface;
+    }
+
+    private int getType(IWifiIface iface) throws Exception {
+        Mutable<Integer> typeResp = new Mutable<>();
+        iface.getType((WifiStatus status, int type) -> {
+            typeResp.value = type;
+        });
+        return typeResp.value;
+    }
+
+    private String getName(IWifiIface iface) throws Exception {
+        Mutable<String> nameResp = new Mutable<>();
+        iface.getName((WifiStatus status, String name) -> {
+            nameResp.value = name;
+        });
+        return nameResp.value;
+    }
+
+    private WifiStatus getStatus(int code) {
+        WifiStatus status = new WifiStatus();
+        status.code = code;
+        return status;
+    }
+
+    private static class Mutable<E> {
+        public E value;
+
+        Mutable() {
+            value = null;
+        }
+
+        Mutable(E value) {
+            this.value = value;
+        }
+    }
+
+    // Answer objects
+    private class GetChipIdsAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private WifiStatus mStatus;
+        private ArrayList<Integer> mChipIds;
+
+        GetChipIdsAnswer(WifiStatus status, ArrayList<Integer> chipIds) {
+            mStatus = status;
+            mChipIds = chipIds;
+        }
+
+        public void answer(IWifi.getChipIdsCallback cb) {
+            cb.onValues(mStatus, mChipIds);
+        }
+    }
+
+    private class GetChipAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private WifiStatus mStatus;
+        private IWifiChip mChip;
+
+        GetChipAnswer(WifiStatus status, IWifiChip chip) {
+            mStatus = status;
+            mChip = chip;
+        }
+
+        public void answer(int chipId, IWifi.getChipCallback cb) {
+            cb.onValues(mStatus, mChip);
+        }
+    }
+
+    private class GetIdAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        GetIdAnswer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public void answer(IWifiChip.getIdCallback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.chipId);
+        }
+    }
+
+    private class GetAvailableModesAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        GetAvailableModesAnswer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public void answer(IWifiChip.getAvailableModesCallback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.availableModes);
+        }
+    }
+
+    private class GetModeAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        GetModeAnswer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public void answer(IWifiChip.getModeCallback cb) {
+            cb.onValues(mChipMockBase.chipModeValid ? mStatusOk
+                    : getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE), mChipMockBase.chipModeId);
+        }
+    }
+
+    private class ConfigureChipAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        ConfigureChipAnswer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public WifiStatus answer(int chipMode) {
+            mChipMockBase.chipModeId = chipMode;
+            return mStatusOk;
+        }
+    }
+
+    private class GetXxxIfaceNamesAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        GetXxxIfaceNamesAnswer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public void answer(IWifiChip.getStaIfaceNamesCallback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.interfaceNames.get(IfaceType.STA));
+        }
+
+        public void answer(IWifiChip.getApIfaceNamesCallback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.interfaceNames.get(IfaceType.AP));
+        }
+
+        public void answer(IWifiChip.getP2pIfaceNamesCallback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.interfaceNames.get(IfaceType.P2P));
+        }
+
+        public void answer(IWifiChip.getNanIfaceNamesCallback cb) {
+            cb.onValues(mStatusOk, mChipMockBase.interfaceNames.get(IfaceType.NAN));
+        }
+    }
+
+    private class GetXxxIfaceAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+
+        GetXxxIfaceAnswer(ChipMockBase chipMockBase) {
+            mChipMockBase = chipMockBase;
+        }
+
+        public void answer(String name, IWifiChip.getStaIfaceCallback cb) {
+            IWifiIface iface = mChipMockBase.interfacesByName.get(IfaceType.STA).get(name);
+            cb.onValues(iface != null ? mStatusOk : mStatusFail, (IWifiStaIface) iface);
+        }
+
+        public void answer(String name, IWifiChip.getApIfaceCallback cb) {
+            IWifiIface iface = mChipMockBase.interfacesByName.get(IfaceType.AP).get(name);
+            cb.onValues(iface != null ? mStatusOk : mStatusFail, (IWifiApIface) iface);
+        }
+
+        public void answer(String name, IWifiChip.getP2pIfaceCallback cb) {
+            IWifiIface iface = mChipMockBase.interfacesByName.get(IfaceType.P2P).get(name);
+            cb.onValues(iface != null ? mStatusOk : mStatusFail, (IWifiP2pIface) iface);
+        }
+
+        public void answer(String name, IWifiChip.getNanIfaceCallback cb) {
+            IWifiIface iface = mChipMockBase.interfacesByName.get(IfaceType.NAN).get(name);
+            cb.onValues(iface != null ? mStatusOk : mStatusFail, (IWifiNanIface) iface);
+        }
+    }
+
+    private class CreateXxxIfaceAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+        private WifiStatus mStatus;
+        private IWifiIface mWifiIface;
+
+        CreateXxxIfaceAnswer(ChipMockBase chipMockBase, WifiStatus status, IWifiIface wifiIface) {
+            mChipMockBase = chipMockBase;
+            mStatus = status;
+            mWifiIface = wifiIface;
+        }
+
+        private void addInterfaceInfo(int type) {
+            if (mStatus.code == WifiStatusCode.SUCCESS) {
+                try {
+                    mChipMockBase.interfaceNames.get(type).add(getName(mWifiIface));
+                    mChipMockBase.interfacesByName.get(type).put(getName(mWifiIface), mWifiIface);
+                } catch (Exception e) {
+                    // do nothing
+                }
+            }
+        }
+
+        public void answer(IWifiChip.createStaIfaceCallback cb) {
+            cb.onValues(mStatus, (IWifiStaIface) mWifiIface);
+            addInterfaceInfo(IfaceType.STA);
+        }
+
+        public void answer(IWifiChip.createApIfaceCallback cb) {
+            cb.onValues(mStatus, (IWifiApIface) mWifiIface);
+            addInterfaceInfo(IfaceType.AP);
+        }
+
+        public void answer(IWifiChip.createP2pIfaceCallback cb) {
+            cb.onValues(mStatus, (IWifiP2pIface) mWifiIface);
+            addInterfaceInfo(IfaceType.P2P);
+        }
+
+        public void answer(IWifiChip.createNanIfaceCallback cb) {
+            cb.onValues(mStatus, (IWifiNanIface) mWifiIface);
+            addInterfaceInfo(IfaceType.NAN);
+        }
+    }
+
+    private class RemoveXxxIfaceAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ChipMockBase mChipMockBase;
+        private int mType;
+
+        RemoveXxxIfaceAnswer(ChipMockBase chipMockBase, int type) {
+            mChipMockBase = chipMockBase;
+            mType = type;
+        }
+
+        private WifiStatus removeIface(int type, String ifname) {
+            try {
+                if (!mChipMockBase.interfaceNames.get(type).remove(ifname)) {
+                    return mStatusFail;
+                }
+                if (mChipMockBase.interfacesByName.get(type).remove(ifname) == null) {
+                    return mStatusFail;
+                }
+            } catch (Exception e) {
+                return mStatusFail;
+            }
+            return mStatusOk;
+        }
+
+        public WifiStatus answer(String ifname) {
+            return removeIface(mType, ifname);
+        }
+    }
+
+    private class GetNameAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private String mName;
+
+        GetNameAnswer(String name) {
+            mName = name;
+        }
+
+        public void answer(IWifiIface.getNameCallback cb) {
+            cb.onValues(mStatusOk, mName);
+        }
+    }
+
+    private class GetTypeAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private int mType;
+
+        GetTypeAnswer(int type) {
+            mType = type;
+        }
+
+        public void answer(IWifiIface.getTypeCallback cb) {
+            cb.onValues(mStatusOk, mType);
+        }
+    }
+
+    // chip configuration
+
+    private class ChipMockBase {
+        public IWifiChip chip;
+        public int chipId;
+        public boolean chipModeValid = false;
+        public int chipModeId = -1000;
+        public Map<Integer, ArrayList<String>> interfaceNames = new HashMap<>();
+        public Map<Integer, Map<String, IWifiIface>> interfacesByName = new HashMap<>();
+
+        public ArrayList<IWifiChip.ChipMode> availableModes;
+
+        void initialize() throws Exception {
+            chip = mock(IWifiChip.class);
+
+            interfaceNames.put(IfaceType.STA, new ArrayList<>());
+            interfaceNames.put(IfaceType.AP, new ArrayList<>());
+            interfaceNames.put(IfaceType.P2P, new ArrayList<>());
+            interfaceNames.put(IfaceType.NAN, new ArrayList<>());
+
+            interfacesByName.put(IfaceType.STA, new HashMap<>());
+            interfacesByName.put(IfaceType.AP, new HashMap<>());
+            interfacesByName.put(IfaceType.P2P, new HashMap<>());
+            interfacesByName.put(IfaceType.NAN, new HashMap<>());
+
+            when(chip.registerEventCallback(any(IWifiChipEventCallback.class))).thenReturn(
+                    mStatusOk);
+            when(chip.configureChip(anyInt())).thenAnswer(new ConfigureChipAnswer(this));
+            doAnswer(new GetIdAnswer(this)).when(chip).getId(any(IWifiChip.getIdCallback.class));
+            doAnswer(new GetModeAnswer(this)).when(chip).getMode(
+                    any(IWifiChip.getModeCallback.class));
+            GetXxxIfaceNamesAnswer getXxxIfaceNamesAnswer = new GetXxxIfaceNamesAnswer(this);
+            doAnswer(getXxxIfaceNamesAnswer).when(chip).getStaIfaceNames(
+                    any(IWifiChip.getStaIfaceNamesCallback.class));
+            doAnswer(getXxxIfaceNamesAnswer).when(chip).getApIfaceNames(
+                    any(IWifiChip.getApIfaceNamesCallback.class));
+            doAnswer(getXxxIfaceNamesAnswer).when(chip).getP2pIfaceNames(
+                    any(IWifiChip.getP2pIfaceNamesCallback.class));
+            doAnswer(getXxxIfaceNamesAnswer).when(chip).getNanIfaceNames(
+                    any(IWifiChip.getNanIfaceNamesCallback.class));
+            GetXxxIfaceAnswer getXxxIfaceAnswer = new GetXxxIfaceAnswer(this);
+            doAnswer(getXxxIfaceAnswer).when(chip).getStaIface(anyString(),
+                    any(IWifiChip.getStaIfaceCallback.class));
+            doAnswer(getXxxIfaceAnswer).when(chip).getApIface(anyString(),
+                    any(IWifiChip.getApIfaceCallback.class));
+            doAnswer(getXxxIfaceAnswer).when(chip).getP2pIface(anyString(),
+                    any(IWifiChip.getP2pIfaceCallback.class));
+            doAnswer(getXxxIfaceAnswer).when(chip).getNanIface(anyString(),
+                    any(IWifiChip.getNanIfaceCallback.class));
+            doAnswer(new RemoveXxxIfaceAnswer(this, IfaceType.STA)).when(chip).removeStaIface(
+                    anyString());
+            doAnswer(new RemoveXxxIfaceAnswer(this, IfaceType.AP)).when(chip).removeApIface(
+                    anyString());
+            doAnswer(new RemoveXxxIfaceAnswer(this, IfaceType.P2P)).when(chip).removeP2pIface(
+                    anyString());
+            doAnswer(new RemoveXxxIfaceAnswer(this, IfaceType.NAN)).when(chip).removeNanIface(
+                    anyString());
+        }
+    }
+
+    // emulate baseline/legacy config:
+    // mode: STA + NAN || P2P
+    // mode: NAN
+    private class BaselineChip extends ChipMockBase {
+        static final int STA_CHIP_MODE_ID = 0;
+        static final int AP_CHIP_MODE_ID = 1;
+
+        void initialize() throws Exception {
+            super.initialize();
+
+            // chip Id configuration
+            ArrayList<Integer> chipIds;
+            chipId = 10;
+            chipIds = new ArrayList<>();
+            chipIds.add(chipId);
+            doAnswer(new GetChipIdsAnswer(mStatusOk, chipIds)).when(mWifiMock).getChipIds(
+                    any(IWifi.getChipIdsCallback.class));
+
+            doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(10),
+                    any(IWifi.getChipCallback.class));
+
+            // initialize dummy chip modes
+            IWifiChip.ChipMode cm;
+            IWifiChip.ChipIfaceCombination cic;
+            IWifiChip.ChipIfaceCombinationLimit cicl;
+
+            //   Mode 0: 1xSTA + 1x{P2P,NAN}
+            //   Mode 1: 1xAP
+            availableModes = new ArrayList<>();
+            cm = new IWifiChip.ChipMode();
+            cm.id = STA_CHIP_MODE_ID;
+
+            cic = new IWifiChip.ChipIfaceCombination();
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cic.limits.add(cicl);
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.P2P);
+            cicl.types.add(IfaceType.NAN);
+            cic.limits.add(cicl);
+            cm.availableCombinations.add(cic);
+            availableModes.add(cm);
+
+            cm = new IWifiChip.ChipMode();
+            cm.id = AP_CHIP_MODE_ID;
+            cic = new IWifiChip.ChipIfaceCombination();
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.AP);
+            cic.limits.add(cicl);
+            cm.availableCombinations.add(cic);
+            availableModes.add(cm);
+
+            doAnswer(new GetAvailableModesAnswer(this)).when(chip)
+                    .getAvailableModes(any(IWifiChip.getAvailableModesCallback.class));
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java b/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
deleted file mode 100644
index 6e485f6..0000000
--- a/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
+++ /dev/null
@@ -1,143 +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 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.Field;
-import java.util.Iterator;
-
-public class HalMockUtils {
-    private static final String TAG = "HalMockUtils";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = true;
-
-    private static native int initHalMock();
-
-    public static native void setHalMockObject(Object obj);
-
-    static {
-        System.loadLibrary("wifi-hal-mock");
-    }
-
-    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.
-         */
-        Field field = WifiNative.class.getDeclaredField("sWifiHalHandle");
-        field.setAccessible(true);
-        long currentWifiHalHandle = field.getLong(null);
-        if (DBG) Log.d(TAG, "currentWifiHalHandle=" + currentWifiHalHandle);
-        if (currentWifiHalHandle == 0) {
-            field.setLong(null, 5);
-
-            field = WifiNative.class.getDeclaredField("sWifiIfaceHandles");
-            field.setAccessible(true);
-            long[] wifiIfaceHandles = {
-                    10 };
-            field.set(null, wifiIfaceHandles);
-        }
-
-        initHalMock();
-    }
-
-    /*
-     * JSON data-model for passing arguments between mock host (java) and mock
-     * HAL (C):
-     * {
-     *      "name" : { "type" : "int|byte_array", "value" : 123 | [1, 2, 3, 4] }
-     * }
-     */
-
-    private static final String TYPE_KEY = "type";
-    private static final String VALUE_KEY = "value";
-
-    private static final String TYPE_INT = "int";
-    private static final String TYPE_BYTE_ARRAY = "byte_array";
-
-    public static Bundle convertJsonToBundle(String jsonArgs) throws JSONException {
-        if (VDBG) Log.v(TAG, "convertJsonToBundle: jsonArgs=" + jsonArgs);
-
-        Bundle bundle = new Bundle();
-
-        JSONObject jsonObject = new JSONObject(jsonArgs);
-        Iterator<String> iter = jsonObject.keys();
-        while (iter.hasNext()) {
-            String key = iter.next();
-            JSONObject field = jsonObject.optJSONObject(key);
-
-            String type = field.getString(TYPE_KEY);
-
-            if (TYPE_INT.equals(type)) {
-                bundle.putInt(key, field.optInt(VALUE_KEY));
-            } else if (TYPE_BYTE_ARRAY.equals(type)) {
-                JSONArray array = field.optJSONArray(VALUE_KEY);
-                byte[] bArray = new byte[array.length()];
-                for (int i = 0; i < array.length(); ++i) {
-                    bArray[i] = (byte) array.getInt(i);
-                }
-                bundle.putByteArray(key, bArray);
-            } else {
-                throw new JSONException("Unexpected TYPE read from mock HAL -- '" + type + "'");
-            }
-        }
-
-        if (DBG) Log.d(TAG, "convertJsonToBundle: returning bundle=" + bundle);
-        return bundle;
-    }
-
-    public static JSONObject convertBundleToJson(Bundle bundle) throws JSONException {
-        if (VDBG) Log.v(TAG, "convertBundleToJson: bundle=" + bundle.toString());
-
-        JSONObject json = new JSONObject();
-        for (String key : bundle.keySet()) {
-            Object value = bundle.get(key);
-            JSONObject child = new JSONObject();
-            if (value instanceof Integer) {
-                child.put(TYPE_KEY, TYPE_INT);
-                child.put(VALUE_KEY, ((Integer) value).intValue());
-            } else if (value instanceof byte[]) {
-                byte[] array = (byte[]) value;
-                JSONArray jsonArray = new JSONArray();
-                for (int i = 0; i < array.length; ++i) {
-                    jsonArray.put(array[i]);
-                }
-                child.put(TYPE_KEY, TYPE_BYTE_ARRAY);
-                child.put(VALUE_KEY, jsonArray);
-            } else {
-                throw new JSONException("Unexpected type of JSON tree node (not an Integer "
-                        + "or byte[]): " + value);
-            }
-            json.put(key, child);
-        }
-
-        if (DBG) Log.d(TAG, "convertBundleToJson: returning JSONObject=" + json);
-        return json;
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/IMSIParameterTest.java b/tests/wifitests/src/com/android/server/wifi/IMSIParameterTest.java
new file mode 100644
index 0000000..d99d8a5
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/IMSIParameterTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.IMSIParameter}.
+ */
+@SmallTest
+public class IMSIParameterTest {
+    /**
+     * Data points for testing function {@link IMSIParameter#build}.
+     */
+    private static final Map<String, IMSIParameter> BUILD_PARAM_TEST_MAP = new HashMap<>();
+    static {
+        BUILD_PARAM_TEST_MAP.put(null, null);
+        BUILD_PARAM_TEST_MAP.put("", null);
+        BUILD_PARAM_TEST_MAP.put("1234a123", null);    // IMSI contained invalid character.
+        BUILD_PARAM_TEST_MAP.put("1234567890123451", null);   // IMSI exceeding max length.
+        BUILD_PARAM_TEST_MAP.put("123456789012345", new IMSIParameter("123456789012345", false));
+        BUILD_PARAM_TEST_MAP.put("1234*", new IMSIParameter("1234", true));
+    }
+
+    /**
+     * Verify the expectations of {@link IMSIParameter#build} function using the predefined
+     * test data {@link #BUILD_PARAM_TEST_MAP}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyBuildIMSIParameter() throws Exception {
+        for (Map.Entry<String, IMSIParameter> entry : BUILD_PARAM_TEST_MAP.entrySet()) {
+            assertEquals(entry.getValue(), IMSIParameter.build(entry.getKey()));
+        }
+    }
+
+    /**
+     * Verify that attempt to match a null IMSI will not cause any crash and should return false.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchesNullImsi() throws Exception {
+        IMSIParameter param = new IMSIParameter("1234", false);
+        assertFalse(param.matchesImsi(null));
+    }
+
+    /**
+     * Verify that an IMSIParameter containing a full IMSI will only match against an IMSI of the
+     * same value.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchesImsiWithFullImsi() throws Exception {
+        IMSIParameter param = new IMSIParameter("1234", false);
+
+        // Full IMSI requires exact matching.
+        assertFalse(param.matchesImsi("123"));
+        assertFalse(param.matchesImsi("12345"));
+        assertTrue(param.matchesImsi("1234"));
+    }
+
+    /**
+     * Verify that an IMSIParameter containing a prefix IMSI will match against any IMSI that
+     * starts with the same prefix.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchesImsiWithPrefixImsi() throws Exception {
+        IMSIParameter param = new IMSIParameter("1234", true);
+
+        // Prefix IMSI will match any IMSI that starts with the same prefix.
+        assertFalse(param.matchesImsi("123"));
+        assertTrue(param.matchesImsi("12345"));
+        assertTrue(param.matchesImsi("1234"));
+    }
+
+    /**
+     * Verify that attempt to match a null MCC-MNC will not cause any crash and should return
+     * false.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchesNullMccMnc() throws Exception {
+        IMSIParameter param = new IMSIParameter("1234", false);
+        assertFalse(param.matchesMccMnc(null));
+    }
+
+    /**
+     * Verify that an IMSIParameter containing a full IMSI will only match against a 6 digit
+     * MCC-MNC that is a prefix of the IMSI.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchesMccMncFullImsi() throws Exception {
+        IMSIParameter param = new IMSIParameter("1234567890", false);
+
+        assertFalse(param.matchesMccMnc("1234567"));    // Invalid length for MCC-MNC
+        assertFalse(param.matchesMccMnc("12345"));      // Invalid length for MCC-MNC
+        assertTrue(param.matchesMccMnc("123456"));
+    }
+
+    /**
+     * Verify that an IMSIParameter containing an IMSI prefix that's less than 6 digits
+     * will match against any 6-digit MCC-MNC that starts with the same prefix.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchesMccMncWithPrefixImsiLessThanMccMncLength() throws Exception {
+        IMSIParameter param = new IMSIParameter("12345", true);
+
+        assertFalse(param.matchesMccMnc("123448"));     // Prefix mismatch
+        assertFalse(param.matchesMccMnc("12345"));      // Invalid length for MCC-MNC
+        assertFalse(param.matchesMccMnc("1234567"));    // Invalid length for MCC-MNC
+        assertTrue(param.matchesMccMnc("123457"));
+        assertTrue(param.matchesMccMnc("123456"));
+    }
+
+    /**
+     * Verify that an IMSIParameter containing an IMSI prefix that's more than 6 digits
+     * will only match against a 6-digit MCC-MNC that matches the first 6-digit of the prefix.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchesMccMncWithPrefixImsiMoreThanMccMncLength() throws Exception {
+        IMSIParameter param = new IMSIParameter("1234567890", true);
+        assertFalse(param.matchesMccMnc("12345"));      // Invalid length for MCC-MNC
+        assertFalse(param.matchesMccMnc("1234567"));    // Invalid length for MCC-MNC
+        assertTrue(param.matchesMccMnc("123456"));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/LastMileLoggerTest.java b/tests/wifitests/src/com/android/server/wifi/LastMileLoggerTest.java
new file mode 100644
index 0000000..5c7c5ab
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/LastMileLoggerTest.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.FileUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import libcore.io.IoUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Unit tests for {@link LastMileLogger}.
+ */
+@SmallTest
+public class LastMileLoggerTest {
+    @Mock WifiInjector mWifiInjector;
+    @Spy FakeWifiLog mLog;
+    private static final long FAKE_CONNECTION_ID = 1;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mWifiInjector.makeLog(anyString())).thenReturn(mLog);
+        mTraceDataFile = File.createTempFile(TRACE_DATA_PREFIX, null);
+        mTraceEnableFile = File.createTempFile(TRACE_ENABLE_PREFIX, null);
+        mTraceReleaseFile = File.createTempFile(TRACE_RELEASE_PREFIX, null);
+        mTraceDataFile.deleteOnExit();
+        mTraceEnableFile.deleteOnExit();
+        mTraceReleaseFile.deleteOnExit();
+        FileUtils.stringToFile(mTraceEnableFile, "0");
+        mLastMileLogger = new LastMileLogger(mWifiInjector, mTraceDataFile.getPath(),
+                mTraceEnableFile.getPath(),  mTraceReleaseFile.getPath());
+    }
+
+    @Test
+    public void ctorDoesNotCrash() throws Exception {
+        new LastMileLogger(mWifiInjector, mTraceDataFile.getPath(), mTraceEnableFile.getPath(),
+                mTraceReleaseFile.getPath());
+        verifyZeroInteractions(mLog);
+    }
+
+    @Test
+    public void connectionEventStartedEnablesTracing() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        assertEquals("1", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    }
+
+    @Test
+    public void connectionEventStartedDoesNotCrashIfReleaseFileIsMissing() throws Exception {
+        mTraceReleaseFile.delete();
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        verify(mLog).warn(contains("Failed to open free_buffer"));
+    }
+
+    @Test
+    public void connectionEventStartedDoesNotEnableTracingIfReleaseFileIsMissing()
+            throws Exception {
+        mTraceReleaseFile.delete();
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    }
+
+    @Test
+    public void connectionEventStartedDoesNotAttemptToReopenReleaseFile() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+
+        // This is a rather round-about way of verifying that we don't attempt to re-open
+        // the file. Namely: if we delete the |release| file, and CONNECTION_EVENT_STARTED
+        // _did_ re-open the file, then we'd log an error message. Since the test is deleting the
+        // |release| file, the absence of a warning message means that we didn't try to open the
+        // file again.
+        //
+        // A more direct test would require the use of a factory for the creation of the
+        // FileInputStream.
+        mTraceReleaseFile.delete();
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        verifyZeroInteractions(mLog);
+    }
+
+    @Test
+    public void connectionEventStartedDoesNotEnableTracingForInvalidConnectionId()
+            throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                -1, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    }
+
+    @Test
+    public void connectionEventStartedDoesNotCrashIfEnableFileIsMissing() throws Exception {
+        mTraceEnableFile.delete();
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+    }
+
+    @Test
+    public void connectionEventStartedDoesNotCrashOnRepeatedCalls() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+    }
+
+    @Test
+    public void connectionEventSucceededDisablesTracing() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    }
+
+    @Test
+    public void connectionEventSucceededDoesNotCrashIfEnableFileIsMissing() throws Exception {
+        mTraceEnableFile.delete();
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+    }
+
+    @Test
+    public void connectionEventSucceededDoesNotCrashOnRepeatedCalls() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+    }
+
+    @Test
+    public void connectionEventFailedDisablesTracingWhenPendingFails() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(
+                    FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    }
+
+    @Test
+    public void connectionEventFailedDoesNotDisableTracingOnFailureOfStaleConnection()
+            throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID + 1, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        assertEquals("1", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    }
+
+    @Test
+    public void connectionEventFailedDisablesTracingOnFailureOfFutureConnection()
+            throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID + 1, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        assertEquals("0", IoUtils.readFileAsString(mTraceEnableFile.getPath()));
+    }
+
+    @Test
+    public void connectionEventFailedDoesNotCrashIfEnableFileIsMissing() throws Exception {
+        mTraceEnableFile.delete();
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+    }
+
+    @Test
+    public void connectionEventFailedDoesNotCrashIfDataFileIsMissing() throws Exception {
+        mTraceDataFile.delete();
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+    }
+
+    @Test
+    public void connectionEventFailedDoesNotCrashOnRepeatedCalls() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+    }
+
+    @Test
+    public void dumpShowsFailureTrace() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        assertTrue(getDumpString().contains("--- Last failed"));
+        assertTrue(getDumpString().contains("rdev_connect"));
+    }
+
+    @Test
+    public void dumpShowsFailureTraceEvenIfConnectionIdIncreases() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID + 1, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        assertTrue(getDumpString().contains("--- Last failed"));
+        assertTrue(getDumpString().contains("rdev_connect"));
+    }
+
+    @Test
+    public void dumpShowsPendingConnectionTrace() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
+        assertTrue(getDumpString().contains("No last mile log for \"Last failed"));
+        assertTrue(getDumpString().contains("--- Latest"));
+        assertTrue(getDumpString().contains("rdev_connect"));
+    }
+
+    @Test
+    public void dumpShowsLastFailureTraceAndPendingConnectionTrace() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #1");
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #2");
+
+        String dumpString = getDumpString();
+        assertTrue(dumpString.contains("rdev_connect try #1"));
+        assertTrue(dumpString.contains("rdev_connect try #2"));
+    }
+
+    @Test
+    public void dumpShowsLastFailureTraceAndCurrentConnectionTrace() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #1");
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect try #2");
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+
+        String dumpString = getDumpString();
+        assertTrue(dumpString.contains("rdev_connect try #1"));
+        assertTrue(dumpString.contains("rdev_connect try #2"));
+    }
+
+    @Test
+    public void dumpDoesNotClearLastFailureData() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
+
+        getDumpString();
+        String dumpString = getDumpString();
+        assertTrue(dumpString.contains("rdev_connect"));
+    }
+
+    @Test
+    public void dumpDoesNotClearPendingConnectionTrace() throws Exception {
+        mLastMileLogger.reportConnectionEvent(
+                FAKE_CONNECTION_ID, BaseWifiDiagnostics.CONNECTION_EVENT_STARTED);
+        FileUtils.stringToFile(mTraceDataFile.getPath(), "rdev_connect");
+
+        getDumpString();
+        String dumpString = getDumpString();
+        assertTrue(dumpString.contains("rdev_connect"));
+    }
+
+    @Test
+    public void dumpDoesNotCrashIfDataFileIsEmpty() throws Exception {
+        getDumpString();
+    }
+
+    @Test
+    public void dumpDoesNotCrashIfDataFileIsMissing() throws Exception {
+        mTraceDataFile.delete();
+        getDumpString();
+    }
+
+    private static final String TRACE_DATA_PREFIX = "last-mile-logger-trace-data";
+    private static final String TRACE_ENABLE_PREFIX = "last-mile-logger-trace-enable";
+    private static final String TRACE_RELEASE_PREFIX = "last-mile-logger-trace-release";
+    private LastMileLogger mLastMileLogger;
+    private File mTraceDataFile;
+    private File mTraceEnableFile;
+    private File mTraceReleaseFile;
+
+    private String getDumpString() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mLastMileLogger.dump(pw);
+        return sw.toString();
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/LocalOnlyHotspotRequestInfoTest.java b/tests/wifitests/src/com/android/server/wifi/LocalOnlyHotspotRequestInfoTest.java
new file mode 100644
index 0000000..5f170b8
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/LocalOnlyHotspotRequestInfoTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+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 com.android.server.wifi.LocalOnlyHotspotRequestInfo}.
+ */
+@SmallTest
+public class LocalOnlyHotspotRequestInfoTest {
+
+    private static final String TAG = "LocalOnlyHotspotRequestInfoTest";
+    @Mock IBinder mAppBinder;
+    @Mock LocalOnlyHotspotRequestInfo.RequestingApplicationDeathCallback mCallback;
+    private Handler mHandler;
+    private Messenger mMessenger;
+    private TestLooper mTestLooper;
+    RemoteException mRemoteException;
+    private LocalOnlyHotspotRequestInfo mLOHSRequestInfo;
+
+    /**
+     * Setup test.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTestLooper = new TestLooper();
+        mHandler = new Handler(mTestLooper.getLooper());
+        mMessenger = new Messenger(mHandler);
+        mRemoteException = new RemoteException("Test Remote Exception");
+    }
+
+    /**
+     * Make sure we link the call to request LocalOnlyHotspot by an app is linked to their Binder
+     * call.  This allows us to clean up if the app dies.
+     */
+    @Test
+    public void verifyBinderLinkToDeathIsCalled() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        verify(mAppBinder).linkToDeath(eq(mLOHSRequestInfo), eq(0));
+    }
+
+    /**
+     * Calls to create the requestor to binder death should not pass null callback.
+     */
+    @Test(expected = NullPointerException.class)
+    public void verifyNullCallbackChecked() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, null);
+    }
+
+    /**
+     * Calls to create the request info object should not pass a null messenger.
+     */
+    @Test(expected = NullPointerException.class)
+    public void verifyNullMessengerChecked() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, null, mCallback);
+    }
+
+    /**
+     * Calls to link the requestor to binder death should not pass null binder
+     */
+    @Test(expected = NullPointerException.class)
+    public void verifyNullBinderChecked() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(null, mMessenger, mCallback);
+    }
+
+    /**
+     * Calls to unlink the DeathRecipient should call to unlink from Binder.
+     */
+    @Test
+    public void verifyUnlinkDeathRecipientUnlinksFromBinder() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        mLOHSRequestInfo.unlinkDeathRecipient();
+        verify(mAppBinder).unlinkToDeath(eq(mLOHSRequestInfo), eq(0));
+    }
+
+    /**
+     * Binder death notification should trigger a callback on the requestor.
+     */
+    @Test
+    public void verifyBinderDeathTriggersCallback() {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        mLOHSRequestInfo.binderDied();
+        verify(mCallback).onLocalOnlyHotspotRequestorDeath(eq(mLOHSRequestInfo));
+    }
+
+    /**
+     * Verify a RemoteException when calling linkToDeath triggers the callback.
+     */
+    @Test
+    public void verifyRemoteExceptionTriggersCallback() throws Exception {
+        doThrow(mRemoteException).when(mAppBinder)
+                .linkToDeath(any(IBinder.DeathRecipient.class), eq(0));
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        verify(mCallback).onLocalOnlyHotspotRequestorDeath(eq(mLOHSRequestInfo));
+    }
+
+    /**
+     * Verify the pid is properly set.
+     */
+    @Test
+    public void verifyPid() {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        assertEquals(Process.myPid(), mLOHSRequestInfo.getPid());
+    }
+
+    /**
+     * Verify that sendHotspotFailedMessage does send a Message properly
+     */
+    @Test
+    public void verifySendFailedMessage() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        mLOHSRequestInfo.sendHotspotFailedMessage(
+                WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC);
+        Message message = mTestLooper.nextMessage();
+        assertEquals(WifiManager.HOTSPOT_FAILED, message.what);
+        assertEquals(WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC, message.arg1);
+    }
+
+    /**
+     * Verify that sendHotspotStartedMessage does send a Message properly
+     */
+    @Test
+    public void verifySendStartedMessage() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        WifiConfiguration config = mock(WifiConfiguration.class);
+        mLOHSRequestInfo.sendHotspotStartedMessage(config);
+        Message message = mTestLooper.nextMessage();
+        assertEquals(WifiManager.HOTSPOT_STARTED, message.what);
+        assertEquals(config, (WifiConfiguration) message.obj);
+    }
+
+    /**
+     * Verify that sendHotspotStoppedMessage does send a Message properly
+     */
+    @Test
+    public void verifySendStoppedMessage() throws Exception {
+        mLOHSRequestInfo = new LocalOnlyHotspotRequestInfo(mAppBinder, mMessenger, mCallback);
+        mLOHSRequestInfo.sendHotspotStoppedMessage();
+        Message message = mTestLooper.nextMessage();
+        assertEquals(WifiManager.HOTSPOT_STOPPED, message.what);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/LogcatLogTest.java b/tests/wifitests/src/com/android/server/wifi/LogcatLogTest.java
new file mode 100644
index 0000000..ac5afe7
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/LogcatLogTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link LogcatLog}.
+ */
+@SmallTest
+public class LogcatLogTest {
+    private static final String TAG = "LogcatLogTest";
+    private LogcatLog mLogger;
+
+    /** Initializes test fixture. */
+    @Before
+    public void setUp() {
+        mLogger = new LogcatLog(TAG);
+    }
+
+    /**
+     * Verifies that LogcatLog's LogMessage implementation correctly
+     * handles a format with no parameters.
+     *
+     * Note: In practice, we expect clients to use eC() and friends
+     * when the message is a literal. But we still want to make sure
+     * this functionality works.
+     */
+    @Test
+    public void logMessageWorksWithParameterlessFormat() {
+        WifiLog.LogMessage logMessage = mLogger.err("hello world");
+        logMessage.flush();
+        assertEquals("hello world", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works with an empty format. */
+    @Test
+    public void logMessageWorksWithEmptyFormat() {
+        WifiLog.LogMessage logMessage = mLogger.err("");
+        logMessage.flush();
+        assertEquals("", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works with a value-only format. */
+    @Test
+    public void logMessageWorksWithValueOnly() {
+        WifiLog.LogMessage logMessage = mLogger.err("%");
+        logMessage.c(1).flush();
+        assertEquals("1", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage works when the placeholder is replaced
+     * by the placeholder character.
+     */
+    @Test
+    public void logMessageIsNotConfusedByPlaceholderInValue() {
+        WifiLog.LogMessage logMessage = mLogger.err("%");
+        logMessage.c('%').flush();
+        assertEquals("%", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works when a value is at the start of the format. */
+    @Test
+    public void logMessageWorksWithValueAtBegin() {
+        WifiLog.LogMessage logMessage = mLogger.err("%stuff");
+        logMessage.c(1).flush();
+        assertEquals("1stuff", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works when a value is in the middle of the format. */
+    @Test
+    public void logMessageWorksWithValueInMiddle() {
+        WifiLog.LogMessage logMessage = mLogger.err("s%uff");
+        logMessage.c(1).flush();
+        assertEquals("s1uff", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works when a value is at the end of the format. */
+    @Test
+    public void logMessageWorksWithValueAtEnd() {
+        WifiLog.LogMessage logMessage = mLogger.err("stuff%");
+        logMessage.c(1).flush();
+        assertEquals("stuff1", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works when a format has multiple values. */
+    @Test
+    public void logMessageWorksWithMultipleValues() {
+        WifiLog.LogMessage logMessage = mLogger.err("% %");
+        logMessage.c("hello").c("world").flush();
+        assertEquals("hello world", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works when a format has multiple values and literals. */
+    @Test
+    public void logMessageWorksWithMultipleValuesAndLiterals() {
+        WifiLog.LogMessage logMessage = mLogger.err("first:% second:%");
+        logMessage.c("hello").c("world").flush();
+        assertEquals("first:hello second:world", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage works when a format has multiple adjacent values. */
+    @Test
+    public void logMessageWorksWithAdjacentValues() {
+        WifiLog.LogMessage logMessage = mLogger.err("%%");
+        logMessage.c("hello").c("world").flush();
+        assertEquals("helloworld", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage silently ignores extraneous values. */
+    @Test
+    public void logMessageSilentlyIgnoresExtraneousValues() {
+        WifiLog.LogMessage logMessage = mLogger.err("%");
+        logMessage.c("hello world");
+        logMessage.c("more stuff");
+        logMessage.flush();
+        assertEquals("hello world", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage silently ignores extraneous values,
+     * even with an empty format string.
+     */
+    @Test
+    public void logMessageSilentlyIgnoresExtraneousValuesEvenForEmptyFormat() {
+        WifiLog.LogMessage logMessage = mLogger.err("");
+        logMessage.c("hello world");
+        logMessage.c("more stuff");
+        logMessage.flush();
+        assertEquals("", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage silently ignores extraneous values,
+     * even if the format string is all literals.
+     */
+    @Test
+    public void logMessageSilentlyIgnoresExtraneousValuesEvenForFormatWithoutPlaceholders() {
+        WifiLog.LogMessage logMessage = mLogger.err("literal format");
+        logMessage.c("hello world");
+        logMessage.c("more stuff");
+        logMessage.flush();
+        assertEquals("literal format", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage copies an unused placeholder to output. */
+    @Test
+    public void logMessageCopiesUnusedPlaceholderToOutput() {
+        WifiLog.LogMessage logMessage = mLogger.err("%");
+        logMessage.flush();
+        assertEquals("%", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage copies multiple unused placeholders to output. */
+    @Test
+    public void logMessageCopiesMultipleUnusedPlaceholdersToOutput() {
+        WifiLog.LogMessage logMessage = mLogger.err("%%%%%");
+        logMessage.flush();
+        assertEquals("%%%%%", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage copies an unused placeholder to output,
+     * even if preceded by non-placeholders.
+     */
+    @Test
+    public void logMessageCopiesUnusedPlaceholderAtEndToOutput() {
+        WifiLog.LogMessage logMessage = mLogger.err("foo%");
+        logMessage.flush();
+        assertEquals("foo%", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage copies an unused placeholder to output,
+     * even if followed by non-placeholders.
+     */
+    @Test
+    public void logMessageCopiesUnusedPlaceholderAtBeginToOutput() {
+        WifiLog.LogMessage logMessage = mLogger.err("%foo");
+        logMessage.flush();
+        assertEquals("%foo", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage copies an unused placeholder to output,
+     * even if it is in the middle of non-placeholders.
+     */
+    @Test
+    public void logMessageCopiesUnusedPlaceholderInMiddleToOutput() {
+        WifiLog.LogMessage logMessage = mLogger.err("f%o");
+        logMessage.flush();
+        assertEquals("f%o", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage copies multiple unused placeholders to output,
+     * even if they are embedded amongst non-placeholders.
+     */
+    @Test
+    public void logMessageCopiesUnusedPlaceholdersInMiddleToOutput() {
+        WifiLog.LogMessage logMessage = mLogger.err("f%o%o%d");
+        logMessage.flush();
+        assertEquals("f%o%o%d", logMessage.toString());
+    }
+
+    /**
+     * Verifies that LogMessage preserves meta-characters in format string.
+     *
+     * Note that we deliberately test only the meta-characters that we
+     * expect to find in log messages. (Newline might also be
+     * preserved, but clients shouldn't depend on that, as messages
+     * that have newlines make logs hard to read.)
+     */
+    @Test
+    public void logMessagePreservesMetaCharactersInFormat() {
+        WifiLog.LogMessage logMessage = mLogger.err("\\hello\tworld\\");
+        logMessage.flush();
+        assertEquals("\\hello\tworld\\", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage propagates meta-characters in char values. */
+    @Test
+    public void logMessagePropagatesMetaCharactersInCharValues() {
+        WifiLog.LogMessage logMessage = mLogger.err("hello%big%world");
+        logMessage.c('\t').c('\\').flush();
+        assertEquals("hello\tbig\\world", logMessage.toString());
+    }
+
+    /** Verifies that LogMessage propagates meta-characters in String values. */
+    @Test
+    public void logMessagePropagatesMetaCharactersInStringValues() {
+        WifiLog.LogMessage logMessage = mLogger.err("%%world");
+        logMessage.c("hello\t").c("big\\").flush();
+        assertEquals("hello\tbig\\world", logMessage.toString());
+    }
+}
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..fc3418c 100644
--- a/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
+++ b/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
@@ -17,52 +17,40 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 
 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;
 
 /**
- * Creates a WifiMonitor and installs it as the current WifiMonitor instance
+ * Creates a mock WifiMonitor.
  * WARNING: This does not perfectly mock the behavior of WifiMonitor at the moment
- *          ex. startMoniroting does nothing and will not send a connection/disconnection event
+ *          ex. startMonitoring does nothing and will not send a connection/disconnection event
  */
-public class MockWifiMonitor {
-    private final WifiMonitor mWifiMonitor;
+public class MockWifiMonitor extends  WifiMonitor {
+    private final Map<String, SparseArray<Handler>> mHandlerMap = new HashMap<>();
 
-    public MockWifiMonitor() throws Exception {
-        mWifiMonitor = mock(WifiMonitor.class);
-
-        Field field = WifiMonitor.class.getDeclaredField("sWifiMonitor");
-        field.setAccessible(true);
-        field.set(null, mWifiMonitor);
-
-        doAnswer(new RegisterHandlerAnswer())
-                .when(mWifiMonitor).registerHandler(anyString(), anyInt(), any(Handler.class));
-
+    public MockWifiMonitor() {
+        super(mock(WifiInjector.class));
     }
 
-    private final Map<String, SparseArray<Handler>> mHandlerMap = new HashMap<>();
-    private class RegisterHandlerAnswer extends AnswerWithArguments {
-        public void answer(String iface, int what, Handler handler) {
-            SparseArray<Handler> ifaceHandlers = mHandlerMap.get(iface);
-            if (ifaceHandlers == null) {
-                ifaceHandlers = new SparseArray<>();
-                mHandlerMap.put(iface, ifaceHandlers);
-            }
-            ifaceHandlers.put(what, handler);
+    @Override
+    public void registerHandler(String iface, int what, Handler handler) {
+        SparseArray<Handler> ifaceHandlers = mHandlerMap.get(iface);
+        if (ifaceHandlers == null) {
+            ifaceHandlers = new SparseArray<>();
+            mHandlerMap.put(iface, ifaceHandlers);
         }
+        ifaceHandlers.put(what, handler);
+    }
+
+    @Override
+    public synchronized void startMonitoring(String iface, boolean isStaIface) {
+        return;
     }
 
     /**
@@ -71,6 +59,7 @@
     public void sendMessage(String iface, int what) {
         sendMessage(iface, Message.obtain(null, what));
     }
+
     public void sendMessage(String iface, Message message) {
         SparseArray<Handler> ifaceHandlers = mHandlerMap.get(iface);
         if (ifaceHandlers != null) {
@@ -87,6 +76,7 @@
                     + ",what=" + message.what, sent);
         }
     }
+
     private boolean sendMessage(SparseArray<Handler> ifaceHandlers, Message message) {
         Handler handler = ifaceHandlers.get(message.what);
         if (handler != null) {
diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
new file mode 100644
index 0000000..01257c1
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.XmlUtilTest;
+
+import org.junit.Before;
+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.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.NetworksListStoreData}.
+ */
+@SmallTest
+public class NetworkListStoreDataTest {
+
+    private static final String TEST_SSID = "WifiConfigStoreDataSSID_";
+    private static final String TEST_CONNECT_CHOICE = "XmlUtilConnectChoice";
+    private static final long TEST_CONNECT_CHOICE_TIMESTAMP = 0x4566;
+    private static final String SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT =
+            "<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"
+                    + "<boolean name=\"RequirePMF\" 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"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
+                    + "<boolean name=\"Shared\" value=\"%s\" />\n"
+                    + "<int name=\"Status\" value=\"2\" />\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"
+                    + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
+                    + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
+                    + "</WifiConfiguration>\n"
+                    + "<NetworkStatus>\n"
+                    + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
+                    + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\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";
+
+    private static final String SINGLE_EAP_NETWORK_DATA_XML_STRING_FORMAT =
+            "<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"
+                    + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
+                    + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">0c</byte-array>\n"
+                    + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
+                    + "<boolean name=\"Shared\" value=\"%s\" />\n"
+                    + "<int name=\"Status\" value=\"2\" />\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"
+                    + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
+                    + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
+                    + "</WifiConfiguration>\n"
+                    + "<NetworkStatus>\n"
+                    + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
+                    + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\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"
+                    + "<WifiEnterpriseConfiguration>\n"
+                    + "<string name=\"Identity\"></string>\n"
+                    + "<string name=\"AnonIdentity\"></string>\n"
+                    + "<string name=\"Password\"></string>\n"
+                    + "<string name=\"ClientCert\"></string>\n"
+                    + "<string name=\"CaCert\"></string>\n"
+                    + "<string name=\"SubjectMatch\"></string>\n"
+                    + "<string name=\"Engine\"></string>\n"
+                    + "<string name=\"EngineId\"></string>\n"
+                    + "<string name=\"PrivateKeyId\"></string>\n"
+                    + "<string name=\"AltSubjectMatch\"></string>\n"
+                    + "<string name=\"DomSuffixMatch\"></string>\n"
+                    + "<string name=\"CaPath\"></string>\n"
+                    + "<int name=\"EapMethod\" value=\"2\" />\n"
+                    + "<int name=\"Phase2Method\" value=\"0\" />\n"
+                    + "<string name=\"PLMN\"></string>\n"
+                    + "<string name=\"Realm\"></string>\n"
+                    + "</WifiEnterpriseConfiguration>\n"
+                    + "</Network>\n";
+
+    private NetworkListStoreData mNetworkListStoreData;
+
+    @Before
+    public void setUp() throws Exception {
+        mNetworkListStoreData = new NetworkListStoreData();
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     *
+     * @param shared Flag indicating serializing shared or user configurations
+     * @return byte[] of the XML data
+     * @throws Exception
+     */
+    private byte[] serializeData(boolean shared) throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mNetworkListStoreData.serializeData(out, shared);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     *
+     * @param data XML data to parse from
+     * @param shared Flag indicating parsing of shared or user configurations
+     * @return List of WifiConfiguration parsed
+     * @throws Exception
+     */
+    private List<WifiConfiguration> deserializeData(byte[] data, boolean shared) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mNetworkListStoreData.deserializeData(in, in.getDepth(), shared);
+        if (shared) {
+            return mNetworkListStoreData.getSharedConfigurations();
+        } else {
+            return mNetworkListStoreData.getUserConfigurations();
+        }
+    }
+
+    /**
+     * Helper function for generating a network list for testing purpose.  The network list
+     * will contained an open and an EAP network.
+     *
+     * @param shared Flag indicating shared network
+     * @return List of WifiConfiguration
+     */
+    private List<WifiConfiguration> getTestNetworksConfig(boolean shared) {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.shared = shared;
+        openNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        eapNetwork.shared = shared;
+        eapNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
+        List<WifiConfiguration> networkList = new ArrayList<>();
+        networkList.add(openNetwork);
+        networkList.add(eapNetwork);
+        return networkList;
+    }
+
+    /**
+     * Helper function for generating XML block containing two networks, an open and an EAP
+     * network.
+     *
+     * @param openNetwork The WifiConfiguration for an open network
+     * @param eapNetwork The WifiConfiguration for an EAP network
+     * @return byte[] of the XML data
+     */
+    private byte[] getTestNetworksXmlBytes(WifiConfiguration openNetwork,
+            WifiConfiguration eapNetwork) {
+        String openNetworkXml = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid);
+        String eapNetworkXml = String.format(SINGLE_EAP_NETWORK_DATA_XML_STRING_FORMAT,
+                eapNetwork.configKey().replaceAll("\"", "&quot;"),
+                eapNetwork.SSID.replaceAll("\"", "&quot;"),
+                eapNetwork.shared, eapNetwork.creatorUid);
+        return (openNetworkXml + eapNetworkXml).getBytes(StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Verify that serializing the store data without any configuration doesn't cause any crash
+     * and no data should be serialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeEmptyConfigs() throws Exception {
+        assertEquals(0, serializeData(true /* shared */).length);
+        assertEquals(0, serializeData(false /* shared */).length);
+    }
+
+    /**
+     * Verify that parsing an empty data doesn't cause any crash and no configuration should
+     * be parsed.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeEmptyData() throws Exception {
+        assertTrue(deserializeData(new byte[0], true /* shared */).isEmpty());
+        assertTrue(deserializeData(new byte[0], false /* shared */).isEmpty());
+    }
+
+    /**
+     * Verify that NetworkListStoreData does support share data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void supportShareData() throws Exception {
+        assertTrue(mNetworkListStoreData.supportShareData());
+    }
+
+    /**
+     * Verify that the shared configurations (containing an open and an EAP network) are serialized
+     * correctly, matching the expected XML string.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeSharedConfigurations() throws Exception {
+        List<WifiConfiguration> networkList = getTestNetworksConfig(true /* shared */);
+        mNetworkListStoreData.setSharedConfigurations(networkList);
+        byte[] expectedData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
+        assertTrue(Arrays.equals(expectedData, serializeData(true /* shared */)));
+    }
+
+    /**
+     * Verify that the shared configurations are parsed correctly from a XML string containing
+     * test networks (an open and an EAP network).
+     * @throws Exception
+     */
+    @Test
+    public void deserializeSharedConfigurations() throws Exception {
+        List<WifiConfiguration> networkList = getTestNetworksConfig(true /* shared */);
+        byte[] xmlData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
+                networkList, deserializeData(xmlData, true /* shared */));
+    }
+
+    /**
+     * Verify that the user configurations (containing an open and an EAP network) are serialized
+     * correctly, matching the expected XML string.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeUserConfigurations() throws Exception {
+        List<WifiConfiguration> networkList = getTestNetworksConfig(false /* shared */);
+        mNetworkListStoreData.setUserConfigurations(networkList);
+        byte[] expectedData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
+        assertTrue(Arrays.equals(expectedData, serializeData(false /* shared */)));
+    }
+
+    /**
+     * Verify that the user configurations are parsed correctly from a XML string containing
+     * test networks (an open and an EAP network).
+     * @throws Exception
+     */
+    @Test
+    public void deserializeUserConfigurations() throws Exception {
+        List<WifiConfiguration> networkList = getTestNetworksConfig(false /* shared */);
+        byte[] xmlData = getTestNetworksXmlBytes(networkList.get(0), networkList.get(1));
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
+                networkList, deserializeData(xmlData, false /* shared */));
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when parsing a <Network> block
+     * containing an unknown tag.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void parseNetworkWithUnknownTag() throws Exception {
+        String configFormat =
+                "<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"
+                        + "<boolean name=\"RequirePMF\" 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"
+                        + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
+                        + "<byte-array name=\"AllowedPairwiseCiphers\" 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"
+                        + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
+                        + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\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"
+                        + "<Unknown>"       // Unknown tag.
+                        + "<int name=\"test\" value=\"0\" />\n"
+                        + "</Unknown>"
+                        + "</Network>\n";
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        byte[] xmlData = String.format(configFormat,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid).getBytes(StandardCharsets.UTF_8);
+        deserializeData(xmlData, true);
+    }
+
+    /**
+     * Verify that a XmlPullParseException will be thrown when parsing a network configuration
+     * containing a mismatched config key.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void parseNetworkWithMismatchConfigKey() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        byte[] xmlData = String.format(SINGLE_OPEN_NETWORK_DATA_XML_STRING_FORMAT,
+                "InvalidConfigKey",
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid).getBytes(StandardCharsets.UTF_8);
+        deserializeData(xmlData, true);
+    }
+
+    /**
+     * Tests that an invalid data in one of the WifiConfiguration object parsing would be skipped
+     * gracefully. The other networks in the XML should still be parsed out correctly.
+     */
+    @Test
+    public void parseNetworkListWithOneNetworkIllegalArgException() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        String xmlString = new String(getTestNetworksXmlBytes(openNetwork, eapNetwork));
+        // Manipulate the XML data to set the EAP method to None, this should raise an Illegal
+        // argument exception in WifiEnterpriseConfig.setEapMethod().
+        xmlString = xmlString.replaceAll(
+                String.format(XmlUtilTest.XML_STRING_EAP_METHOD_REPLACE_FORMAT,
+                        eapNetwork.enterpriseConfig.getEapMethod()),
+                String.format(XmlUtilTest.XML_STRING_EAP_METHOD_REPLACE_FORMAT,
+                        WifiEnterpriseConfig.Eap.NONE));
+        List<WifiConfiguration> retrievedNetworkList =
+                deserializeData(xmlString.getBytes(StandardCharsets.UTF_8), true /* shared */);
+        // Retrieved network should not contain the eap network.
+        assertEquals(1, retrievedNetworkList.size());
+        for (WifiConfiguration network : retrievedNetworkList) {
+            assertNotEquals(eapNetwork.SSID, network.SSID);
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/PasspointManagementObjectManagerTest.java b/tests/wifitests/src/com/android/server/wifi/PasspointManagementObjectManagerTest.java
deleted file mode 100644
index d3022b9..0000000
--- a/tests/wifitests/src/com/android/server/wifi/PasspointManagementObjectManagerTest.java
+++ /dev/null
@@ -1,329 +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 static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-
-import android.net.wifi.PasspointManagementObjectDefinition;
-import android.net.wifi.WifiEnterpriseConfig;
-import android.security.KeyStore;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.wifi.hotspot2.omadm.MOTree;
-import com.android.server.wifi.hotspot2.omadm.OMAParser;
-import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
-import com.android.server.wifi.hotspot2.omadm.XMLNode;
-import com.android.server.wifi.hotspot2.pps.Credential;
-import com.android.server.wifi.hotspot2.pps.HomeSP;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.xml.sax.SAXException;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Unit tests for {@link com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager}.
- */
-@SmallTest
-public class PasspointManagementObjectManagerTest {
-
-    private static final String TAG = "PasspointManagementObjectManagerTest";
-
-    @Rule
-    public TemporaryFolder tempFolder = new TemporaryFolder();
-
-    private File createFileFromResource(String configFile) throws Exception {
-        InputStream in = getClass().getClassLoader().getResourceAsStream(configFile);
-        File file = tempFolder.newFile(configFile.split("/")[1]);
-
-        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-        FileOutputStream out = new FileOutputStream(file);
-
-        String line;
-
-        while ((line = reader.readLine()) != null) {
-            out.write(line.getBytes(StandardCharsets.UTF_8));
-        }
-
-        out.flush();
-        out.close();
-        return file;
-    }
-
-    private void addMoFromWifiConfig(PasspointManagementObjectManager moMgr) throws Exception {
-        HashMap<String, Long> ssidMap = new HashMap<>();
-        String fqdn = "tunisia.org";
-        HashSet<Long> roamingConsortiums = new HashSet<Long>();
-        HashSet<String> otherHomePartners = new HashSet<String>();
-        Set<Long> matchAnyOIs = new HashSet<Long>();
-        List<Long> matchAllOIs = new ArrayList<Long>();
-        String friendlyName = "Tunisian Passpoint Provider";
-        String iconUrl = "http://www.tunisia.org/icons/base_icon.png";
-
-        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
-        enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
-        enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.PAP);
-        enterpriseConfig.setIdentity("testIdentity1");
-        enterpriseConfig.setPassword("AnDrO1D");
-
-        KeyStore keyStore = mock(KeyStore.class);
-        Credential credential = new Credential(enterpriseConfig, keyStore, false);
-
-        HomeSP newHomeSP = new HomeSP(Collections.<String, Long>emptyMap(), fqdn,
-                roamingConsortiums, Collections.<String>emptySet(),
-                Collections.<Long>emptySet(), Collections.<Long>emptyList(),
-                friendlyName, null, credential);
-
-        moMgr.addSP(newHomeSP);
-    }
-
-    private void addMoFromXml(PasspointManagementObjectManager moMgr) throws Exception {
-        InputStream in = getClass().getClassLoader().getResourceAsStream(R2_TTLS_XML_FILE);
-        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-        StringBuilder builder = new StringBuilder();
-        String line;
-        while ((line = reader.readLine()) != null) {
-            builder.append(line).append("\n");
-        }
-
-        String xml = builder.toString();
-        moMgr.addSP(xml);
-    }
-
-    private static final String R1_CONFIG_FILE     = "assets/r1.PerProviderSubscription.conf";
-    private static final String R2_CONFIG_FILE     = "assets/r2.PerProviderSubscription.conf";
-    private static final String R2_TTLS_XML_FILE   = "assets/r2-ttls-tree.xml";
-    private static final String RUCKUS_CONFIG_FILE = "assets/ruckus.PerProviderSubscription.conf";
-
-    /** ensure that loading R1 configs works */
-    @Test
-    public void loadR1Configs() throws Exception {
-        File file = createFileFromResource(R1_CONFIG_FILE);
-        PasspointManagementObjectManager moMgr = new PasspointManagementObjectManager(file, true);
-        List<HomeSP> homeSPs = moMgr.loadAllSPs();
-        assertEquals(2, homeSPs.size());
-
-        /* TODO: Verify more attributes */
-        HomeSP homeSP = moMgr.getHomeSP("twcwifi.com");
-        assertNotNull(homeSP);
-        assertEquals("TWC-WiFi", homeSP.getFriendlyName());
-        assertEquals("tushar4", homeSP.getCredential().getUserName());
-
-        homeSP = moMgr.getHomeSP("wi-fi.org");
-        assertNotNull(homeSP);
-        assertEquals("Wi-Fi Alliance", homeSP.getFriendlyName());
-        assertEquals("sta006", homeSP.getCredential().getUserName());
-    }
-
-    /** ensure that loading R2 configs works */
-    @Test
-    public void loadR2Configs() throws Exception {
-        File file = createFileFromResource(R2_CONFIG_FILE);
-        PasspointManagementObjectManager moMgr = new PasspointManagementObjectManager(file, true);
-        List<HomeSP> homeSPs = moMgr.loadAllSPs();
-        assertEquals(2, homeSPs.size());
-
-        /* TODO: Verify more attributes */
-        HomeSP homeSP = moMgr.getHomeSP("twcwifi.com");
-        assertNotNull(homeSP);
-        assertEquals("TWC-WiFi", homeSP.getFriendlyName());
-        assertEquals("tushar4", homeSP.getCredential().getUserName());
-
-        homeSP = moMgr.getHomeSP("wi-fi.org");
-        assertNotNull(homeSP);
-        assertEquals("Wi-Fi Alliance", homeSP.getFriendlyName());
-        assertEquals("sta015", homeSP.getCredential().getUserName());
-    }
-
-    /** verify adding a new service provider works */
-    @Test
-    public void addSP() throws Exception {
-        File file = tempFolder.newFile("PerProviderSubscription.conf");
-        PasspointManagementObjectManager moMgr = new PasspointManagementObjectManager(file, true);
-        List<HomeSP> homeSPs = moMgr.loadAllSPs();
-        assertEquals(0, homeSPs.size());
-
-        addMoFromWifiConfig(moMgr);
-        addMoFromXml(moMgr);
-
-        PasspointManagementObjectManager moMgr2 = new PasspointManagementObjectManager(file, true);
-        homeSPs = moMgr2.loadAllSPs();
-        assertEquals(2, homeSPs.size());
-
-        /* TODO: Verify more attributes */
-        HomeSP homeSP = moMgr2.getHomeSP("rk-ttls.org");
-        assertNotNull(homeSP);
-        assertEquals("RK TTLS", homeSP.getFriendlyName());
-        assertEquals("sta020", homeSP.getCredential().getUserName());
-
-        homeSP = moMgr.getHomeSP("tunisia.org");
-        assertNotNull(homeSP);
-        assertEquals("Tunisian Passpoint Provider", homeSP.getFriendlyName());
-        assertEquals("testIdentity1", homeSP.getCredential().getUserName());
-    }
-
-    /** Verify IOException is thrown when trying to add a SP from a null XML string. */
-    @Test(expected = IOException.class)
-    public void addSPFromNullXmlString() throws Exception {
-        File file = tempFolder.newFile("PerProviderSubscription.conf");
-        PasspointManagementObjectManager moMgr = new PasspointManagementObjectManager(file, true);
-        String xml = null;  // Needed to avoid ambiguity on function call.
-        moMgr.addSP(xml);
-    }
-
-    /** Verify IOException is thrown when trying to build a SP from a null XML string. */
-    @Test(expected = IOException.class)
-    public void buildSPFromNullXmlString() throws Exception {
-        PasspointManagementObjectManager.buildSP(null);
-    }
-
-    /** verify that xml serialization/deserialization works */
-    public void checkXml() throws Exception {
-        InputStream in = getClass().getClassLoader().getResourceAsStream(R2_TTLS_XML_FILE);
-        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-        StringBuilder builder = new StringBuilder();
-        String line;
-        while ((line = reader.readLine()) != null) {
-            builder.append(line).append("\n");
-        }
-
-        String xmlIn = builder.toString();
-
-        try {
-            // Parse the file content:
-            OMAParser parser = new OMAParser();
-            MOTree moTree = parser.parse(xmlIn, "");
-            XMLNode rootIn = parser.getRoot();
-
-            // Serialize it back out:
-            String xmlOut = moTree.toXml();
-            parser = new OMAParser();
-            // And parse it again:
-            parser.parse(xmlOut, "");
-            XMLNode rootOut = parser.getRoot();
-
-            // Compare the two roots:
-            assertTrue("Checking serialized XML", rootIn.equals(rootOut));
-        } catch (SAXException se) {
-            throw new IOException(se);
-        }
-    }
-
-    /** verify modifying an existing service provider works */
-    @Test
-    public void modifySP() throws Exception {
-        File file = createFileFromResource(R2_CONFIG_FILE);
-        PasspointManagementObjectManager moMgr = new PasspointManagementObjectManager(file, true);
-        List<HomeSP> homeSPs = moMgr.loadAllSPs();
-        assertEquals(2, homeSPs.size());
-
-        /* verify that wi-fi.org has update identifier of 1 */
-        HomeSP homeSP = moMgr.getHomeSP("wi-fi.org");
-        assertNotNull(homeSP);
-        assertEquals("Wi-Fi Alliance", homeSP.getFriendlyName());
-        assertEquals("sta015", homeSP.getCredential().getUserName());
-        assertEquals(1, homeSP.getUpdateIdentifier());
-
-        /* PasspointManagementObjectDefinition to change update identifier */
-        String urn = "wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
-        String baseUri = "./Wi-Fi/wi-fi.org/PerProviderSubscription/UpdateIdentifier";
-        String xmlTree =
-                  "<MgmtTree>\n"
-                + "     <VerDTD>1.2</VerDTD>\n"
-                + "     <Node>\n"
-                + "         <NodeName>UpdateIdentifier</NodeName>\n"
-                + "         <Value>9</Value>\n"
-                + "     </Node>\n"
-                + "</MgmtTree>";
-
-        PasspointManagementObjectDefinition moDef =
-                new PasspointManagementObjectDefinition(baseUri, urn, xmlTree);
-        List<PasspointManagementObjectDefinition> moDefs =
-                new ArrayList<PasspointManagementObjectDefinition>();
-        moDefs.add(moDef);
-        moMgr.modifySP("wi-fi.org", moDefs);
-
-        /* reload all the SPs again */
-        moMgr.loadAllSPs();
-
-        homeSP = moMgr.getHomeSP("wi-fi.org");
-        assertEquals("Wi-Fi Alliance", homeSP.getFriendlyName());
-        assertEquals("sta015", homeSP.getCredential().getUserName());
-        assertEquals(9, homeSP.getUpdateIdentifier());
-    }
-
-    /** Verify IOException is thrown when trying to modify a SP using a null XML string. */
-    @Test(expected = IOException.class)
-    public void modifySPFromNullXmlString() throws Exception {
-        File file = createFileFromResource(R2_CONFIG_FILE);
-        PasspointManagementObjectManager moMgr = new PasspointManagementObjectManager(file, true);
-        List<HomeSP> homeSPs = moMgr.loadAllSPs();
-        assertEquals(2, homeSPs.size());
-
-        /* PasspointManagementObjectDefinition with null xmlTree. */
-        String urn = "wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
-        String baseUri = "./Wi-Fi/wi-fi.org/PerProviderSubscription/UpdateIdentifier";
-        String xmlTree = null;
-
-        PasspointManagementObjectDefinition moDef =
-                new PasspointManagementObjectDefinition(baseUri, urn, xmlTree);
-        List<PasspointManagementObjectDefinition> moDefs =
-                new ArrayList<PasspointManagementObjectDefinition>();
-        moDefs.add(moDef);
-        moMgr.modifySP("wi-fi.org", moDefs);
-    }
-
-    /** verify removing an existing service provider works */
-    @Test
-    public void removeSP() throws Exception {
-        File file = createFileFromResource(R2_CONFIG_FILE);
-        PasspointManagementObjectManager moMgr = new PasspointManagementObjectManager(file, true);
-        List<HomeSP> homeSPs = moMgr.loadAllSPs();
-        assertEquals(2, homeSPs.size());
-
-        moMgr.removeSP("wi-fi.org");
-
-        homeSPs = moMgr.loadAllSPs();
-        assertEquals(null, moMgr.getHomeSP("wi-fi.org"));
-    }
-}
-
-
-
-
-
-
-
-
-
diff --git a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
index 177705c..689ade8 100644
--- a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
@@ -30,14 +30,18 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.net.wifi.IWificond;
 import android.net.wifi.RttManager;
 import android.net.wifi.RttManager.ParcelableRttParams;
 import android.net.wifi.RttManager.ResponderConfig;
 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 +64,11 @@
     Context mContext;
     @Mock
     WifiNative mWifiNative;
-    MockLooper mLooper;
+    TestLooper mLooper;
+    @Mock
+    WifiInjector mWifiInjector;
+    @Mock
+    IWificond mWificond;
 
     RttService.RttServiceImpl mRttServiceImpl;
     ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor = ArgumentCaptor
@@ -69,9 +77,11 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        TestUtil.installWlanWifiNative(mWifiNative);
-        mLooper = new MockLooper();
-        mRttServiceImpl = new RttService.RttServiceImpl(mContext, mLooper.getLooper());
+        mLooper = new TestLooper();
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
+        mRttServiceImpl = new RttService.RttServiceImpl(mContext, mLooper.getLooper(),
+                mWifiInjector);
         mRttServiceImpl.startService();
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/SavedNetworkEvaluatorTest.java b/tests/wifitests/src/com/android/server/wifi/SavedNetworkEvaluatorTest.java
new file mode 100644
index 0000000..7f63604
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/SavedNetworkEvaluatorTest.java
@@ -0,0 +1,548 @@
+/*
+ * 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.WifiConfigurationTestUtil.SECURITY_NONE;
+import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LocalLog;
+
+import com.android.internal.R;
+import com.android.server.wifi.WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.SavedNetworkEvaluator}.
+ */
+@SmallTest
+public class SavedNetworkEvaluatorTest {
+
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        setupContext();
+        setupResource();
+        setupWifiConfigManager();
+        mLocalLog = new LocalLog(512);
+
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
+
+        mThresholdMinimumRssi2G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mThresholdMinimumRssi5G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdQualifiedRssi2G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+        mThresholdQualifiedRssi5G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+        mThresholdSaturatedRssi2G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz);
+        mThresholdSaturatedRssi5G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz);
+
+        mSavedNetworkEvaluator = new SavedNetworkEvaluator(mContext, mWifiConfigManager,
+                mClock, mLocalLog, mWifiConnectivityHelper);
+    }
+
+    /** Cleans up test. */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    private SavedNetworkEvaluator mSavedNetworkEvaluator;
+    @Mock private WifiConfigManager mWifiConfigManager;
+    @Mock private WifiConnectivityHelper mWifiConnectivityHelper;
+    @Mock private Context mContext;
+    @Mock private Resources mResource;
+    @Mock private Clock mClock;
+    private LocalLog mLocalLog;
+    private int mThresholdMinimumRssi2G;
+    private int mThresholdMinimumRssi5G;
+    private int mThresholdQualifiedRssi2G;
+    private int mThresholdQualifiedRssi5G;
+    private int mThresholdSaturatedRssi2G;
+    private int mThresholdSaturatedRssi5G;
+
+    private void setupContext() {
+        when(mContext.getResources()).thenReturn(mResource);
+    }
+
+    private void setupResource() {
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz))
+                .thenReturn(-57);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz))
+                .thenReturn(-60);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz))
+                .thenReturn(-70);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz))
+                .thenReturn(-73);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz))
+                .thenReturn(-82);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz))
+                .thenReturn(-85);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_RSSI_SCORE_SLOPE))
+                .thenReturn(4);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_RSSI_SCORE_OFFSET))
+                .thenReturn(85);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_SAME_BSSID_AWARD))
+                .thenReturn(24);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_SECURITY_AWARD))
+                .thenReturn(80);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_5GHz_preference_boost_factor))
+                .thenReturn(16);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_current_network_boost))
+                .thenReturn(16);
+    }
+
+    private void setupWifiConfigManager() {
+        when(mWifiConfigManager.getLastSelectedNetwork())
+                .thenReturn(WifiConfiguration.INVALID_NETWORK_ID);
+    }
+
+    /**
+     * Do not evaluate networks that {@link WifiConfiguration#useExternalScores}.
+     */
+    @Test
+    public void ignoreNetworksIfUseExternalScores() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 10};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        for (WifiConfiguration wifiConfiguration : savedConfigs) {
+            wifiConfiguration.useExternalScores = true;
+        }
+
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, true, false, null);
+
+        assertNull(candidate);
+    }
+
+    /**
+     * Do not evaluate networks that {@link WifiConfiguration#isEphemeral}.
+     */
+    @Test
+    public void ignoreEphemeralNetworks() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[ESS]", "[ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 10};
+        int[] securities = {SECURITY_NONE, SECURITY_NONE};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        for (WifiConfiguration wifiConfiguration : savedConfigs) {
+            wifiConfiguration.ephemeral = true;
+        }
+
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, true, false, null);
+
+        assertNull(candidate);
+    }
+
+    /**
+     * Set the candidate {@link ScanResult} for all {@link WifiConfiguration}s regardless of
+     * whether they are secure saved, open saved, or {@link WifiConfiguration#useExternalScores}.
+     */
+    @Test
+    public void setCandidateScanResultsForAllSavedNetworks() {
+        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
+        int[] freqs = {5200, 5220, 5240};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels =
+                {mThresholdQualifiedRssi5G, mThresholdQualifiedRssi5G, mThresholdQualifiedRssi5G};
+        int[] securities = {SECURITY_PSK, SECURITY_NONE, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration useExternalScoresConfig = scanDetailsAndConfigs.getWifiConfigs()[0];
+        useExternalScoresConfig.useExternalScores = true;
+        WifiConfiguration openNetworkConfig = scanDetailsAndConfigs.getWifiConfigs()[1];
+        WifiConfiguration secureNetworkConfig = scanDetailsAndConfigs.getWifiConfigs()[2];
+
+        mSavedNetworkEvaluator.evaluateNetworks(scanDetails, null, null, true, false, null);
+
+        verify(mWifiConfigManager, atLeastOnce()).setNetworkCandidateScanResult(
+                eq(useExternalScoresConfig.networkId),
+                eq(scanDetails.get(0).getScanResult()),
+                anyInt());
+        verify(mWifiConfigManager, atLeastOnce()).setNetworkCandidateScanResult(
+                eq(openNetworkConfig.networkId),
+                eq(scanDetails.get(1).getScanResult()),
+                anyInt());
+        verify(mWifiConfigManager, atLeastOnce()).setNetworkCandidateScanResult(
+                eq(secureNetworkConfig.networkId),
+                eq(scanDetails.get(2).getScanResult()),
+                anyInt());
+    }
+
+    /**
+     * Between two 2G networks, choose the one with stronger RSSI value if other conditions
+     * are the same and the RSSI values are not saturated.
+     */
+    @Test
+    public void chooseStrongerRssi2GNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 10};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, true, false, null);
+
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * Between two 5G networks, choose the one with stronger RSSI value if other conditions
+     * are the same and the RSSI values are not saturated.
+     */
+    @Test
+    public void chooseStrongerRssi5GNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {5200, 5240};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G + 8, mThresholdQualifiedRssi5G + 10};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, true, false, null);
+
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * Choose secure network over open network if other conditions are the same.
+     */
+    @Test
+    public void chooseSecureNetworkOverOpenNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {5200, 5240};
+        String[] caps = {"[ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G, mThresholdQualifiedRssi5G};
+        int[] securities = {SECURITY_NONE, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, true, false, null);
+
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * Choose 5G network over 2G network if other conditions are the same.
+     */
+    @Test
+    public void choose5GNetworkOver2GNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5240};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G, mThresholdQualifiedRssi5G};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, true, false, null);
+
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * Verify that we stick to the currently connected network if the other one is
+     * just slightly better scored.
+     */
+    @Test
+    public void stickToCurrentNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {5200, 5240};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        // test2 has slightly stronger RSSI value than test1
+        int[] levels = {mThresholdMinimumRssi5G + 2, mThresholdMinimumRssi5G + 4};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        // Simuluate we are connected to SSID test1 already.
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                savedConfigs[0], null, true, false, null);
+
+        // Even though test2 has higher RSSI value, test1 is chosen because of the
+        // currently connected network bonus.
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * Verify that we stick to the currently connected BSSID if the other one is
+     * just slightly better scored.
+     */
+    @Test
+    public void stickToCurrentBSSID() {
+        // Same SSID
+        String[] ssids = {"\"test1\"", "\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {5200, 5240};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        // test2 has slightly stronger RSSI value than test1
+        int[] levels = {mThresholdMinimumRssi5G + 2, mThresholdMinimumRssi5G + 6};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        // Simuluate we are connected to BSSID "6c:f3:7f:ae:8c:f3" already
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, bssids[0], true, false, null);
+
+        // Even though test2 has higher RSSI value, test1 is chosen because of the
+        // currently connected BSSID bonus.
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+    }
+
+    /**
+     * Verify that the same BSSID award is applied to all the BSSIDs which are under the same
+     * network as the currently connected BSSID.
+     */
+    @Test
+    public void currentBssidAwardForAllBssidsWithinTheSameNetworkWhenFirmwareRoamingSupported() {
+        // Three BSSIDs are carefully setup there:
+        // BSSID_0 and BSSID_1 have the same SSID and security type, so they are considered under
+        // the same 2.4 GHz network. BSSID_1 RSSI is stronger than BSSID_0.
+        // BSSID_2 is under a 5GHz network different from BSSID_0 and BSSID_1. Its RSSI is
+        // slightly stronger than BSSID_1.
+        //
+        // When firmware roaming is not supported, BSSID_2 has higher score than BSSID_0 and
+        // BSSID_1.
+        // When firmware roaming is suported, BSSID_1 has higher score than BSSID_2 because the
+        // same BSSID award is now applied to both BSSID_0 and BSSID_1.
+        String[] ssids = {"\"test1\"", "\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
+        int[] freqs = {2470, 2437, 5200};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + 2, mThresholdMinimumRssi2G + 5,
+                mThresholdMinimumRssi5G + 7};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        // Firmware roaming is not supported.
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
+        // Simuluate we are connected to BSSID_0 already.
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                savedConfigs[0], bssids[0], true, false, null);
+        // Verify that BSSID_2 is chosen.
+        ScanResult chosenScanResult = scanDetails.get(2).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[2], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+
+        // Firmware roaming is supported.
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        // Simuluate we are connected to BSSID_0 already.
+        candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                savedConfigs[0], bssids[0], true, false, null);
+        // Verify that BSSID_1 is chosen.
+        chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * One 2.4GHz network and one 5GHz network have the same security type. Perform
+     * the following tests to verify that once across the RSSI saturation threshold
+     * stronger RSSI value doesn't increase network score.
+     *
+     * 1) Both 2.4GHz network and 5GHz network have the same RSSI value,
+     *    mThresholdQualifiedRssi2G, which is below the saturation threshold. 5GHz
+     *    network is chosen because of the 5G band award.
+     * 2) Bump up 2.4GHz network RSSI 20dBm higher. Verify that it helps the 2.4GHz network
+     *    score and it gets chosen over the 5GHz network.
+     * 3) Bring both 2.4GHz network and 5GHz network RSSI value to mThresholdSaturatedRssi2G.
+     *    Verify that 5GHz network is chosen because of the 5G band award.
+     * 4) Bump up 2.4GHz network RSSI to be 20dBm higher than mThresholdSaturatedRssi2G.
+     *    Verify that the incresed RSSI doesn't help 2.4GHz network score and 5GHz network
+     *    is still chosen.
+     */
+    @Test
+    public void saturatedRssiAddsNoWeightToNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5400};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        // 1) The RSSI of both networks is mThresholdQualifiedRssi2G
+        int[] levels = {mThresholdQualifiedRssi2G, mThresholdQualifiedRssi2G};
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        WifiConfiguration candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, false, null);
+        // Verify that 5GHz network is chosen because of 5G band award
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+
+        // 2) Bump up 2.4GHz network RSSI by 20dBm.
+        levels[0] = mThresholdQualifiedRssi2G + 20;
+        scanDetailsAndConfigs = WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids,
+                bssids, freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        scanDetails = scanDetailsAndConfigs.getScanDetails();
+        savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails, null, null, false,
+                false, null);
+        // Verify that 2.4GHz network is chosen because of much higher RSSI value
+        chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+
+        // 3) Bring both 2.4GHz network and 5GHz network RSSI to mThresholdSaturatedRssi2G
+        levels[0] = levels[1] = mThresholdSaturatedRssi2G;
+        scanDetailsAndConfigs = WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids,
+                bssids, freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        scanDetails = scanDetailsAndConfigs.getScanDetails();
+        savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails, null, null, false,
+                false, null);
+        // Verify that 5GHz network is chosen because of 5G band award
+        chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+
+        // 4) Bump 2.4GHz network RSSI to be 20dBm higher than mThresholdSaturatedRssi2G
+        levels[0] = mThresholdSaturatedRssi2G + 20;
+        scanDetailsAndConfigs = WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids,
+                bssids, freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        scanDetails = scanDetailsAndConfigs.getScanDetails();
+        savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        candidate = mSavedNetworkEvaluator.evaluateNetworks(scanDetails, null, null, false,
+                false, null);
+        // Verify that the increased RSSI doesn't help 2.4GHz network and 5GHz network
+        // is still chosen
+        chosenScanResult = scanDetails.get(1).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java b/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
index 7a1bdd2..ab9a762 100644
--- a/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/ScanTestUtil.java
@@ -111,12 +111,16 @@
         }
 
         /**
-         * Add the provided hidden network IDs to scan request.
-         * @param networkIds List of hidden network IDs
+         * Add the provided hidden network SSIDs to scan request.
+         * @param networkSSIDs List of hidden network SSIDs
          * @return builder object
          */
-        public NativeScanSettingsBuilder withHiddenNetworkIds(int[] networkIds) {
-            mSettings.hiddenNetworkIds = networkIds;
+        public NativeScanSettingsBuilder withHiddenNetworkSSIDs(String[] networkSSIDs) {
+            mSettings.hiddenNetworks = new WifiNative.HiddenNetwork[networkSSIDs.length];
+            for (int i = 0; i < networkSSIDs.length; i++) {
+                mSettings.hiddenNetworks[i] = new WifiNative.HiddenNetwork();
+                mSettings.hiddenNetworks[i].ssid = networkSSIDs[i];
+            }
             return this;
         }
 
@@ -233,6 +237,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 +257,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) {
@@ -367,10 +375,6 @@
         for (int i = 0; i < expected.networkList.length; i++) {
             assertEquals("networkList[" + i + "].ssid",
                     expected.networkList[i].ssid, actual.networkList[i].ssid);
-            assertEquals("networkList[" + i + "].networkId",
-                    expected.networkList[i].networkId, actual.networkList[i].networkId);
-            assertEquals("networkList[" + i + "].priority",
-                    expected.networkList[i].priority, actual.networkList[i].priority);
             assertEquals("networkList[" + i + "].flags",
                     expected.networkList[i].flags, actual.networkList[i].flags);
             assertEquals("networkList[" + i + "].auth_bit_field",
diff --git a/tests/wifitests/src/com/android/server/wifi/ScoredNetworkEvaluatorTest.java b/tests/wifitests/src/com/android/server/wifi/ScoredNetworkEvaluatorTest.java
new file mode 100644
index 0000000..55b00c9
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/ScoredNetworkEvaluatorTest.java
@@ -0,0 +1,577 @@
+/*
+ * 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.WifiConfigurationTestUtil.SECURITY_NONE;
+import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.Uri;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LocalLog;
+
+import com.android.server.wifi.WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link ScoredNetworkEvaluator}.
+ */
+@SmallTest
+public class ScoredNetworkEvaluatorTest {
+    private ContentObserver mContentObserver;
+    private int mThresholdQualifiedRssi2G;
+    private int mThresholdQualifiedRssi5G;
+
+    @Mock private Context mContext;
+    @Mock private Clock mClock;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private NetworkScoreManager mNetworkScoreManager;
+    @Mock private WifiConfigManager mWifiConfigManager;
+
+    @Captor private ArgumentCaptor<NetworkKey[]> mNetworkKeyArrayCaptor;
+
+    private WifiNetworkScoreCache mScoreCache;
+    private ScoredNetworkEvaluator mScoredNetworkEvaluator;
+
+    @Before
+    public void setUp() throws Exception {
+        mThresholdQualifiedRssi2G = -73;
+        mThresholdQualifiedRssi5G = -70;
+
+        MockitoAnnotations.initMocks(this);
+
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0))
+                .thenReturn(1);
+
+        ArgumentCaptor<ContentObserver> observerCaptor =
+                ArgumentCaptor.forClass(ContentObserver.class);
+        mScoreCache = new WifiNetworkScoreCache(mContext);
+        mScoredNetworkEvaluator = new ScoredNetworkEvaluator(mContext,
+                Looper.getMainLooper(), mFrameworkFacade, mNetworkScoreManager,
+                mWifiConfigManager, new LocalLog(0), mScoreCache);
+        verify(mFrameworkFacade).registerContentObserver(eq(mContext), any(Uri.class), eq(false),
+                observerCaptor.capture());
+        mContentObserver = observerCaptor.getValue();
+
+        reset(mNetworkScoreManager);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
+    }
+
+    @After
+    public void tearDown() {
+        validateMockitoUsage();
+    }
+
+    @Test
+    public void testUpdate_recommendationsDisabled() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {2470};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = WifiNetworkSelectorTestUtil
+                .setupScanDetailsAndConfigStore(
+                ssids, bssids, freqs, caps, levels, securities, mWifiConfigManager, mClock);
+
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0))
+                .thenReturn(0);
+
+        mContentObserver.onChange(false /* unused */);
+
+        mScoredNetworkEvaluator.update(scanDetailsAndConfigs.getScanDetails());
+
+        verifyZeroInteractions(mNetworkScoreManager);
+    }
+
+    @Test
+    public void testUpdate_emptyScanList() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {2470};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = WifiNetworkSelectorTestUtil
+                .setupScanDetailsAndConfigStore(
+                        ssids, bssids, freqs, caps, levels, securities, mWifiConfigManager, mClock);
+
+        mScoredNetworkEvaluator.update(new ArrayList<ScanDetail>());
+
+        verifyZeroInteractions(mNetworkScoreManager);
+    }
+
+    @Test
+    public void testUpdate_allNetworksUnscored() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
+        int[] securities = {SECURITY_PSK, SECURITY_NONE};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 10};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = WifiNetworkSelectorTestUtil
+                .setupScanDetailsAndConfigStore(
+                        ssids, bssids, freqs, caps, levels, securities, mWifiConfigManager, mClock);
+
+        mScoredNetworkEvaluator.update(scanDetailsAndConfigs.getScanDetails());
+
+        verify(mNetworkScoreManager).requestScores(mNetworkKeyArrayCaptor.capture());
+        assertEquals(2, mNetworkKeyArrayCaptor.getValue().length);
+        NetworkKey expectedNetworkKey = NetworkKey.createFromScanResult(
+                scanDetailsAndConfigs.getScanDetails().get(0).getScanResult());
+        assertEquals(expectedNetworkKey, mNetworkKeyArrayCaptor.getValue()[0]);
+        expectedNetworkKey = NetworkKey.createFromScanResult(
+                scanDetailsAndConfigs.getScanDetails().get(1).getScanResult());
+        assertEquals(expectedNetworkKey, mNetworkKeyArrayCaptor.getValue()[1]);
+    }
+
+    @Test
+    public void testUpdate_oneScored_oneUnscored() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
+        int[] securities = {SECURITY_PSK, SECURITY_NONE};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 10};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs = WifiNetworkSelectorTestUtil
+                .setupScanDetailsAndConfigStore(
+                        ssids, bssids, freqs, caps, levels, securities, mWifiConfigManager, mClock);
+
+        List<ScanDetail> scoredScanDetails = scanDetailsAndConfigs.getScanDetails().subList(0, 1);
+        Integer[] scores = {120};
+        boolean[] meteredHints = {true};
+        WifiNetworkSelectorTestUtil.configureScoreCache(
+                mScoreCache, scoredScanDetails, scores, meteredHints);
+
+        mScoredNetworkEvaluator.update(scanDetailsAndConfigs.getScanDetails());
+
+        verify(mNetworkScoreManager).requestScores(mNetworkKeyArrayCaptor.capture());
+
+        NetworkKey[] requestedScores = mNetworkKeyArrayCaptor.getValue();
+        assertEquals(1, requestedScores.length);
+        NetworkKey expectedNetworkKey = NetworkKey.createFromScanResult(
+                scanDetailsAndConfigs.getScanDetails().get(1).getScanResult());
+        assertEquals(expectedNetworkKey, requestedScores[0]);
+    }
+
+    @Test
+    public void testEvaluateNetworks_recommendationsDisabled() {
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0))
+                .thenReturn(0);
+
+        mContentObserver.onChange(false /* unused */);
+
+        mScoredNetworkEvaluator.evaluateNetworks(null, null, null, false, false, null);
+
+        verifyZeroInteractions(mWifiConfigManager, mNetworkScoreManager);
+    }
+
+    /**
+     * When no saved networks available, choose the available ephemeral networks
+     * if untrusted networks are allowed.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseEphemeralNetworkBecauseOfNoSavedNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 10};
+        Integer[] scores = {null, 120};
+        boolean[] meteredHints = {false, true};
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        // No saved networks.
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+                .thenReturn(null);
+
+        ScanResult scanResult = scanDetails.get(1).getScanResult();
+        WifiConfiguration ephemeralNetworkConfig = WifiNetworkSelectorTestUtil
+                .setupEphemeralNetwork(mWifiConfigManager, 1, scanDetails.get(1), meteredHints[1]);
+
+        // Untrusted networks allowed.
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(ephemeralNetworkConfig, candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanResult, candidate);
+        assertEquals(meteredHints[1], candidate.meteredHint);
+    }
+
+    /**
+     * When no saved networks available, choose the highest scored ephemeral networks
+     * if untrusted networks are allowed.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseHigherScoredEphemeralNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[ESS]", "[ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 8};
+        Integer[] scores = {100, 120};
+        boolean[] meteredHints = {true, true};
+        ScanResult[] scanResults = new ScanResult[2];
+        WifiConfiguration[] ephemeralNetworkConfigs = new WifiConfiguration[2];
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        // No saved networks.
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+                .thenReturn(null);
+
+        for (int i = 0; i < 2; i++) {
+            scanResults[i] = scanDetails.get(i).getScanResult();
+            ephemeralNetworkConfigs[i] = WifiNetworkSelectorTestUtil.setupEphemeralNetwork(
+                    mWifiConfigManager, i, scanDetails.get(i), meteredHints[i]);
+        }
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(ephemeralNetworkConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanResults[1], candidate);
+        assertEquals(meteredHints[1], candidate.meteredHint);
+    }
+
+    /**
+     * Don't choose available ephemeral networks if no saved networks and untrusted networks
+     * are not allowed.
+     */
+    @Test
+    public void testEvaluateNetworks_noEphemeralNetworkWhenUntrustedNetworksNotAllowed() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 10};
+        Integer[] scores = {null, 120};
+        boolean[] meteredHints = {false, true};
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(
+                ssids, bssids, freqs, caps, levels, mClock);
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        // No saved networks.
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+                .thenReturn(null);
+
+        WifiNetworkSelectorTestUtil.setupEphemeralNetwork(
+                mWifiConfigManager, 1, scanDetails.get(1), meteredHints[1]);
+
+        // Untrusted networks not allowed.
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, false, null);
+
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+    /**
+     * Choose externally scored saved network.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseSavedNetworkWithExternalScore() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {5200};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] securities = {SECURITY_PSK};
+        int[] levels = {mThresholdQualifiedRssi5G + 8};
+        Integer[] scores = {120};
+        boolean[] meteredHints = {false};
+
+        WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        savedConfigs[0].useExternalScores = true;
+
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanDetails.get(0).getScanResult(), candidate);
+    }
+
+    /**
+     * Choose externally scored saved network with higher score.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseSavedNetworkWithHigherExternalScore() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 8};
+        Integer[] scores = {100, 120};
+        boolean[] meteredHints = {false, false};
+
+        WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        savedConfigs[0].useExternalScores = savedConfigs[1].useExternalScores = true;
+
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanDetails.get(1).getScanResult(), candidate);
+    }
+
+    /**
+     * Prefer externally scored saved network over untrusted network when they have
+     * the same score.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseExternallyScoredOverUntrustedNetworksWithSameScore() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
+        int[] securities = {SECURITY_PSK, SECURITY_NONE};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 8};
+        Integer[] scores = {120, 120};
+        boolean[] meteredHints = {false, true};
+
+        WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        savedConfigs[0].useExternalScores = true;
+
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanDetails.get(0).getScanResult(), candidate);
+    }
+
+    /**
+     * Choose untrusted network when it has higher score than the externally scored
+     * saved network.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseUntrustedWithHigherScoreThanExternallyScoredNetwork() {
+        // Saved network.
+        String[] savedSsids = {"\"test1\""};
+        String[] savedBssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] savedFreqs = {2470};
+        String[] savedCaps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] savedSecurities = {SECURITY_PSK};
+        int[] savedLevels = {mThresholdQualifiedRssi2G + 8};
+        // Ephemeral network.
+        String[] ephemeralSsids = {"\"test2\""};
+        String[] ephemeralBssids = {"6c:f3:7f:ae:8c:f4"};
+        int[] ephemeralFreqs = {2437};
+        String[] ephemeralCaps = {"[ESS]"};
+        int[] ephemeralLevels = {mThresholdQualifiedRssi2G + 8};
+        // Ephemeral network has higher score than the saved network.
+        Integer[] scores = {100, 120};
+        boolean[] meteredHints = {false, true};
+
+        // Set up the saved network.
+        WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(savedSsids,
+                        savedBssids, savedFreqs, savedCaps, savedLevels, savedSecurities,
+                        mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        savedConfigs[0].useExternalScores = true;
+
+        // Set up the ephemeral network.
+        scanDetails.addAll(WifiNetworkSelectorTestUtil.buildScanDetails(
+                ephemeralSsids, ephemeralBssids, ephemeralFreqs,
+                ephemeralCaps, ephemeralLevels, mClock));
+        ScanResult ephemeralScanResult = scanDetails.get(1).getScanResult();
+        WifiConfiguration ephemeralNetworkConfig = WifiNetworkSelectorTestUtil
+                .setupEphemeralNetwork(mWifiConfigManager, 1, scanDetails.get(1),
+                        meteredHints[1]);
+
+        // Set up score cache for both the saved network and the ephemeral network.
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(ephemeralNetworkConfig, candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                ephemeralScanResult, candidate);
+    }
+
+    /**
+     * Prefer externally scored saved network over untrusted network when they have
+     * the same score.
+     */
+    @Test
+    public void testEvaluateNetworks_nullScoredNetworks() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
+        int[] securities = {SECURITY_PSK, SECURITY_NONE};
+        int[] levels = {mThresholdQualifiedRssi2G + 8, mThresholdQualifiedRssi2G + 8};
+        Integer[] scores = {null, null};
+        boolean[] meteredHints = {false, true};
+
+        WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        savedConfigs[0].useExternalScores = true;
+
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, scores, meteredHints);
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                null, null, false, true, null);
+
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+    /**
+     * Between two ephemeral networks with the same RSSI, choose
+     * the currently connected one.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseActiveEphemeralNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[ESS]", "[ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 28, mThresholdQualifiedRssi2G + 28};
+        boolean[] meteredHints = {true, true};
+        ScanResult[] scanResults = new ScanResult[2];
+        WifiConfiguration[] ephemeralNetworkConfigs = new WifiConfiguration[2];
+
+        List<ScanDetail> scanDetails = WifiNetworkSelectorTestUtil
+                .buildScanDetails(ssids, bssids, freqs, caps, levels, mClock);
+
+        WifiNetworkSelectorTestUtil.configureScoreCache(
+                mScoreCache, scanDetails, null, meteredHints);
+
+        // No saved networks.
+        when(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(any(ScanDetail.class)))
+                .thenReturn(null);
+
+        for (int i = 0; i < 2; i++) {
+            scanResults[i] = scanDetails.get(i).getScanResult();
+            ephemeralNetworkConfigs[i] = WifiNetworkSelectorTestUtil.setupEphemeralNetwork(
+                    mWifiConfigManager, i, scanDetails.get(i), meteredHints[i]);
+        }
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(
+                scanDetails, ephemeralNetworkConfigs[1],
+                bssids[1], true, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(ephemeralNetworkConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanResults[1], candidate);
+        assertEquals(meteredHints[1], candidate.meteredHint);
+    }
+
+    /**
+     *  Between two externally scored saved networks with the same RSSI, choose
+     *  the currently connected one.
+     */
+    @Test
+    public void testEvaluateNetworks_chooseActiveSavedNetwork() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2470, 2437};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+        int[] levels = {mThresholdQualifiedRssi2G + 28, mThresholdQualifiedRssi2G + 28};
+        boolean[] meteredHints = {false, false};
+
+        WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        savedConfigs[0].useExternalScores = savedConfigs[1].useExternalScores = true;
+
+        WifiNetworkSelectorTestUtil.configureScoreCache(mScoreCache,
+                scanDetails, null, meteredHints);
+
+        WifiConfiguration candidate = mScoredNetworkEvaluator.evaluateNetworks(scanDetails,
+                savedConfigs[1], bssids[1], true, true, null);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[1], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                scanDetails.get(1).getScanResult(), candidate);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java b/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
new file mode 100644
index 0000000..0e55b72
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/SelfRecoveryTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.mockito.Mockito.*;
+import static org.mockito.MockitoAnnotations.*;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.SelfRecovery}.
+ */
+@SmallTest
+public class SelfRecoveryTest {
+    SelfRecovery mSelfRecovery;
+    @Mock WifiController mWifiController;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        mSelfRecovery = new SelfRecovery(mWifiController);
+    }
+
+    /**
+     * Verifies that invocations of {@link SelfRecovery#trigger(int)} with valid reasons will send
+     * the restart message to {@link WifiController}.
+     */
+    @Test
+    public void testValidTriggerReasonsSendMessageToWifiController() {
+        mSelfRecovery.trigger(SelfRecovery.REASON_LAST_RESORT_WATCHDOG);
+        verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+
+        mSelfRecovery.trigger(SelfRecovery.REASON_HAL_CRASH);
+        verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+
+        mSelfRecovery.trigger(SelfRecovery.REASON_WIFICOND_CRASH);
+        verify(mWifiController).sendMessage(eq(WifiController.CMD_RESTART_WIFI));
+        reset(mWifiController);
+
+    }
+
+    /**
+     * Verifies that invocations of {@link SelfRecovery#trigger(int)} with invalid reasons will not
+     * send the restart message to {@link WifiController}.
+     */
+    @Test
+    public void testInvalidTriggerReasonsDoesNotSendMessageToWifiController() {
+        mSelfRecovery.trigger(-1);
+        verify(mWifiController, never()).sendMessage(anyInt());
+
+        mSelfRecovery.trigger(8);
+        verify(mWifiController, never()).sendMessage(anyInt());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index b91df5a..900e6a6 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.wifi;
 
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
@@ -24,16 +25,18 @@
 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 com.android.server.net.BaseNetworkObserver;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -41,6 +44,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Locale;
@@ -51,21 +55,30 @@
 
     private static final String TAG = "SoftApManagerTest";
 
-    private static final String TEST_INTERFACE_NAME = "TestInterface";
+    private static final String DEFAULT_SSID = "DefaultTestSSID";
+    private static final String TEST_SSID = "TestSSID";
     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 static final String TEST_INTERFACE_NAME = "testif0";
 
     private final ArrayList<Integer> mAllowed2GChannels =
-            new ArrayList<Integer>(Arrays.asList(ALLOWED_2G_CHANNELS));
+            new ArrayList<>(Arrays.asList(ALLOWED_2G_CHANNELS));
 
-    MockLooper mLooper;
-    @Mock Context mContext;
+    private final WifiConfiguration mDefaultApConfig = createDefaultApConfig();
+
+    TestLooper mLooper;
     @Mock WifiNative mWifiNative;
-    @Mock INetworkManagementService mNmService;
-    @Mock ConnectivityManager mConnectivityManager;
     @Mock SoftApManager.Listener mListener;
     @Mock InterfaceConfiguration mInterfaceConfiguration;
+    @Mock IBinder mApInterfaceBinder;
+    @Mock IApInterface mApInterface;
+    @Mock INetworkManagementService mNmService;
+    @Mock WifiApConfigStore mWifiApConfigStore;
+    @Mock WifiMetrics mWifiMetrics;
+    final ArgumentCaptor<DeathRecipient> mDeathListenerCaptor =
+            ArgumentCaptor.forClass(DeathRecipient.class);
+    final ArgumentCaptor<BaseNetworkObserver> mNetworkObserverCaptor =
+            ArgumentCaptor.forClass(BaseNetworkObserver.class);
 
     SoftApManager mSoftApManager;
 
@@ -73,40 +86,84 @@
     @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);
-
-        mSoftApManager = new SoftApManager(mLooper.getLooper(),
-                                           mWifiNative,
-                                           mNmService,
-                                           TEST_COUNTRY_CODE,
-                                           mAllowed2GChannels,
-                                           mListener);
-
-        mLooper.dispatchAll();
+        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
+        when(mApInterface.startHostapd()).thenReturn(true);
+        when(mApInterface.stopHostapd()).thenReturn(true);
+        when(mApInterface.writeHostapdConfig(
+                any(), anyBoolean(), anyInt(), anyInt(), any())).thenReturn(true);
+        when(mApInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
     }
 
-    /** Verifies startSoftAp will fail if AP configuration is not provided. */
+    private WifiConfiguration createDefaultApConfig() {
+        WifiConfiguration defaultConfig = new WifiConfiguration();
+        defaultConfig.SSID = DEFAULT_SSID;
+        return defaultConfig;
+    }
+
+    private SoftApManager createSoftApManager(WifiConfiguration config) throws Exception {
+        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
+        when(mApInterface.startHostapd()).thenReturn(true);
+        when(mApInterface.stopHostapd()).thenReturn(true);
+        if (config == null) {
+            when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
+        }
+        SoftApManager newSoftApManager = new SoftApManager(mLooper.getLooper(),
+                                                           mWifiNative,
+                                                           TEST_COUNTRY_CODE,
+                                                           mListener,
+                                                           mApInterface,
+                                                           mNmService,
+                                                           mWifiApConfigStore,
+                                                           config,
+                                                           mWifiMetrics);
+        mLooper.dispatchAll();
+        return newSoftApManager;
+    }
+
+    /** Verifies startSoftAp will use default config if AP configuration is not provided. */
     @Test
     public void startSoftApWithoutConfig() throws Exception {
-        InOrder order = inOrder(mListener);
+        startSoftApAndVerifyEnabled(null);
+    }
 
-        mSoftApManager.start(null);
+    /** Verifies startSoftAp will use provided config and start AP. */
+    @Test
+    public void startSoftApWithConfig() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_2GHZ;
+        config.SSID = TEST_SSID;
+        startSoftApAndVerifyEnabled(config);
+    }
+
+    /** Tests softap startup if default config fails to load. **/
+    @Test
+    public void startSoftApDefaultConfigFailedToLoad() throws Exception {
+        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
+        when(mApInterface.startHostapd()).thenReturn(true);
+        when(mApInterface.stopHostapd()).thenReturn(true);
+        when(mWifiApConfigStore.getApConfiguration()).thenReturn(null);
+        SoftApManager newSoftApManager = new SoftApManager(mLooper.getLooper(),
+                                                           mWifiNative,
+                                                           TEST_COUNTRY_CODE,
+                                                           mListener,
+                                                           mApInterface,
+                                                           mNmService,
+                                                           mWifiApConfigStore,
+                                                           null,
+                                                           mWifiMetrics);
         mLooper.dispatchAll();
-
-        order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
-        order.verify(mListener).onStateChanged(
-                WifiManager.WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL);
+        newSoftApManager.start();
+        mLooper.dispatchAll();
+        verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
+                WifiManager.SAP_START_FAILURE_GENERAL);
     }
 
     /** Tests the handling of stop command when soft AP is not started. */
     @Test
     public void stopWhenNotStarted() throws Exception {
+        mSoftApManager = createSoftApManager(null);
         mSoftApManager.stop();
         mLooper.dispatchAll();
         /* Verify no state changes. */
@@ -116,36 +173,57 @@
     /** Tests the handling of stop command when soft AP is started. */
     @Test
     public void stopWhenStarted() throws Exception {
-        startSoftApAndVerifyEnabled();
+        startSoftApAndVerifyEnabled(null);
 
         InOrder order = inOrder(mListener);
 
         mSoftApManager.stop();
         mLooper.dispatchAll();
 
-        verify(mNmService).stopAccessPoint(TEST_INTERFACE_NAME);
+        verify(mApInterface).stopHostapd();
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
     }
 
-    /** Starts soft AP and verifies that it is enabled successfully. */
-    protected void startSoftApAndVerifyEnabled() throws Exception {
-        InOrder order = inOrder(mListener);
+    @Test
+    public void handlesWificondInterfaceDeath() throws Exception {
+        startSoftApAndVerifyEnabled(null);
 
-        /**
-         *  Only test the default configuration. Testing for different configurations
-         *  are taken care of by ApConfigUtilTest.
-         */
-        WifiConfiguration config = new WifiConfiguration();
-        config.apBand = WifiConfiguration.AP_BAND_2GHZ;
+        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(WifiConfiguration config) throws Exception {
+        String expectedSSID;
+        InOrder order = inOrder(mListener, mApInterfaceBinder, mApInterface, mNmService);
+
         when(mWifiNative.isHalStarted()).thenReturn(false);
         when(mWifiNative.setCountryCodeHal(TEST_COUNTRY_CODE.toUpperCase(Locale.ROOT)))
                 .thenReturn(true);
-        mSoftApManager.start(config);
+
+        mSoftApManager = createSoftApManager(config);
+        if (config == null) {
+            when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
+            expectedSSID = mDefaultApConfig.SSID;
+        } else {
+            expectedSSID = config.SSID;
+        }
+        mSoftApManager.start();
         mLooper.dispatchAll();
-        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(mNmService).registerObserver(mNetworkObserverCaptor.capture());
+        order.verify(mApInterface).writeHostapdConfig(
+                eq(expectedSSID.getBytes(StandardCharsets.UTF_8)), anyBoolean(),
+                anyInt(), anyInt(), any());
+        order.verify(mApInterface).startHostapd();
+        mNetworkObserverCaptor.getValue().interfaceLinkStateChanged(TEST_INTERFACE_NAME, true);
+        mLooper.dispatchAll();
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java b/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
new file mode 100644
index 0000000..2deef52
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
@@ -0,0 +1,1580 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyShort;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.test.MockAnswerUtil;
+import android.content.Context;
+import android.hardware.wifi.supplicant.V1_0.ISupplicant;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback.BssidChangeReason;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetwork;
+import android.hardware.wifi.supplicant.V1_0.IfaceType;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
+import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.net.IpConfiguration;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import com.android.server.wifi.hotspot2.AnqpEvent;
+import com.android.server.wifi.hotspot2.IconEvent;
+import com.android.server.wifi.hotspot2.WnmData;
+import com.android.server.wifi.util.NativeUtil;
+
+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.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Unit tests for SupplicantStaIfaceHal
+ */
+public class SupplicantStaIfaceHalTest {
+    private static final String TAG = "SupplicantStaIfaceHalTest";
+    private static final Map<Integer, String> NETWORK_ID_TO_SSID = new HashMap<Integer, String>() {{
+            put(1, "\"ssid1\"");
+            put(2, "\"ssid2\"");
+            put(3, "\"ssid3\"");
+        }};
+    private static final int SUPPLICANT_NETWORK_ID = 2;
+    private static final String SUPPLICANT_SSID = NETWORK_ID_TO_SSID.get(SUPPLICANT_NETWORK_ID);
+    private static final int ROAM_NETWORK_ID = 4;
+    private static final String BSSID = "fa:45:23:23:12:12";
+    private static final String WLAN_IFACE_NAME = "wlan0";
+    private static final String P2P_IFACE_NAME = "p2p0";
+    private static final String ICON_FILE_NAME  = "blahblah";
+    private static final int ICON_FILE_SIZE = 72;
+    private static final String HS20_URL = "http://blahblah";
+
+    @Mock IServiceManager mServiceManagerMock;
+    @Mock ISupplicant mISupplicantMock;
+    @Mock ISupplicantIface mISupplicantIfaceMock;
+    @Mock ISupplicantStaIface mISupplicantStaIfaceMock;
+    @Mock Context mContext;
+    @Mock WifiMonitor mWifiMonitor;
+    @Mock SupplicantStaNetworkHal mSupplicantStaNetworkMock;
+    SupplicantStatus mStatusSuccess;
+    SupplicantStatus mStatusFailure;
+    ISupplicant.IfaceInfo mStaIface;
+    ISupplicant.IfaceInfo mP2pIface;
+    ArrayList<ISupplicant.IfaceInfo> mIfaceInfoList;
+    ISupplicantStaIfaceCallback mISupplicantStaIfaceCallback;
+    private SupplicantStaIfaceHal mDut;
+    private ArgumentCaptor<IHwBinder.DeathRecipient> mServiceManagerDeathCaptor =
+            ArgumentCaptor.forClass(IHwBinder.DeathRecipient.class);
+    private ArgumentCaptor<IHwBinder.DeathRecipient> mSupplicantDeathCaptor =
+            ArgumentCaptor.forClass(IHwBinder.DeathRecipient.class);
+    private ArgumentCaptor<IHwBinder.DeathRecipient> mSupplicantStaIfaceDeathCaptor =
+            ArgumentCaptor.forClass(IHwBinder.DeathRecipient.class);
+    private ArgumentCaptor<IServiceNotification.Stub> mServiceNotificationCaptor =
+            ArgumentCaptor.forClass(IServiceNotification.Stub.class);
+    private InOrder mInOrder;
+
+    private class SupplicantStaIfaceHalSpy extends SupplicantStaIfaceHal {
+        SupplicantStaIfaceHalSpy(Context context, WifiMonitor monitor) {
+            super(context, monitor);
+        }
+
+        @Override
+        protected IServiceManager getServiceManagerMockable() throws RemoteException {
+            return mServiceManagerMock;
+        }
+
+        @Override
+        protected ISupplicant getSupplicantMockable() throws RemoteException {
+            return mISupplicantMock;
+        }
+
+        @Override
+        protected ISupplicantStaIface getStaIfaceMockable(ISupplicantIface iface) {
+            return mISupplicantStaIfaceMock;
+        }
+
+        @Override
+        protected SupplicantStaNetworkHal getStaNetworkMockable(
+                ISupplicantStaNetwork iSupplicantStaNetwork) {
+            return mSupplicantStaNetworkMock;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mStatusSuccess = createSupplicantStatus(SupplicantStatusCode.SUCCESS);
+        mStatusFailure = createSupplicantStatus(SupplicantStatusCode.FAILURE_UNKNOWN);
+        mStaIface = createIfaceInfo(IfaceType.STA, WLAN_IFACE_NAME);
+        mP2pIface = createIfaceInfo(IfaceType.P2P, P2P_IFACE_NAME);
+
+        mIfaceInfoList = new ArrayList<>();
+        mIfaceInfoList.add(mStaIface);
+        mIfaceInfoList.add(mP2pIface);
+
+        when(mServiceManagerMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong())).thenReturn(true);
+        when(mServiceManagerMock.registerForNotifications(anyString(), anyString(),
+                any(IServiceNotification.Stub.class))).thenReturn(true);
+        when(mISupplicantMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong())).thenReturn(true);
+        when(mISupplicantStaIfaceMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong())).thenReturn(true);
+        mDut = new SupplicantStaIfaceHalSpy(mContext, mWifiMonitor);
+    }
+
+    /**
+     * Sunny day scenario for SupplicantStaIfaceHal initialization
+     * Asserts successful initialization
+     */
+    @Test
+    public void testInitialize_success() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false, false);
+    }
+
+    /**
+     * Tests the initialization flow, with a RemoteException occurring when 'getInterface' is called
+     * Ensures initialization fails.
+     */
+    @Test
+    public void testInitialize_remoteExceptionFailure() throws Exception {
+        executeAndValidateInitializationSequence(true, false, false, false);
+    }
+
+    /**
+     * Tests the initialization flow, with listInterfaces returning 0 interfaces.
+     * Ensures failure
+     */
+    @Test
+    public void testInitialize_zeroInterfacesFailure() throws Exception {
+        executeAndValidateInitializationSequence(false, true, false, false);
+    }
+
+    /**
+     * Tests the initialization flow, with a null interface being returned by getInterface.
+     * Ensures initialization fails.
+     */
+    @Test
+    public void testInitialize_nullInterfaceFailure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, true, false);
+    }
+
+    /**
+     * Tests the initialization flow, with a callback registration failure.
+     * Ensures initialization fails.
+     */
+    @Test
+    public void testInitialize_callbackRegistrationFailure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false, true);
+    }
+
+    /**
+     * Tests the loading of networks using {@link SupplicantStaNetworkHal}.
+     * Fills up only the SSID field of configs and uses it as a configKey as well.
+     */
+    @Test
+    public void testLoadNetworks() throws Exception {
+        executeAndValidateInitializationSequence();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) {
+                cb.onValues(mStatusSuccess, new ArrayList<>(NETWORK_ID_TO_SSID.keySet()));
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantStaIface.getNetworkCallback cb) {
+                // Reset the |mSupplicantStaNetwork| mock for each network.
+                doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+                    public boolean answer(
+                            WifiConfiguration config, Map<String, String> networkExtra) {
+                        config.SSID = NETWORK_ID_TO_SSID.get(networkId);
+                        config.networkId = networkId;
+                        networkExtra.put(
+                                SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY, config.SSID);
+                        return true;
+                    }
+                }).when(mSupplicantStaNetworkMock)
+                        .loadWifiConfiguration(any(WifiConfiguration.class), any(Map.class));
+                cb.onValues(mStatusSuccess, mock(ISupplicantStaNetwork.class));
+                return;
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantStaIface.getNetworkCallback.class));
+
+        Map<String, WifiConfiguration> configs = new HashMap<>();
+        SparseArray<Map<String, String>> extras = new SparseArray<>();
+        assertTrue(mDut.loadNetworks(configs, extras));
+
+        assertEquals(3, configs.size());
+        assertEquals(3, extras.size());
+        for (Map.Entry<Integer, String> network : NETWORK_ID_TO_SSID.entrySet()) {
+            WifiConfiguration config = configs.get(network.getValue());
+            assertTrue(config != null);
+            assertEquals(network.getKey(), Integer.valueOf(config.networkId));
+            assertEquals(network.getValue(), config.SSID);
+            assertEquals(IpConfiguration.IpAssignment.DHCP, config.getIpAssignment());
+            assertEquals(IpConfiguration.ProxySettings.NONE, config.getProxySettings());
+        }
+    }
+
+    /**
+     * Tests the loading of networks using {@link SupplicantStaNetworkHal} removes any networks
+     * with duplicate config key.
+     * Fills up only the SSID field of configs and uses it as a configKey as well.
+     */
+    @Test
+    public void testLoadNetworksRemovesDuplicates() throws Exception {
+        // Network ID which will have the same config key as the previous one.
+        final int duplicateNetworkId = 2;
+        final int toRemoveNetworkId = duplicateNetworkId - 1;
+        executeAndValidateInitializationSequence();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) {
+                cb.onValues(mStatusSuccess, new ArrayList<>(NETWORK_ID_TO_SSID.keySet()));
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public SupplicantStatus answer(int id) {
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaIfaceMock).removeNetwork(eq(toRemoveNetworkId));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantStaIface.getNetworkCallback cb) {
+                // Reset the |mSupplicantStaNetwork| mock for each network.
+                doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+                    public boolean answer(
+                            WifiConfiguration config, Map<String, String> networkExtra) {
+                        config.SSID = NETWORK_ID_TO_SSID.get(networkId);
+                        config.networkId = networkId;
+                        // Duplicate network gets the same config key as the to removed one.
+                        if (networkId == duplicateNetworkId) {
+                            networkExtra.put(
+                                    SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY,
+                                    NETWORK_ID_TO_SSID.get(toRemoveNetworkId));
+                        } else {
+                            networkExtra.put(
+                                    SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY,
+                                    NETWORK_ID_TO_SSID.get(networkId));
+                        }
+                        return true;
+                    }
+                }).when(mSupplicantStaNetworkMock)
+                        .loadWifiConfiguration(any(WifiConfiguration.class), any(Map.class));
+                cb.onValues(mStatusSuccess, mock(ISupplicantStaNetwork.class));
+                return;
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantStaIface.getNetworkCallback.class));
+
+        Map<String, WifiConfiguration> configs = new HashMap<>();
+        SparseArray<Map<String, String>> extras = new SparseArray<>();
+        assertTrue(mDut.loadNetworks(configs, extras));
+
+        assertEquals(2, configs.size());
+        assertEquals(2, extras.size());
+        for (Map.Entry<Integer, String> network : NETWORK_ID_TO_SSID.entrySet()) {
+            if (network.getKey() == toRemoveNetworkId) {
+                continue;
+            }
+            WifiConfiguration config;
+            // Duplicate network gets the same config key as the to removed one. So, use that to
+            // lookup the map.
+            if (network.getKey() == duplicateNetworkId) {
+                config = configs.get(NETWORK_ID_TO_SSID.get(toRemoveNetworkId));
+            } else {
+                config = configs.get(network.getValue());
+            }
+            assertTrue(config != null);
+            assertEquals(network.getKey(), Integer.valueOf(config.networkId));
+            assertEquals(network.getValue(), config.SSID);
+            assertEquals(IpConfiguration.IpAssignment.DHCP, config.getIpAssignment());
+            assertEquals(IpConfiguration.ProxySettings.NONE, config.getProxySettings());
+        }
+    }
+
+    /**
+     * Tests the failure to load networks because of listNetworks failure.
+     */
+    @Test
+    public void testLoadNetworksFailedDueToListNetworks() throws Exception {
+        executeAndValidateInitializationSequence();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) {
+                cb.onValues(mStatusFailure, null);
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+
+        Map<String, WifiConfiguration> configs = new HashMap<>();
+        SparseArray<Map<String, String>> extras = new SparseArray<>();
+        assertFalse(mDut.loadNetworks(configs, extras));
+    }
+
+    /**
+     * Tests the failure to load networks because of getNetwork failure.
+     */
+    @Test
+    public void testLoadNetworksFailedDueToGetNetwork() throws Exception {
+        executeAndValidateInitializationSequence();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) {
+                cb.onValues(mStatusSuccess, new ArrayList<>(NETWORK_ID_TO_SSID.keySet()));
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantStaIface.getNetworkCallback cb) {
+                cb.onValues(mStatusFailure, mock(ISupplicantStaNetwork.class));
+                return;
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantStaIface.getNetworkCallback.class));
+
+        Map<String, WifiConfiguration> configs = new HashMap<>();
+        SparseArray<Map<String, String>> extras = new SparseArray<>();
+        assertFalse(mDut.loadNetworks(configs, extras));
+    }
+
+    /**
+     * Tests the failure to load networks because of loadWifiConfiguration failure.
+     */
+    @Test
+    public void testLoadNetworksFailedDueToLoadWifiConfiguration() throws Exception {
+        executeAndValidateInitializationSequence();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) {
+                cb.onValues(mStatusSuccess, new ArrayList<>(NETWORK_ID_TO_SSID.keySet()));
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantStaIface.getNetworkCallback cb) {
+                cb.onValues(mStatusSuccess, mock(ISupplicantStaNetwork.class));
+                return;
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantStaIface.getNetworkCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public boolean answer(WifiConfiguration config, Map<String, String> networkExtra) {
+                return false;
+            }
+        }).when(mSupplicantStaNetworkMock)
+                .loadWifiConfiguration(any(WifiConfiguration.class), any(Map.class));
+
+        Map<String, WifiConfiguration> configs = new HashMap<>();
+        SparseArray<Map<String, String>> extras = new SparseArray<>();
+        assertTrue(mDut.loadNetworks(configs, extras));
+        assertTrue(configs.isEmpty());
+    }
+
+    /**
+     * Tests the failure to load networks because of loadWifiConfiguration exception.
+     */
+    @Test
+    public void testLoadNetworksFailedDueToExceptionInLoadWifiConfiguration() throws Exception {
+        executeAndValidateInitializationSequence();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) {
+                cb.onValues(mStatusSuccess, new ArrayList<>(NETWORK_ID_TO_SSID.keySet()));
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantStaIface.getNetworkCallback cb) {
+                cb.onValues(mStatusSuccess, mock(ISupplicantStaNetwork.class));
+                return;
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantStaIface.getNetworkCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public boolean answer(WifiConfiguration config, Map<String, String> networkExtra)
+                    throws Exception {
+                throw new IllegalArgumentException();
+            }
+        }).when(mSupplicantStaNetworkMock)
+                .loadWifiConfiguration(any(WifiConfiguration.class), any(Map.class));
+
+        Map<String, WifiConfiguration> configs = new HashMap<>();
+        SparseArray<Map<String, String>> extras = new SparseArray<>();
+        assertTrue(mDut.loadNetworks(configs, extras));
+        assertTrue(configs.isEmpty());
+    }
+
+    /**
+     * Tests connection to a specified network with empty existing network.
+     */
+    @Test
+    public void testConnectWithEmptyExistingNetwork() throws Exception {
+        executeAndValidateInitializationSequence();
+        executeAndValidateConnectSequence(0, false);
+    }
+
+    @Test
+    public void testConnectToNetworkWithDifferentConfigReplacesNetworkInSupplicant()
+            throws Exception {
+        executeAndValidateInitializationSequence();
+        WifiConfiguration config = executeAndValidateConnectSequence(
+                SUPPLICANT_NETWORK_ID, false);
+        // Reset mocks for mISupplicantStaIfaceMock because we finished the first connection.
+        reset(mISupplicantStaIfaceMock);
+        setupMocksForConnectSequence(true /*haveExistingNetwork*/);
+        // Make this network different by changing SSID.
+        config.SSID = "AnDifferentSSID";
+        assertTrue(mDut.connectToNetwork(config));
+        verify(mISupplicantStaIfaceMock).removeNetwork(SUPPLICANT_NETWORK_ID);
+        verify(mISupplicantStaIfaceMock)
+                .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
+    }
+
+    @Test
+    public void connectToNetworkWithSameNetworkDoesNotRemoveNetworkFromSupplicant()
+            throws Exception {
+        executeAndValidateInitializationSequence();
+        WifiConfiguration config = executeAndValidateConnectSequence(SUPPLICANT_NETWORK_ID, false);
+        // Reset mocks for mISupplicantStaIfaceMock because we finished the first connection.
+        reset(mISupplicantStaIfaceMock);
+        setupMocksForConnectSequence(true /*haveExistingNetwork*/);
+        assertTrue(mDut.connectToNetwork(config));
+        verify(mISupplicantStaIfaceMock, never()).removeNetwork(anyInt());
+        verify(mISupplicantStaIfaceMock, never())
+                .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
+    }
+
+    /**
+     * Tests connection to a specified network failure due to network add.
+     */
+    @Test
+    public void testConnectFailureDueToNetworkAddFailure() throws Exception {
+        executeAndValidateInitializationSequence();
+        setupMocksForConnectSequence(false);
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.addNetworkCallback cb) throws RemoteException {
+                cb.onValues(mStatusFailure, mock(ISupplicantStaNetwork.class));
+                return;
+            }
+        }).when(mISupplicantStaIfaceMock).addNetwork(
+                any(ISupplicantStaIface.addNetworkCallback.class));
+
+        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+    }
+
+    /**
+     * Tests connection to a specified network failure due to network save.
+     */
+    @Test
+    public void testConnectFailureDueToNetworkSaveFailure() throws Exception {
+        executeAndValidateInitializationSequence();
+        setupMocksForConnectSequence(true);
+
+        when(mSupplicantStaNetworkMock.saveWifiConfiguration(any(WifiConfiguration.class)))
+                .thenReturn(false);
+
+        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+        // We should have removed the existing network once before connection and once more
+        // on failure to save network configuration.
+        verify(mISupplicantStaIfaceMock, times(2)).removeNetwork(anyInt());
+    }
+
+    /**
+     * Tests connection to a specified network failure due to exception in network save.
+     */
+    @Test
+    public void testConnectFailureDueToNetworkSaveException() throws Exception {
+        executeAndValidateInitializationSequence();
+        setupMocksForConnectSequence(true);
+
+        doThrow(new IllegalArgumentException("Some error!!!"))
+                .when(mSupplicantStaNetworkMock).saveWifiConfiguration(
+                        any(WifiConfiguration.class));
+
+        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+        // We should have removed the existing network once before connection and once more
+        // on failure to save network configuration.
+        verify(mISupplicantStaIfaceMock, times(2)).removeNetwork(anyInt());
+    }
+
+    /**
+     * Tests connection to a specified network failure due to network select.
+     */
+    @Test
+    public void testConnectFailureDueToNetworkSelectFailure() throws Exception {
+        executeAndValidateInitializationSequence();
+        setupMocksForConnectSequence(false);
+
+        when(mSupplicantStaNetworkMock.select()).thenReturn(false);
+
+        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+    }
+
+    /**
+     * Tests roaming to the same network as the currently connected one.
+     */
+    @Test
+    public void testRoamToSameNetwork() throws Exception {
+        executeAndValidateInitializationSequence();
+        executeAndValidateRoamSequence(true);
+        assertTrue(mDut.connectToNetwork(createTestWifiConfiguration()));
+    }
+
+    /**
+     * Tests roaming to a different network.
+     */
+    @Test
+    public void testRoamToDifferentNetwork() throws Exception {
+        executeAndValidateInitializationSequence();
+        executeAndValidateRoamSequence(false);
+    }
+
+    /**
+     * Tests roaming failure because of unable to set bssid.
+     */
+    @Test
+    public void testRoamFailureDueToBssidSet() throws Exception {
+        executeAndValidateInitializationSequence();
+        int connectedNetworkId = 5;
+        executeAndValidateConnectSequence(connectedNetworkId, false);
+        when(mSupplicantStaNetworkMock.setBssid(anyString())).thenReturn(false);
+
+        WifiConfiguration roamingConfig = new WifiConfiguration();
+        roamingConfig.networkId = connectedNetworkId;
+        roamingConfig.getNetworkSelectionStatus().setNetworkSelectionBSSID("45:34:23:23:ab:ed");
+        assertFalse(mDut.roamToNetwork(roamingConfig));
+    }
+
+    /**
+     * Tests removal of all configured networks from wpa_supplicant.
+     */
+    @Test
+    public void testRemoveAllNetworks() throws Exception {
+        executeAndValidateInitializationSequence();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) {
+                cb.onValues(mStatusSuccess, new ArrayList<>(NETWORK_ID_TO_SSID.keySet()));
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public SupplicantStatus answer(int id) {
+                assertTrue(NETWORK_ID_TO_SSID.containsKey(id));
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaIfaceMock).removeNetwork(anyInt());
+
+        assertTrue(mDut.removeAllNetworks());
+        verify(mISupplicantStaIfaceMock, times(NETWORK_ID_TO_SSID.size())).removeNetwork(anyInt());
+    }
+
+    /**
+     * Remove all networks while connected, verify that the current network info is resetted.
+     */
+    @Test
+    public void testRemoveAllNetworksWhileConnected() throws Exception {
+        String testBssid = "11:22:33:44:55:66";
+        when(mSupplicantStaNetworkMock.setBssid(eq(testBssid))).thenReturn(true);
+
+        executeAndValidateInitializationSequence();
+
+        // Connect to a network and verify current network is set.
+        executeAndValidateConnectSequence(4, false);
+        assertTrue(mDut.setCurrentNetworkBssid(testBssid));
+        verify(mSupplicantStaNetworkMock).setBssid(eq(testBssid));
+        reset(mSupplicantStaNetworkMock);
+
+        // Remove all networks and verify current network info is resetted.
+        assertTrue(mDut.removeAllNetworks());
+        assertFalse(mDut.setCurrentNetworkBssid(testBssid));
+        verify(mSupplicantStaNetworkMock, never()).setBssid(eq(testBssid));
+    }
+
+    /**
+     * Tests roaming failure because of unable to reassociate.
+     */
+    @Test
+    public void testRoamFailureDueToReassociate() throws Exception {
+        executeAndValidateInitializationSequence();
+        int connectedNetworkId = 5;
+        executeAndValidateConnectSequence(connectedNetworkId, false);
+
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public SupplicantStatus answer() throws RemoteException {
+                return mStatusFailure;
+            }
+        }).when(mISupplicantStaIfaceMock).reassociate();
+        when(mSupplicantStaNetworkMock.setBssid(anyString())).thenReturn(true);
+
+        WifiConfiguration roamingConfig = new WifiConfiguration();
+        roamingConfig.networkId = connectedNetworkId;
+        roamingConfig.getNetworkSelectionStatus().setNetworkSelectionBSSID("45:34:23:23:ab:ed");
+        assertFalse(mDut.roamToNetwork(roamingConfig));
+    }
+
+    /**
+     * Tests the retrieval of WPS NFC token.
+     */
+    @Test
+    public void testGetCurrentNetworkWpsNfcConfigurationToken() throws Exception {
+        String token = "45adbc1";
+        when(mSupplicantStaNetworkMock.getWpsNfcConfigurationToken()).thenReturn(token);
+
+        executeAndValidateInitializationSequence();
+        // Return null when not connected to the network.
+        assertTrue(mDut.getCurrentNetworkWpsNfcConfigurationToken() == null);
+        verify(mSupplicantStaNetworkMock, never()).getWpsNfcConfigurationToken();
+        executeAndValidateConnectSequence(4, false);
+        assertEquals(token, mDut.getCurrentNetworkWpsNfcConfigurationToken());
+        verify(mSupplicantStaNetworkMock).getWpsNfcConfigurationToken();
+    }
+
+    /**
+     * Tests the setting of BSSID.
+     */
+    @Test
+    public void testSetCurrentNetworkBssid() throws Exception {
+        String bssidStr = "34:34:12:12:12:90";
+        when(mSupplicantStaNetworkMock.setBssid(eq(bssidStr))).thenReturn(true);
+
+        executeAndValidateInitializationSequence();
+        // Fail when not connected to a network.
+        assertFalse(mDut.setCurrentNetworkBssid(bssidStr));
+        verify(mSupplicantStaNetworkMock, never()).setBssid(eq(bssidStr));
+        executeAndValidateConnectSequence(4, false);
+        assertTrue(mDut.setCurrentNetworkBssid(bssidStr));
+        verify(mSupplicantStaNetworkMock).setBssid(eq(bssidStr));
+    }
+
+    /**
+     * Tests the sending identity response for the current network.
+     */
+    @Test
+    public void testSetCurrentNetworkEapIdentityResponse() throws Exception {
+        String identity = "blah@blah.com";
+        when(mSupplicantStaNetworkMock.sendNetworkEapIdentityResponse(eq(identity)))
+                .thenReturn(true);
+
+        executeAndValidateInitializationSequence();
+        // Fail when not connected to a network.
+        assertFalse(mDut.sendCurrentNetworkEapIdentityResponse(identity));
+        verify(mSupplicantStaNetworkMock, never()).sendNetworkEapIdentityResponse(eq(identity));
+        executeAndValidateConnectSequence(4, false);
+        assertTrue(mDut.sendCurrentNetworkEapIdentityResponse(identity));
+        verify(mSupplicantStaNetworkMock).sendNetworkEapIdentityResponse(eq(identity));
+    }
+
+    /**
+     * Tests the getting of anonymous identity for the current network.
+     */
+    @Test
+    public void testGetCurrentNetworkEapAnonymousIdentity() throws Exception {
+        String anonymousIdentity = "aaa@bbb.ccc";
+        when(mSupplicantStaNetworkMock.fetchEapAnonymousIdentity())
+                .thenReturn(anonymousIdentity);
+        executeAndValidateInitializationSequence();
+
+        // Return null when not connected to the network.
+        assertEquals(null, mDut.getCurrentNetworkEapAnonymousIdentity());
+        executeAndValidateConnectSequence(4, false);
+        // Return anonymous identity for the current network.
+        assertEquals(anonymousIdentity, mDut.getCurrentNetworkEapAnonymousIdentity());
+    }
+
+    /**
+     * Tests the sending gsm auth response for the current network.
+     */
+    @Test
+    public void testSetCurrentNetworkEapSimGsmAuthResponse() throws Exception {
+        String params = "test";
+        when(mSupplicantStaNetworkMock.sendNetworkEapSimGsmAuthResponse(eq(params)))
+                .thenReturn(true);
+
+        executeAndValidateInitializationSequence();
+        // Fail when not connected to a network.
+        assertFalse(mDut.sendCurrentNetworkEapSimGsmAuthResponse(params));
+        verify(mSupplicantStaNetworkMock, never()).sendNetworkEapSimGsmAuthResponse(eq(params));
+        executeAndValidateConnectSequence(4, false);
+        assertTrue(mDut.sendCurrentNetworkEapSimGsmAuthResponse(params));
+        verify(mSupplicantStaNetworkMock).sendNetworkEapSimGsmAuthResponse(eq(params));
+    }
+
+    /**
+     * Tests the sending umts auth response for the current network.
+     */
+    @Test
+    public void testSetCurrentNetworkEapSimUmtsAuthResponse() throws Exception {
+        String params = "test";
+        when(mSupplicantStaNetworkMock.sendNetworkEapSimUmtsAuthResponse(eq(params)))
+                .thenReturn(true);
+
+        executeAndValidateInitializationSequence();
+        // Fail when not connected to a network.
+        assertFalse(mDut.sendCurrentNetworkEapSimUmtsAuthResponse(params));
+        verify(mSupplicantStaNetworkMock, never()).sendNetworkEapSimUmtsAuthResponse(eq(params));
+        executeAndValidateConnectSequence(4, false);
+        assertTrue(mDut.sendCurrentNetworkEapSimUmtsAuthResponse(params));
+        verify(mSupplicantStaNetworkMock).sendNetworkEapSimUmtsAuthResponse(eq(params));
+    }
+
+    /**
+     * Tests the sending umts auts response for the current network.
+     */
+    @Test
+    public void testSetCurrentNetworkEapSimUmtsAutsResponse() throws Exception {
+        String params = "test";
+        when(mSupplicantStaNetworkMock.sendNetworkEapSimUmtsAutsResponse(eq(params)))
+                .thenReturn(true);
+
+        executeAndValidateInitializationSequence();
+        // Fail when not connected to a network.
+        assertFalse(mDut.sendCurrentNetworkEapSimUmtsAutsResponse(params));
+        verify(mSupplicantStaNetworkMock, never()).sendNetworkEapSimUmtsAutsResponse(eq(params));
+        executeAndValidateConnectSequence(4, false);
+        assertTrue(mDut.sendCurrentNetworkEapSimUmtsAutsResponse(params));
+        verify(mSupplicantStaNetworkMock).sendNetworkEapSimUmtsAutsResponse(eq(params));
+    }
+
+    /**
+     * Tests the setting of WPS device type.
+     */
+    @Test
+    public void testSetWpsDeviceType() throws Exception {
+        String validDeviceTypeStr = "10-0050F204-5";
+        byte[] expectedDeviceType = { 0x0, 0xa, 0x0, 0x50, (byte) 0xf2, 0x04, 0x0, 0x05};
+        String invalidDeviceType1Str = "10-02050F204-5";
+        String invalidDeviceType2Str = "10-0050F204-534";
+        when(mISupplicantStaIfaceMock.setWpsDeviceType(any(byte[].class)))
+                .thenReturn(mStatusSuccess);
+
+        executeAndValidateInitializationSequence();
+
+        // This should work.
+        assertTrue(mDut.setWpsDeviceType(validDeviceTypeStr));
+        verify(mISupplicantStaIfaceMock).setWpsDeviceType(eq(expectedDeviceType));
+
+        // This should not work
+        assertFalse(mDut.setWpsDeviceType(invalidDeviceType1Str));
+        // This should not work
+        assertFalse(mDut.setWpsDeviceType(invalidDeviceType2Str));
+    }
+
+    /**
+     * Tests the setting of WPS config methods.
+     */
+    @Test
+    public void testSetWpsConfigMethods() throws Exception {
+        String validConfigMethodsStr = "physical_display virtual_push_button";
+        Short expectedConfigMethods =
+                WpsConfigMethods.PHY_DISPLAY | WpsConfigMethods.VIRT_PUSHBUTTON;
+        String invalidConfigMethodsStr = "physical_display virtual_push_button test";
+        when(mISupplicantStaIfaceMock.setWpsConfigMethods(anyShort())).thenReturn(mStatusSuccess);
+
+        executeAndValidateInitializationSequence();
+
+        // This should work.
+        assertTrue(mDut.setWpsConfigMethods(validConfigMethodsStr));
+        verify(mISupplicantStaIfaceMock).setWpsConfigMethods(eq(expectedConfigMethods));
+
+        // This should throw an illegal argument exception.
+        try {
+            assertFalse(mDut.setWpsConfigMethods(invalidConfigMethodsStr));
+        } catch (IllegalArgumentException e) {
+            return;
+        }
+        assertTrue(false);
+    }
+
+    /**
+     * Tests the handling of ANQP done callback.
+     * Note: Since the ANQP element parsing methods are static, this can only test the negative test
+     * where all the parsing fails because the data is empty. It'll be non-trivial and unnecessary
+     * to test out the parsing logic here.
+     */
+    @Test
+    public void testAnqpDoneCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+        byte[] bssid = NativeUtil.macAddressToByteArray(BSSID);
+        mISupplicantStaIfaceCallback.onAnqpQueryDone(
+                bssid, new ISupplicantStaIfaceCallback.AnqpData(),
+                new ISupplicantStaIfaceCallback.Hs20AnqpData());
+
+        ArgumentCaptor<AnqpEvent> anqpEventCaptor = ArgumentCaptor.forClass(AnqpEvent.class);
+        verify(mWifiMonitor).broadcastAnqpDoneEvent(eq(WLAN_IFACE_NAME), anqpEventCaptor.capture());
+        assertEquals(
+                ByteBufferReader.readInteger(
+                        ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
+                anqpEventCaptor.getValue().getBssid());
+    }
+
+    /**
+     * Tests the handling of Icon done callback.
+     */
+    @Test
+    public void testIconDoneCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        byte[] bssid = NativeUtil.macAddressToByteArray(BSSID);
+        byte[] iconData = new byte[ICON_FILE_SIZE];
+        new Random().nextBytes(iconData);
+        mISupplicantStaIfaceCallback.onHs20IconQueryDone(
+                bssid, ICON_FILE_NAME, NativeUtil.byteArrayToArrayList(iconData));
+
+        ArgumentCaptor<IconEvent> iconEventCaptor = ArgumentCaptor.forClass(IconEvent.class);
+        verify(mWifiMonitor).broadcastIconDoneEvent(eq(WLAN_IFACE_NAME), iconEventCaptor.capture());
+        assertEquals(
+                ByteBufferReader.readInteger(
+                        ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
+                iconEventCaptor.getValue().getBSSID());
+        assertEquals(ICON_FILE_NAME, iconEventCaptor.getValue().getFileName());
+        assertArrayEquals(iconData, iconEventCaptor.getValue().getData());
+    }
+
+    /**
+     * Tests the handling of HS20 subscription remediation callback.
+     */
+    @Test
+    public void testHs20SubscriptionRemediationCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        byte[] bssid = NativeUtil.macAddressToByteArray(BSSID);
+        byte osuMethod = ISupplicantStaIfaceCallback.OsuMethod.OMA_DM;
+        mISupplicantStaIfaceCallback.onHs20SubscriptionRemediation(
+                bssid, osuMethod, HS20_URL);
+
+        ArgumentCaptor<WnmData> wnmDataCaptor = ArgumentCaptor.forClass(WnmData.class);
+        verify(mWifiMonitor).broadcastWnmEvent(eq(WLAN_IFACE_NAME), wnmDataCaptor.capture());
+        assertEquals(
+                ByteBufferReader.readInteger(
+                        ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
+                wnmDataCaptor.getValue().getBssid());
+        assertEquals(osuMethod, wnmDataCaptor.getValue().getMethod());
+        assertEquals(HS20_URL, wnmDataCaptor.getValue().getUrl());
+    }
+
+    /**
+     * Tests the handling of HS20 deauth imminent callback.
+     */
+    @Test
+    public void testHs20DeauthImminentCallbackWithEssReasonCode() throws Exception {
+        executeAndValidateHs20DeauthImminentCallback(true);
+    }
+
+    /**
+     * Tests the handling of HS20 deauth imminent callback.
+     */
+    @Test
+    public void testHs20DeauthImminentCallbackWithNonEssReasonCode() throws Exception {
+        executeAndValidateHs20DeauthImminentCallback(false);
+
+    }
+
+    /**
+     * Tests the handling of state change notification without any configured network.
+     */
+    @Test
+    public void testStateChangeCallbackWithNoConfiguredNetwork() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.INACTIVE,
+                NativeUtil.macAddressToByteArray(BSSID), SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
+
+        // Can't compare WifiSsid instances because they lack an equals.
+        verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
+                eq(WLAN_IFACE_NAME), eq(WifiConfiguration.INVALID_NETWORK_ID),
+                any(WifiSsid.class), eq(BSSID), eq(SupplicantState.INACTIVE));
+    }
+
+    /**
+     * Tests the handling of state change notification to associated after configuring a network.
+     */
+    @Test
+    public void testStateChangeToAssociatedCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        int frameworkNetworkId = 6;
+        executeAndValidateConnectSequence(frameworkNetworkId, false);
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.ASSOCIATED,
+                NativeUtil.macAddressToByteArray(BSSID), SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
+
+        verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
+                eq(WLAN_IFACE_NAME), eq(frameworkNetworkId),
+                any(WifiSsid.class), eq(BSSID), eq(SupplicantState.ASSOCIATED));
+    }
+
+    /**
+     * Tests the handling of state change notification to completed after configuring a network.
+     */
+    @Test
+    public void testStateChangeToCompletedCallback() throws Exception {
+        InOrder wifiMonitorInOrder = inOrder(mWifiMonitor);
+        executeAndValidateInitializationSequence();
+        int frameworkNetworkId = 6;
+        executeAndValidateConnectSequence(frameworkNetworkId, false);
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.COMPLETED,
+                NativeUtil.macAddressToByteArray(BSSID), SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
+
+        wifiMonitorInOrder.verify(mWifiMonitor).broadcastNetworkConnectionEvent(
+                eq(WLAN_IFACE_NAME), eq(frameworkNetworkId), eq(BSSID));
+        wifiMonitorInOrder.verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
+                eq(WLAN_IFACE_NAME), eq(frameworkNetworkId),
+                any(WifiSsid.class), eq(BSSID), eq(SupplicantState.COMPLETED));
+    }
+
+    /**
+     * Tests the handling of network disconnected notification.
+     */
+    @Test
+    public void testDisconnectedCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        int reasonCode = 5;
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), true, reasonCode);
+        verify(mWifiMonitor).broadcastNetworkDisconnectionEvent(
+                eq(WLAN_IFACE_NAME), eq(1), eq(reasonCode), eq(BSSID));
+
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
+        verify(mWifiMonitor).broadcastNetworkDisconnectionEvent(
+                eq(WLAN_IFACE_NAME), eq(0), eq(reasonCode), eq(BSSID));
+    }
+
+    /**
+     * Tests the handling of incorrect network passwords.
+     */
+    @Test
+    public void testAuthFailurePassword() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        int reasonCode = 3;
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), true, reasonCode);
+        verify(mWifiMonitor, times(0)).broadcastAuthenticationFailureEvent(any(), anyInt());
+
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
+        verify(mWifiMonitor, times(0)).broadcastAuthenticationFailureEvent(any(), anyInt());
+
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), true, reasonCode);
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
+
+        verify(mWifiMonitor, times(2)).broadcastAuthenticationFailureEvent(eq(WLAN_IFACE_NAME),
+                eq(WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD));
+
+    }
+
+     /**
+      * Tests the handling of incorrect network passwords, edge case.
+      *
+      * If the disconnect reason is "IE in 4way differs", do not call it a password mismatch.
+      */
+    @Test
+    public void testIeDiffers() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        int reasonCode = 17; // IEEE 802.11i WLAN_REASON_IE_IN_4WAY_DIFFERS
+
+        mISupplicantStaIfaceCallback.onStateChanged(
+                ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE,
+                NativeUtil.macAddressToByteArray(BSSID),
+                SUPPLICANT_NETWORK_ID,
+                NativeUtil.decodeSsid(SUPPLICANT_SSID));
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), true, reasonCode);
+        verify(mWifiMonitor, times(0)).broadcastAuthenticationFailureEvent(any(), anyInt());
+    }
+
+
+    /**
+     * Tests the handling of association rejection notification.
+     */
+    @Test
+    public void testAssociationRejectionCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        int statusCode = 7;
+        mISupplicantStaIfaceCallback.onAssociationRejected(
+                NativeUtil.macAddressToByteArray(BSSID), statusCode, false);
+        verify(mWifiMonitor).broadcastAssociationRejectionEvent(
+                eq(WLAN_IFACE_NAME), eq(statusCode), eq(false), eq(BSSID));
+    }
+
+    /**
+     * Tests the handling of authentification timeout notification.
+     */
+    @Test
+    public void testAuthenticationTimeoutCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onAuthenticationTimeout(
+                NativeUtil.macAddressToByteArray(BSSID));
+        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN_IFACE_NAME),
+                eq(WifiManager.ERROR_AUTH_FAILURE_TIMEOUT));
+    }
+
+    /**
+     * Tests the handling of bssid change notification.
+     */
+    @Test
+    public void testBssidChangedCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onBssidChanged(
+                BssidChangeReason.ASSOC_START, NativeUtil.macAddressToByteArray(BSSID));
+        verify(mWifiMonitor).broadcastTargetBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor, never()).broadcastAssociatedBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+
+        reset(mWifiMonitor);
+        mISupplicantStaIfaceCallback.onBssidChanged(
+                BssidChangeReason.ASSOC_COMPLETE, NativeUtil.macAddressToByteArray(BSSID));
+        verify(mWifiMonitor, never()).broadcastTargetBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor).broadcastAssociatedBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+
+        reset(mWifiMonitor);
+        mISupplicantStaIfaceCallback.onBssidChanged(
+                BssidChangeReason.DISASSOC, NativeUtil.macAddressToByteArray(BSSID));
+        verify(mWifiMonitor, never()).broadcastTargetBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor, never()).broadcastAssociatedBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+    }
+
+    /**
+     * Tests the handling of EAP failure notification.
+     */
+    @Test
+    public void testEapFailureCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onEapFailure();
+        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN_IFACE_NAME),
+                eq(WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE));
+    }
+
+    /**
+     * Tests the handling of Wps success notification.
+     */
+    @Test
+    public void testWpsSuccessCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onWpsEventSuccess();
+        verify(mWifiMonitor).broadcastWpsSuccessEvent(eq(WLAN_IFACE_NAME));
+    }
+
+    /**
+     * Tests the handling of Wps fail notification.
+     */
+    @Test
+    public void testWpsFailureCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        short cfgError = ISupplicantStaIfaceCallback.WpsConfigError.MULTIPLE_PBC_DETECTED;
+        short errorInd = ISupplicantStaIfaceCallback.WpsErrorIndication.SECURITY_WEP_PROHIBITED;
+        mISupplicantStaIfaceCallback.onWpsEventFail(
+                NativeUtil.macAddressToByteArray(BSSID), cfgError, errorInd);
+        verify(mWifiMonitor).broadcastWpsFailEvent(eq(WLAN_IFACE_NAME),
+                eq((int) cfgError), eq((int) errorInd));
+    }
+
+    /**
+     * Tests the handling of Wps fail notification.
+     */
+    @Test
+    public void testWpsTimeoutCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        short cfgError = ISupplicantStaIfaceCallback.WpsConfigError.MSG_TIMEOUT;
+        short errorInd = ISupplicantStaIfaceCallback.WpsErrorIndication.NO_ERROR;
+        mISupplicantStaIfaceCallback.onWpsEventFail(
+                NativeUtil.macAddressToByteArray(BSSID), cfgError, errorInd);
+        verify(mWifiMonitor).broadcastWpsTimeoutEvent(eq(WLAN_IFACE_NAME));
+    }
+
+    /**
+     * Tests the handling of Wps pbc overlap notification.
+     */
+    @Test
+    public void testWpsPbcOverlapCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        mISupplicantStaIfaceCallback.onWpsEventPbcOverlap();
+        verify(mWifiMonitor).broadcastWpsOverlapEvent(eq(WLAN_IFACE_NAME));
+    }
+
+    /**
+     * Tests the handling of service manager death notification.
+     */
+    @Test
+    public void testServiceManagerDeathCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mServiceManagerDeathCaptor.getValue());
+        assertTrue(mDut.isInitializationComplete());
+
+        mServiceManagerDeathCaptor.getValue().serviceDied(5L);
+
+        assertFalse(mDut.isInitializationComplete());
+        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN_IFACE_NAME));
+    }
+
+    /**
+     * Tests the handling of supplicant death notification.
+     */
+    @Test
+    public void testSupplicantDeathCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mSupplicantDeathCaptor.getValue());
+        assertTrue(mDut.isInitializationComplete());
+
+        mSupplicantDeathCaptor.getValue().serviceDied(5L);
+
+        assertFalse(mDut.isInitializationComplete());
+        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN_IFACE_NAME));
+    }
+
+    /**
+     * Tests the handling of supplicant sta iface death notification.
+     */
+    @Test
+    public void testSupplicantStaIfaceDeathCallback() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mSupplicantStaIfaceDeathCaptor.getValue());
+        assertTrue(mDut.isInitializationComplete());
+
+        mSupplicantStaIfaceDeathCaptor.getValue().serviceDied(5L);
+
+        assertFalse(mDut.isInitializationComplete());
+        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN_IFACE_NAME));
+    }
+
+    /**
+     * Tests the setting of log level.
+     */
+    @Test
+    public void testSetLogLevel() throws Exception {
+        when(mISupplicantMock.setDebugParams(anyInt(), anyBoolean(), anyBoolean()))
+                .thenReturn(mStatusSuccess);
+
+        // Fail before initialization is performed.
+        assertFalse(mDut.setLogLevel(true));
+
+        executeAndValidateInitializationSequence();
+
+        // This should work.
+        assertTrue(mDut.setLogLevel(true));
+        verify(mISupplicantMock)
+                .setDebugParams(eq(ISupplicant.DebugLevel.DEBUG), eq(false), eq(false));
+    }
+
+    /**
+     * Tests the setting of concurrency priority.
+     */
+    @Test
+    public void testConcurrencyPriority() throws Exception {
+        when(mISupplicantMock.setConcurrencyPriority(anyInt())).thenReturn(mStatusSuccess);
+
+        // Fail before initialization is performed.
+        assertFalse(mDut.setConcurrencyPriority(false));
+
+        executeAndValidateInitializationSequence();
+
+        // This should work.
+        assertTrue(mDut.setConcurrencyPriority(false));
+        verify(mISupplicantMock).setConcurrencyPriority(eq(IfaceType.P2P));
+        assertTrue(mDut.setConcurrencyPriority(true));
+        verify(mISupplicantMock).setConcurrencyPriority(eq(IfaceType.STA));
+    }
+
+    /**
+     * Tests the start of wps registrar.
+     */
+    @Test
+    public void testStartWpsRegistrar() throws Exception {
+        when(mISupplicantStaIfaceMock.startWpsRegistrar(any(byte[].class), anyString()))
+                .thenReturn(mStatusSuccess);
+
+        // Fail before initialization is performed.
+        assertFalse(mDut.startWpsRegistrar(null, null));
+
+        executeAndValidateInitializationSequence();
+
+        assertFalse(mDut.startWpsRegistrar(null, null));
+        verify(mISupplicantStaIfaceMock, never()).startWpsRegistrar(any(byte[].class), anyString());
+
+        assertFalse(mDut.startWpsRegistrar(new String(), "452233"));
+        verify(mISupplicantStaIfaceMock, never()).startWpsRegistrar(any(byte[].class), anyString());
+
+        assertTrue(mDut.startWpsRegistrar("45:23:12:12:12:98", "562535"));
+        verify(mISupplicantStaIfaceMock).startWpsRegistrar(any(byte[].class), anyString());
+    }
+
+    /**
+     * Tests the start of wps PBC.
+     */
+    @Test
+    public void testStartWpsPbc() throws Exception {
+        when(mISupplicantStaIfaceMock.startWpsPbc(any(byte[].class))).thenReturn(mStatusSuccess);
+        String bssid = "45:23:12:12:12:98";
+        byte[] bssidBytes = {0x45, 0x23, 0x12, 0x12, 0x12, (byte) 0x98};
+        byte[] anyBssidBytes = {0, 0, 0, 0, 0, 0};
+
+        // Fail before initialization is performed.
+        assertFalse(mDut.startWpsPbc(bssid));
+        verify(mISupplicantStaIfaceMock, never()).startWpsPbc(any(byte[].class));
+
+        executeAndValidateInitializationSequence();
+
+        assertTrue(mDut.startWpsPbc(bssid));
+        verify(mISupplicantStaIfaceMock).startWpsPbc(eq(bssidBytes));
+
+        assertTrue(mDut.startWpsPbc(null));
+        verify(mISupplicantStaIfaceMock).startWpsPbc(eq(anyBssidBytes));
+    }
+
+    private WifiConfiguration createTestWifiConfiguration() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = SUPPLICANT_NETWORK_ID;
+        return config;
+    }
+
+    private void executeAndValidateHs20DeauthImminentCallback(boolean isEss) throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        byte[] bssid = NativeUtil.macAddressToByteArray(BSSID);
+        int reasonCode = isEss ? WnmData.ESS : WnmData.ESS + 1;
+        int reauthDelay = 5;
+        mISupplicantStaIfaceCallback.onHs20DeauthImminentNotice(
+                bssid, reasonCode, reauthDelay, HS20_URL);
+
+        ArgumentCaptor<WnmData> wnmDataCaptor = ArgumentCaptor.forClass(WnmData.class);
+        verify(mWifiMonitor).broadcastWnmEvent(eq(WLAN_IFACE_NAME), wnmDataCaptor.capture());
+        assertEquals(
+                ByteBufferReader.readInteger(
+                        ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
+                wnmDataCaptor.getValue().getBssid());
+        assertEquals(isEss, wnmDataCaptor.getValue().isEss());
+        assertEquals(reauthDelay, wnmDataCaptor.getValue().getDelay());
+        assertEquals(HS20_URL, wnmDataCaptor.getValue().getUrl());
+    }
+
+    private void executeAndValidateInitializationSequence() throws  Exception {
+        executeAndValidateInitializationSequence(false, false, false, false);
+    }
+
+    /**
+     * Calls.initialize(), mocking various call back answers and verifying flow, asserting for the
+     * expected result. Verifies if ISupplicantStaIface manager is initialized or reset.
+     * Each of the arguments will cause a different failure mode when set true.
+     */
+    private void executeAndValidateInitializationSequence(boolean causeRemoteException,
+                                                          boolean getZeroInterfaces,
+                                                          boolean getNullInterface,
+                                                          boolean causeCallbackRegFailure)
+            throws Exception {
+        boolean shouldSucceed =
+                !causeRemoteException && !getZeroInterfaces && !getNullInterface
+                        && !causeCallbackRegFailure;
+        // Setup callback mock answers
+        ArrayList<ISupplicant.IfaceInfo> interfaces;
+        if (getZeroInterfaces) {
+            interfaces = new ArrayList<>();
+        } else {
+            interfaces = mIfaceInfoList;
+        }
+        doAnswer(new GetListInterfacesAnswer(interfaces)).when(mISupplicantMock)
+                .listInterfaces(any(ISupplicant.listInterfacesCallback.class));
+        if (causeRemoteException) {
+            doThrow(new RemoteException("Some error!!!"))
+                    .when(mISupplicantMock).getInterface(any(ISupplicant.IfaceInfo.class),
+                    any(ISupplicant.getInterfaceCallback.class));
+        } else {
+            doAnswer(new GetGetInterfaceAnswer(getNullInterface))
+                    .when(mISupplicantMock).getInterface(any(ISupplicant.IfaceInfo.class),
+                    any(ISupplicant.getInterfaceCallback.class));
+        }
+        /** Callback registeration */
+        if (causeCallbackRegFailure) {
+            doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+                public SupplicantStatus answer(ISupplicantStaIfaceCallback cb)
+                        throws RemoteException {
+                    return mStatusFailure;
+                }
+            }).when(mISupplicantStaIfaceMock)
+                    .registerCallback(any(ISupplicantStaIfaceCallback.class));
+        } else {
+            doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+                public SupplicantStatus answer(ISupplicantStaIfaceCallback cb)
+                        throws RemoteException {
+                    mISupplicantStaIfaceCallback = cb;
+                    return mStatusSuccess;
+                }
+            }).when(mISupplicantStaIfaceMock)
+                    .registerCallback(any(ISupplicantStaIfaceCallback.class));
+        }
+
+        mInOrder = inOrder(mServiceManagerMock, mISupplicantMock, mISupplicantStaIfaceMock,
+                mWifiMonitor);
+        // Initialize SupplicantStaIfaceHal, should call serviceManager.registerForNotifications
+        assertTrue(mDut.initialize());
+        // verify: service manager initialization sequence
+        mInOrder.verify(mServiceManagerMock).linkToDeath(mServiceManagerDeathCaptor.capture(),
+                anyLong());
+        mInOrder.verify(mServiceManagerMock).registerForNotifications(
+                eq(ISupplicant.kInterfaceName), eq(""), mServiceNotificationCaptor.capture());
+        // act: cause the onRegistration(...) callback to execute
+        mServiceNotificationCaptor.getValue().onRegistration(ISupplicant.kInterfaceName, "", true);
+
+        assertTrue(mDut.isInitializationComplete() == shouldSucceed);
+        mInOrder.verify(mISupplicantMock).linkToDeath(mSupplicantDeathCaptor.capture(),
+                anyLong());
+        // verify: listInterfaces is called
+        mInOrder.verify(mISupplicantMock).listInterfaces(
+                any(ISupplicant.listInterfacesCallback.class));
+        if (!getZeroInterfaces) {
+            mInOrder.verify(mISupplicantMock)
+                    .getInterface(any(ISupplicant.IfaceInfo.class),
+                            any(ISupplicant.getInterfaceCallback.class));
+        }
+        if (causeRemoteException) {
+            mInOrder.verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(null));
+        }
+        if (!causeRemoteException && !getZeroInterfaces && !getNullInterface) {
+            mInOrder.verify(mISupplicantStaIfaceMock).linkToDeath(
+                    mSupplicantStaIfaceDeathCaptor.capture(), anyLong());
+            mInOrder.verify(mISupplicantStaIfaceMock)
+                    .registerCallback(any(ISupplicantStaIfaceCallback.class));
+        }
+    }
+
+    private SupplicantStatus createSupplicantStatus(int code) {
+        SupplicantStatus status = new SupplicantStatus();
+        status.code = code;
+        return status;
+    }
+
+    /**
+     * Create an IfaceInfo with given type and name
+     */
+    private ISupplicant.IfaceInfo createIfaceInfo(int type, String name) {
+        ISupplicant.IfaceInfo info = new ISupplicant.IfaceInfo();
+        info.type = type;
+        info.name = name;
+        return info;
+    }
+
+    private class GetListInterfacesAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private ArrayList<ISupplicant.IfaceInfo> mInterfaceList;
+
+        GetListInterfacesAnswer(ArrayList<ISupplicant.IfaceInfo> ifaces) {
+            mInterfaceList = ifaces;
+        }
+
+        public void answer(ISupplicant.listInterfacesCallback cb) {
+            cb.onValues(mStatusSuccess, mInterfaceList);
+        }
+    }
+
+    private class GetGetInterfaceAnswer extends MockAnswerUtil.AnswerWithArguments {
+        boolean mGetNullInterface;
+
+        GetGetInterfaceAnswer(boolean getNullInterface) {
+            mGetNullInterface = getNullInterface;
+        }
+
+        public void answer(ISupplicant.IfaceInfo iface, ISupplicant.getInterfaceCallback cb) {
+            if (mGetNullInterface) {
+                cb.onValues(mStatusSuccess, null);
+            } else {
+                cb.onValues(mStatusSuccess, mISupplicantIfaceMock);
+            }
+        }
+    }
+
+    /**
+     * Setup mocks for connect sequence.
+     */
+    private void setupMocksForConnectSequence(final boolean haveExistingNetwork) throws Exception {
+        final int existingNetworkId = SUPPLICANT_NETWORK_ID;
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public SupplicantStatus answer() throws RemoteException {
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaIfaceMock).disconnect();
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.listNetworksCallback cb) throws RemoteException {
+                if (haveExistingNetwork) {
+                    cb.onValues(mStatusSuccess, new ArrayList<>(Arrays.asList(existingNetworkId)));
+                } else {
+                    cb.onValues(mStatusSuccess, new ArrayList<>());
+                }
+            }
+        }).when(mISupplicantStaIfaceMock)
+                .listNetworks(any(ISupplicantStaIface.listNetworksCallback.class));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public SupplicantStatus answer(int id) throws RemoteException {
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaIfaceMock).removeNetwork(eq(existingNetworkId));
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public void answer(ISupplicantStaIface.addNetworkCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mock(ISupplicantStaNetwork.class));
+                return;
+            }
+        }).when(mISupplicantStaIfaceMock).addNetwork(
+                any(ISupplicantStaIface.addNetworkCallback.class));
+        when(mSupplicantStaNetworkMock.saveWifiConfiguration(any(WifiConfiguration.class)))
+                .thenReturn(true);
+        when(mSupplicantStaNetworkMock.select()).thenReturn(true);
+    }
+
+    /**
+     * Helper function to validate the connect sequence.
+     */
+    private void validateConnectSequence(
+            final boolean haveExistingNetwork, int numNetworkAdditions) throws Exception {
+        if (haveExistingNetwork) {
+            verify(mISupplicantStaIfaceMock).removeNetwork(anyInt());
+        }
+        verify(mISupplicantStaIfaceMock, times(numNetworkAdditions))
+                .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
+        verify(mSupplicantStaNetworkMock, times(numNetworkAdditions))
+                .saveWifiConfiguration(any(WifiConfiguration.class));
+        verify(mSupplicantStaNetworkMock, times(numNetworkAdditions)).select();
+    }
+
+    /**
+     * Helper function to execute all the actions to perform connection to the network.
+     *
+     * @param newFrameworkNetworkId Framework Network Id of the new network to connect.
+     * @param haveExistingNetwork Removes the existing network.
+     * @return the WifiConfiguration object of the new network to connect.
+     */
+    private WifiConfiguration executeAndValidateConnectSequence(
+            final int newFrameworkNetworkId, final boolean haveExistingNetwork) throws Exception {
+        setupMocksForConnectSequence(haveExistingNetwork);
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = newFrameworkNetworkId;
+        assertTrue(mDut.connectToNetwork(config));
+        validateConnectSequence(haveExistingNetwork, 1);
+        return config;
+    }
+
+    /**
+     * Setup mocks for roam sequence.
+     */
+    private void setupMocksForRoamSequence(String roamBssid) throws Exception {
+        doAnswer(new MockAnswerUtil.AnswerWithArguments() {
+            public SupplicantStatus answer() throws RemoteException {
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaIfaceMock).reassociate();
+        when(mSupplicantStaNetworkMock.setBssid(eq(roamBssid))).thenReturn(true);
+    }
+
+    /**
+     * Helper function to execute all the actions to perform roaming to the network.
+     *
+     * @param sameNetwork Roam to the same network or not.
+     */
+    private void executeAndValidateRoamSequence(boolean sameNetwork) throws Exception {
+        int connectedNetworkId = ROAM_NETWORK_ID;
+        String roamBssid = BSSID;
+        int roamNetworkId;
+        if (sameNetwork) {
+            roamNetworkId = connectedNetworkId;
+        } else {
+            roamNetworkId = connectedNetworkId + 1;
+        }
+        executeAndValidateConnectSequence(connectedNetworkId, false);
+        setupMocksForRoamSequence(roamBssid);
+
+        WifiConfiguration roamingConfig = new WifiConfiguration();
+        roamingConfig.networkId = roamNetworkId;
+        roamingConfig.getNetworkSelectionStatus().setNetworkSelectionBSSID(roamBssid);
+        assertTrue(mDut.roamToNetwork(roamingConfig));
+
+        if (!sameNetwork) {
+            validateConnectSequence(false, 2);
+            verify(mSupplicantStaNetworkMock, never()).setBssid(anyString());
+            verify(mISupplicantStaIfaceMock, never()).reassociate();
+        } else {
+            verify(mSupplicantStaNetworkMock).setBssid(eq(roamBssid));
+            verify(mISupplicantStaIfaceMock).reassociate();
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/SupplicantStaNetworkHalTest.java b/tests/wifitests/src/com/android/server/wifi/SupplicantStaNetworkHalTest.java
new file mode 100644
index 0000000..6ea5c55
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/SupplicantStaNetworkHalTest.java
@@ -0,0 +1,1326 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.content.Context;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantNetwork;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetwork;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetworkCallback;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetworkCallback
+        .NetworkRequestEapSimGsmAuthParams;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaNetworkCallback
+        .NetworkRequestEapSimUmtsAuthParams;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.server.wifi.util.NativeUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Unit tests for SupplicantStaNetworkHal
+ */
+public class SupplicantStaNetworkHalTest {
+    private static final String IFACE_NAME = "wlan0";
+    private static final Map<String, String> NETWORK_EXTRAS_VALUES = new HashMap<>();
+    static {
+        NETWORK_EXTRAS_VALUES.put("key1", "value1");
+        NETWORK_EXTRAS_VALUES.put("key2", "value2");
+    }
+    private static final String NETWORK_EXTRAS_SERIALIZED =
+            "%7B%22key1%22%3A%22value1%22%2C%22key2%22%3A%22value2%22%7D";
+    private static final String ANONYMOUS_IDENTITY = "aaa@bbb.cc.ddd";
+
+    private SupplicantStaNetworkHal mSupplicantNetwork;
+    private SupplicantStatus mStatusSuccess;
+    private SupplicantStatus mStatusFailure;
+    @Mock private ISupplicantStaNetwork mISupplicantStaNetworkMock;
+    @Mock private Context mContext;
+    @Mock private WifiMonitor mWifiMonitor;
+    private SupplicantNetworkVariables mSupplicantVariables;
+    private MockResources mResources;
+    private ISupplicantStaNetworkCallback mISupplicantStaNetworkCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mStatusSuccess = createSupplicantStatus(SupplicantStatusCode.SUCCESS);
+        mStatusFailure = createSupplicantStatus(SupplicantStatusCode.FAILURE_UNKNOWN);
+        mSupplicantVariables = new SupplicantNetworkVariables();
+        setupISupplicantNetworkMock();
+
+        mResources = new MockResources();
+        when(mContext.getResources()).thenReturn(mResources);
+        createSupplicantStaNetwork();
+    }
+
+    /**
+     * Tests the saving of WifiConfiguration to wpa_supplicant.
+     */
+    @Test
+    public void testOpenNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        config.updateIdentifier = "45";
+        testWifiConfigurationSaveLoad(config);
+    }
+
+    /**
+     * Tests the saving/loading of WifiConfiguration to wpa_supplicant with psk passphrase.
+     */
+    @Test
+    public void testPskPassphraseNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        config.requirePMF = true;
+        testWifiConfigurationSaveLoad(config);
+        verify(mISupplicantStaNetworkMock).setPskPassphrase(anyString());
+        verify(mISupplicantStaNetworkMock)
+                .getPskPassphrase(any(ISupplicantStaNetwork.getPskPassphraseCallback.class));
+        verify(mISupplicantStaNetworkMock, never()).setPsk(any(byte[].class));
+        verify(mISupplicantStaNetworkMock, never())
+                .getPsk(any(ISupplicantStaNetwork.getPskCallback.class));
+    }
+
+    /**
+     * Tests the saving/loading of WifiConfiguration to wpa_supplicant with raw psk.
+     */
+    @Test
+    public void testPskNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        config.preSharedKey = "945ef00c463c2a7c2496376b13263d1531366b46377179a4b17b393687450779";
+        testWifiConfigurationSaveLoad(config);
+        verify(mISupplicantStaNetworkMock).setPsk(any(byte[].class));
+        verify(mISupplicantStaNetworkMock)
+                .getPsk(any(ISupplicantStaNetwork.getPskCallback.class));
+        verify(mISupplicantStaNetworkMock, never()).setPskPassphrase(anyString());
+        verify(mISupplicantStaNetworkMock)
+                .getPskPassphrase(any(ISupplicantStaNetwork.getPskPassphraseCallback.class));
+    }
+
+    /**
+     * Tests the saving of WifiConfiguration to wpa_supplicant removes enclosing quotes of psk
+     * passphrase
+     */
+    @Test
+    public void testPskNetworkWifiConfigurationSaveRemovesPskQuotes() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        config.preSharedKey = "\"quoted_psd\"";
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        assertEquals(mSupplicantVariables.pskPassphrase,
+                NativeUtil.removeEnclosingQuotes(config.preSharedKey));
+    }
+
+    /**
+     * Tests the saving/loading of WifiConfiguration to wpa_supplicant.
+     */
+    @Test
+    public void testWepNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        config.BSSID = "34:45:19:09:45";
+        testWifiConfigurationSaveLoad(config);
+    }
+
+    /**
+     * Tests the saving of WifiConfiguration to wpa_supplicant.
+     */
+    @Test
+    public void testEapPeapGtcNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig =
+                WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
+        testWifiConfigurationSaveLoad(config);
+    }
+
+    /**
+     * Tests the saving of WifiConfiguration to wpa_supplicant.
+     */
+    @Test
+    public void testEapTlsNoneNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig =
+                WifiConfigurationTestUtil.createTLSWifiEnterpriseConfigWithNonePhase2();
+        testWifiConfigurationSaveLoad(config);
+    }
+
+    /**
+     * Tests the saving of WifiConfiguration to wpa_supplicant.
+     */
+    @Test
+    public void testEapTlsNoneClientCertNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig =
+                WifiConfigurationTestUtil.createTLSWifiEnterpriseConfigWithNonePhase2();
+        config.enterpriseConfig.setClientCertificateAlias("test_alias");
+        testWifiConfigurationSaveLoad(config);
+    }
+
+    /**
+     * Tests the saving of WifiConfiguration to wpa_supplicant.
+     */
+    @Test
+    public void testEapTlsAkaNetworkWifiConfigurationSaveLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig =
+                WifiConfigurationTestUtil.createTLSWifiEnterpriseConfigWithAkaPhase2();
+        testWifiConfigurationSaveLoad(config);
+    }
+
+    /**
+     * Tests the loading of network ID.
+     */
+    @Test
+    public void testNetworkIdLoad() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Modify the supplicant variable directly.
+        mSupplicantVariables.networkId = 5;
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        assertEquals(mSupplicantVariables.networkId, loadConfig.networkId);
+    }
+
+    /**
+     * Tests the failure to load ssid aborts the loading of network variables.
+     */
+    @Test
+    public void testSsidLoadFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getSsidCallback cb) throws RemoteException {
+                cb.onValues(mStatusFailure, mSupplicantVariables.ssid);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getSsid(any(ISupplicantStaNetwork.getSsidCallback.class));
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertFalse(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+    }
+
+    /**
+     * Tests the failure to save ssid.
+     */
+    @Test
+    public void testSsidSaveFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> ssid) throws RemoteException {
+                return mStatusFailure;
+            }
+        }).when(mISupplicantStaNetworkMock).setSsid(any(ArrayList.class));
+
+        assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
+    }
+
+    /**
+     * Tests the failure to save invalid key mgmt (unknown bit set in the
+     * {@link WifiConfiguration#allowedKeyManagement} being saved).
+     */
+    @Test
+    public void testInvalidKeyMgmtSaveFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        config.allowedKeyManagement.set(10);
+        try {
+            assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
+        } catch (IllegalArgumentException e) {
+            return;
+        }
+        assertTrue(false);
+    }
+
+    /**
+     * Tests the failure to load invalid key mgmt (unkown bit set in the supplicant network key_mgmt
+     * variable read).
+     */
+    @Test
+    public void testInvalidKeyMgmtLoadFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Modify the supplicant variable directly.
+        mSupplicantVariables.keyMgmtMask = 0xFFFFF;
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        try {
+            assertFalse(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        } catch (IllegalArgumentException e) {
+            return;
+        }
+        assertTrue(false);
+    }
+
+    /**
+     * Tests the failure to save invalid bssid (less than 6 bytes in the
+     * {@link WifiConfiguration#BSSID} being saved).
+     */
+    @Test
+    public void testInvalidBssidSaveFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        config.getNetworkSelectionStatus().setNetworkSelectionBSSID("45:34:23:12");
+        try {
+            assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
+        } catch (IllegalArgumentException e) {
+            return;
+        }
+        assertTrue(false);
+    }
+
+    /**
+     * Tests the failure to load invalid bssid (less than 6 bytes in the supplicant bssid variable
+     * read).
+     */
+    @Test
+    public void testInvalidBssidLoadFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Modify the supplicant variable directly.
+        mSupplicantVariables.bssid = new byte[3];
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        try {
+            assertFalse(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        } catch (IllegalArgumentException e) {
+            return;
+        }
+        assertTrue(false);
+    }
+
+    /**
+     * Tests the loading of invalid ssid from wpa_supplicant.
+     */
+    @Test
+    public void testInvalidSsidLoadFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Modify the supplicant variable directly.
+        mSupplicantVariables.ssid = null;
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertFalse(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+    }
+
+    /**
+     * Tests the loading of invalid eap method from wpa_supplicant.
+     * Invalid eap method is assumed to be a non enterprise network. So, the loading should
+     * succeed as a non-enterprise network.
+     */
+    @Test
+    public void testInvalidEapMethodLoadFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig =
+                WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Modify the supplicant variable directly.
+        mSupplicantVariables.eapMethod = -1;
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+    }
+
+    /**
+     * Tests the loading of invalid eap phase2 method from wpa_supplicant.
+     */
+    @Test
+    public void testInvalidEapPhase2MethodLoadFailure() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig =
+                WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Modify the supplicant variable directly.
+        mSupplicantVariables.eapPhase2Method = -1;
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertFalse(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+    }
+
+    /**
+     * Tests the parsing of GSM auth response parameters.
+     */
+    @Test
+    public void testSendNetworkEapSimGsmAuthResponseWith2KcSresPair() throws Exception {
+        final byte[] kc = new byte[]{0x45, 0x45, 0x32, 0x34, 0x45, 0x10, 0x34, 0x12};
+        final byte[] sres = new byte[]{0x12, 0x10, 0x32, 0x23};
+        // Send 2 kc/sres pair for this request.
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(kc)
+                + ":" + NativeUtil.hexStringFromByteArray(sres)
+                + ":" + NativeUtil.hexStringFromByteArray(kc)
+                + ":" + NativeUtil.hexStringFromByteArray(sres);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(
+                    ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params)
+                    throws RemoteException {
+                assertEquals(2, params.size());
+                assertArrayEquals(kc, params.get(0).kc);
+                assertArrayEquals(sres, params.get(0).sres);
+                assertArrayEquals(kc, params.get(1).kc);
+                assertArrayEquals(sres, params.get(1).sres);
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimGsmAuthResponse(any(ArrayList.class));
+
+        assertTrue(mSupplicantNetwork.sendNetworkEapSimGsmAuthResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of GSM auth response parameters.
+     */
+    @Test
+    public void testSendNetworkEapSimGsmAuthResponseWith3KcSresPair() throws Exception {
+        final byte[] kc1 = new byte[]{0x45, 0x45, 0x32, 0x34, 0x45, 0x10, 0x34, 0x12};
+        final byte[] sres1 = new byte[]{0x12, 0x10, 0x32, 0x23};
+        final byte[] kc2 = new byte[]{0x45, 0x34, 0x12, 0x34, 0x45, 0x10, 0x34, 0x12};
+        final byte[] sres2 = new byte[]{0x12, 0x23, 0x12, 0x23};
+        final byte[] kc3 = new byte[]{0x25, 0x34, 0x12, 0x14, 0x45, 0x10, 0x34, 0x12};
+        final byte[] sres3 = new byte[]{0x42, 0x23, 0x22, 0x23};
+        // Send 3 kc/sres pair for this request.
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(kc1)
+                + ":" + NativeUtil.hexStringFromByteArray(sres1)
+                + ":" + NativeUtil.hexStringFromByteArray(kc2)
+                + ":" + NativeUtil.hexStringFromByteArray(sres2)
+                + ":" + NativeUtil.hexStringFromByteArray(kc3)
+                + ":" + NativeUtil.hexStringFromByteArray(sres3);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(
+                    ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params)
+                    throws RemoteException {
+                assertEquals(3, params.size());
+                assertArrayEquals(kc1, params.get(0).kc);
+                assertArrayEquals(sres1, params.get(0).sres);
+                assertArrayEquals(kc2, params.get(1).kc);
+                assertArrayEquals(sres2, params.get(1).sres);
+                assertArrayEquals(kc3, params.get(2).kc);
+                assertArrayEquals(sres3, params.get(2).sres);
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimGsmAuthResponse(any(ArrayList.class));
+
+        assertTrue(mSupplicantNetwork.sendNetworkEapSimGsmAuthResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of invalid GSM auth response parameters (invalid kc & sres lengths).
+     */
+    @Test
+    public void testSendInvalidKcSresLenNetworkEapSimGsmAuthResponse() throws Exception {
+        final byte[] kc1 = new byte[]{0x45, 0x45, 0x32, 0x34, 0x45, 0x10, 0x34};
+        final byte[] sres1 = new byte[]{0x12, 0x10, 0x23};
+        final byte[] kc2 = new byte[]{0x45, 0x34, 0x12, 0x34, 0x45, 0x10, 0x34, 0x12};
+        final byte[] sres2 = new byte[]{0x12, 0x23, 0x12, 0x23};
+        // Send 2 kc/sres pair for this request.
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(kc1)
+                + ":" + NativeUtil.hexStringFromByteArray(sres1)
+                + ":" + NativeUtil.hexStringFromByteArray(kc2)
+                + ":" + NativeUtil.hexStringFromByteArray(sres2);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(
+                    ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params)
+                    throws RemoteException {
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimGsmAuthResponse(any(ArrayList.class));
+
+        assertFalse(mSupplicantNetwork.sendNetworkEapSimGsmAuthResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of invalid GSM auth response parameters (invalid number of kc/sres pairs).
+     */
+    @Test
+    public void testSendInvalidKcSresPairNumNetworkEapSimGsmAuthResponse() throws Exception {
+        final byte[] kc = new byte[]{0x45, 0x34, 0x12, 0x34, 0x45, 0x10, 0x34, 0x12};
+        final byte[] sres = new byte[]{0x12, 0x23, 0x12, 0x23};
+        // Send 1 kc/sres pair for this request.
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(kc)
+                + ":" + NativeUtil.hexStringFromByteArray(sres);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(
+                    ArrayList<ISupplicantStaNetwork.NetworkResponseEapSimGsmAuthParams> params)
+                    throws RemoteException {
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimGsmAuthResponse(any(ArrayList.class));
+
+        assertFalse(mSupplicantNetwork.sendNetworkEapSimGsmAuthResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of UMTS auth response parameters.
+     */
+    @Test
+    public void testSendNetworkEapSimUmtsAuthResponse() throws Exception {
+        final byte[] ik = new byte[]{0x45, 0x45, 0x32, 0x34, 0x45, 0x10, 0x34, 0x12, 0x23, 0x34,
+                0x33, 0x23, 0x34, 0x10, 0x40, 0x34};
+        final byte[] ck = new byte[]{0x12, 0x10, 0x32, 0x23, 0x45, 0x10, 0x34, 0x12, 0x23, 0x34,
+                0x33, 0x23, 0x34, 0x10, 0x40, 0x34};
+        final byte[] res = new byte[]{0x12, 0x10, 0x32, 0x23, 0x45, 0x10, 0x34, 0x12, 0x23, 0x34};
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(ik)
+                + ":" + NativeUtil.hexStringFromByteArray(ck)
+                + ":" + NativeUtil.hexStringFromByteArray(res);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(
+                    ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams params)
+                    throws RemoteException {
+                assertArrayEquals(ik, params.ik);
+                assertArrayEquals(ck, params.ck);
+                // Convert to arraylist before comparison.
+                ArrayList<Byte> resList = new ArrayList<>();
+                for (byte b : res) {
+                    resList.add(b);
+                }
+                assertEquals(resList, params.res);
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimUmtsAuthResponse(
+                any(ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams.class));
+
+        assertTrue(mSupplicantNetwork.sendNetworkEapSimUmtsAuthResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of invalid UMTS auth response parameters (invalid ik, ck lengths).
+     */
+    @Test
+    public void testSendInvalidNetworkEapSimUmtsAuthResponse() throws Exception {
+        final byte[] ik = new byte[]{0x45, 0x45, 0x32, 0x34, 0x45, 0x10, 0x34, 0x12};
+        final byte[] ck = new byte[]{0x12, 0x10, 0x32, 0x23, 0x45, 0x10, 0x34, 0x12, 0x23, 0x34,
+                0x33, 0x23, 0x34, 0x10, 0x40};
+        final byte[] res = new byte[]{0x12, 0x10, 0x32, 0x23, 0x45, 0x10, 0x34, 0x12, 0x23, 0x34};
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(ik)
+                + ":" + NativeUtil.hexStringFromByteArray(ck)
+                + ":" + NativeUtil.hexStringFromByteArray(res);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(
+                    ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams params)
+                    throws RemoteException {
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimUmtsAuthResponse(
+                any(ISupplicantStaNetwork.NetworkResponseEapSimUmtsAuthParams.class));
+
+        assertFalse(mSupplicantNetwork.sendNetworkEapSimUmtsAuthResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of UMTS auts response parameters.
+     */
+    @Test
+    public void testSendNetworkEapSimUmtsAutsResponse() throws Exception {
+        final byte[] auts = new byte[]{0x45, 0x45, 0x32, 0x34, 0x45, 0x10, 0x34, 0x12, 0x23, 0x34,
+                0x33, 0x23, 0x34, 0x10};
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(auts);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(byte[] params)
+                    throws RemoteException {
+                assertArrayEquals(auts, params);
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimUmtsAutsResponse(any(byte[].class));
+
+        assertTrue(mSupplicantNetwork.sendNetworkEapSimUmtsAutsResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of invalid UMTS auts response parameters (invalid auts length).
+     */
+    @Test
+    public void testSendInvalidNetworkEapSimUmtsAutsResponse() throws Exception {
+        final byte[] auts = new byte[]{0x45, 0x45, 0x32, 0x34, 0x45, 0x10, 0x34, 0x12, 0x23};
+        String paramsStr = ":" + NativeUtil.hexStringFromByteArray(auts);
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(byte[] params)
+                    throws RemoteException {
+                assertArrayEquals(auts, params);
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapSimUmtsAutsResponse(any(byte[].class));
+
+        assertFalse(mSupplicantNetwork.sendNetworkEapSimUmtsAutsResponse(paramsStr));
+    }
+
+    /**
+     * Tests the parsing of identity string.
+     */
+    @Test
+    public void testSendNetworkEapIdentityResponse() throws Exception {
+        final String identityStr = "test@test.com";
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> identity)
+                    throws RemoteException {
+                assertEquals(identityStr, NativeUtil.stringFromByteArrayList(identity));
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).sendNetworkEapIdentityResponse(any(ArrayList.class));
+
+        assertTrue(mSupplicantNetwork.sendNetworkEapIdentityResponse(identityStr));
+    }
+
+    /**
+     * Tests the addition of FT flags when the device supports it.
+     */
+    @Test
+    public void testAddFtPskFlags() throws Exception {
+        mResources.setBoolean(R.bool.config_wifi_fast_bss_transition_enabled, true);
+        createSupplicantStaNetwork();
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Check the supplicant variables to ensure that we have added the FT flags.
+        assertTrue((mSupplicantVariables.keyMgmtMask & ISupplicantStaNetwork.KeyMgmtMask.FT_PSK)
+                == ISupplicantStaNetwork.KeyMgmtMask.FT_PSK);
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        // The FT flags should be stripped out when reading it back.
+        WifiConfigurationTestUtil.assertConfigurationEqualForSupplicant(config, loadConfig);
+    }
+
+    /**
+     * Tests the addition of FT flags when the device supports it.
+     */
+    @Test
+    public void testAddFtEapFlags() throws Exception {
+        mResources.setBoolean(R.bool.config_wifi_fast_bss_transition_enabled, true);
+        createSupplicantStaNetwork();
+
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+
+        // Check the supplicant variables to ensure that we have added the FT flags.
+        assertTrue((mSupplicantVariables.keyMgmtMask & ISupplicantStaNetwork.KeyMgmtMask.FT_EAP)
+                == ISupplicantStaNetwork.KeyMgmtMask.FT_EAP);
+
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        // The FT flags should be stripped out when reading it back.
+        WifiConfigurationTestUtil.assertConfigurationEqualForSupplicant(config, loadConfig);
+    }
+
+    /**
+     * Tests the retrieval of WPS NFC token.
+     */
+    @Test
+    public void testGetWpsNfcConfigurationToken() throws Exception {
+        final ArrayList<Byte> token = new ArrayList<>();
+        token.add(Byte.valueOf((byte) 0x45));
+        token.add(Byte.valueOf((byte) 0x34));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getWpsNfcConfigurationTokenCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, token);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getWpsNfcConfigurationToken(
+                        any(ISupplicantStaNetwork.getWpsNfcConfigurationTokenCallback.class));
+
+        assertEquals("4534", mSupplicantNetwork.getWpsNfcConfigurationToken());
+    }
+
+    /**
+     * Tests that callback registration failure triggers a failure in saving network config.
+     */
+    @Test
+    public void testSaveFailureDueToCallbackReg() throws Exception {
+        when(mISupplicantStaNetworkMock.registerCallback(any(ISupplicantStaNetworkCallback.class)))
+                .thenReturn(mStatusFailure);
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertFalse(mSupplicantNetwork.saveWifiConfiguration(config));
+    }
+
+    /**
+     * Tests the network gsm auth callback.
+     */
+    @Test
+    public void testNetworkEapGsmAuthCallback() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        assertNotNull(mISupplicantStaNetworkCallback);
+
+        // Now trigger eap gsm callback and ensure that the event is broadcast via WifiMonitor.
+        NetworkRequestEapSimGsmAuthParams params = new NetworkRequestEapSimGsmAuthParams();
+        Random random = new Random();
+        byte[] rand1 = new byte[16];
+        byte[] rand2 = new byte[16];
+        byte[] rand3 = new byte[16];
+        random.nextBytes(rand1);
+        random.nextBytes(rand2);
+        random.nextBytes(rand3);
+        params.rands.add(rand1);
+        params.rands.add(rand2);
+        params.rands.add(rand3);
+
+        String[] expectedRands = {
+                NativeUtil.hexStringFromByteArray(rand1), NativeUtil.hexStringFromByteArray(rand2),
+                NativeUtil.hexStringFromByteArray(rand3)
+        };
+
+        mISupplicantStaNetworkCallback.onNetworkEapSimGsmAuthRequest(params);
+        verify(mWifiMonitor).broadcastNetworkGsmAuthRequestEvent(
+                eq(IFACE_NAME), eq(config.networkId), eq(config.SSID), eq(expectedRands));
+    }
+
+    /**
+     * Tests the network umts auth callback.
+     */
+    @Test
+    public void testNetworkEapUmtsAuthCallback() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        assertNotNull(mISupplicantStaNetworkCallback);
+
+        // Now trigger eap gsm callback and ensure that the event is broadcast via WifiMonitor.
+        NetworkRequestEapSimUmtsAuthParams params = new NetworkRequestEapSimUmtsAuthParams();
+        Random random = new Random();
+        random.nextBytes(params.autn);
+        random.nextBytes(params.rand);
+
+        String[] expectedRands = {
+                NativeUtil.hexStringFromByteArray(params.rand),
+                NativeUtil.hexStringFromByteArray(params.autn)
+        };
+
+        mISupplicantStaNetworkCallback.onNetworkEapSimUmtsAuthRequest(params);
+        verify(mWifiMonitor).broadcastNetworkUmtsAuthRequestEvent(
+                eq(IFACE_NAME), eq(config.networkId), eq(config.SSID), eq(expectedRands));
+    }
+
+    /**
+     * Tests the network identity callback.
+     */
+    @Test
+    public void testNetworkIdentityCallback() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPskNetwork();
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        assertNotNull(mISupplicantStaNetworkCallback);
+
+        // Now trigger identity request callback and ensure that the event is broadcast via
+        // WifiMonitor.
+        mISupplicantStaNetworkCallback.onNetworkEapIdentityRequest();
+        verify(mWifiMonitor).broadcastNetworkIdentityRequestEvent(
+                eq(IFACE_NAME), eq(config.networkId), eq(config.SSID));
+    }
+
+    private void testWifiConfigurationSaveLoad(WifiConfiguration config) {
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        WifiConfiguration loadConfig = new WifiConfiguration();
+        Map<String, String> networkExtras = new HashMap<>();
+        assertTrue(mSupplicantNetwork.loadWifiConfiguration(loadConfig, networkExtras));
+        WifiConfigurationTestUtil.assertConfigurationEqualForSupplicant(config, loadConfig);
+        assertEquals(config.configKey(),
+                networkExtras.get(SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY));
+        assertEquals(
+                config.creatorUid,
+                Integer.parseInt(networkExtras.get(
+                        SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID)));
+        // There is no getter for this one, so check the supplicant variable.
+        if (!TextUtils.isEmpty(config.updateIdentifier)) {
+            assertEquals(Integer.parseInt(config.updateIdentifier),
+                    mSupplicantVariables.updateIdentifier);
+        }
+        // There is no getter for this one, so check the supplicant variable.
+        String oppKeyCaching =
+                config.enterpriseConfig.getFieldValue(WifiEnterpriseConfig.OPP_KEY_CACHING);
+        if (!TextUtils.isEmpty(oppKeyCaching)) {
+            assertEquals(
+                    Integer.parseInt(oppKeyCaching) == 1 ? true : false,
+                    mSupplicantVariables.eapProactiveKeyCaching);
+        }
+    }
+
+    /**
+     * Verifies that createNetworkExtra() & parseNetworkExtra correctly writes a serialized and
+     * URL-encoded JSON object.
+     */
+    @Test
+    public void testNetworkExtra() {
+        assertEquals(NETWORK_EXTRAS_SERIALIZED,
+                SupplicantStaNetworkHal.createNetworkExtra(NETWORK_EXTRAS_VALUES));
+        assertEquals(NETWORK_EXTRAS_VALUES,
+                SupplicantStaNetworkHal.parseNetworkExtra(NETWORK_EXTRAS_SERIALIZED));
+    }
+
+    /**
+     * Verifies that fetachEapAnonymousIdentity() can get the anonymous identity from supplicant.
+     */
+    @Test
+    public void testFetchEapAnonymousIdentity() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createEapNetwork();
+        config.enterpriseConfig.setAnonymousIdentity(ANONYMOUS_IDENTITY);
+        assertTrue(mSupplicantNetwork.saveWifiConfiguration(config));
+        assertEquals(ANONYMOUS_IDENTITY, mSupplicantNetwork.fetchEapAnonymousIdentity());
+    }
+
+    /**
+     * Sets up the HIDL interface mock with all the setters/getter values.
+     * Note: This only sets up the mock to return success on all methods.
+     */
+    private void setupISupplicantNetworkMock() throws Exception {
+        /** SSID */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> ssid) throws RemoteException {
+                mSupplicantVariables.ssid = ssid;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setSsid(any(ArrayList.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getSsidCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.ssid);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getSsid(any(ISupplicantStaNetwork.getSsidCallback.class));
+
+        /** Network Id */
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantNetwork.getIdCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.networkId);
+            }
+        }).when(mISupplicantStaNetworkMock).getId(any(ISupplicantNetwork.getIdCallback.class));
+
+        /** BSSID */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(byte[] bssid) throws RemoteException {
+                mSupplicantVariables.bssid = bssid;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setBssid(any(byte[].class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getBssidCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.bssid);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getBssid(any(ISupplicantStaNetwork.getBssidCallback.class));
+
+        /** Scan SSID (Is Hidden Network?) */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(boolean enable) throws RemoteException {
+                mSupplicantVariables.scanSsid = enable;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setScanSsid(any(boolean.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getScanSsidCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.scanSsid);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getScanSsid(any(ISupplicantStaNetwork.getScanSsidCallback.class));
+
+        /** Require PMF*/
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(boolean enable) throws RemoteException {
+                mSupplicantVariables.requirePmf = enable;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setRequirePmf(any(boolean.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getRequirePmfCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.requirePmf);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getRequirePmf(any(ISupplicantStaNetwork.getRequirePmfCallback.class));
+
+        /** PSK passphrase */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String pskPassphrase) throws RemoteException {
+                mSupplicantVariables.pskPassphrase = pskPassphrase;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setPskPassphrase(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getPskPassphraseCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.pskPassphrase);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getPskPassphrase(any(ISupplicantStaNetwork.getPskPassphraseCallback.class));
+
+        /** PSK */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(byte[] psk) throws RemoteException {
+                mSupplicantVariables.psk = psk;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setPsk(any(byte[].class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getPskCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.psk);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getPsk(any(ISupplicantStaNetwork.getPskCallback.class));
+
+        /** WEP keys **/
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int keyIdx, ArrayList<Byte> key) throws RemoteException {
+                mSupplicantVariables.wepKey[keyIdx] = key;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setWepKey(any(int.class), any(ArrayList.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(int keyIdx, ISupplicantStaNetwork.getWepKeyCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.wepKey[keyIdx]);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getWepKey(any(int.class), any(ISupplicantStaNetwork.getWepKeyCallback.class));
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int keyIdx) throws RemoteException {
+                mSupplicantVariables.wepTxKeyIdx = keyIdx;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setWepTxKeyIdx(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getWepTxKeyIdxCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.wepTxKeyIdx);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getWepTxKeyIdx(any(ISupplicantStaNetwork.getWepTxKeyIdxCallback.class));
+
+        /** allowedKeyManagement */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int mask) throws RemoteException {
+                mSupplicantVariables.keyMgmtMask = mask;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setKeyMgmt(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getKeyMgmtCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.keyMgmtMask);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getKeyMgmt(any(ISupplicantStaNetwork.getKeyMgmtCallback.class));
+
+        /** allowedProtocols */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int mask) throws RemoteException {
+                mSupplicantVariables.protoMask = mask;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setProto(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getProtoCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.protoMask);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getProto(any(ISupplicantStaNetwork.getProtoCallback.class));
+
+        /** allowedAuthAlgorithms */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int mask) throws RemoteException {
+                mSupplicantVariables.authAlgMask = mask;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setAuthAlg(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getAuthAlgCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.authAlgMask);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getAuthAlg(any(ISupplicantStaNetwork.getAuthAlgCallback.class));
+
+        /** allowedGroupCiphers */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int mask) throws RemoteException {
+                mSupplicantVariables.groupCipherMask = mask;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setGroupCipher(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getGroupCipherCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.groupCipherMask);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getGroupCipher(any(ISupplicantStaNetwork.getGroupCipherCallback.class));
+
+        /** allowedPairwiseCiphers */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int mask) throws RemoteException {
+                mSupplicantVariables.pairwiseCipherMask = mask;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setPairwiseCipher(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getPairwiseCipherCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.pairwiseCipherMask);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getPairwiseCipher(any(ISupplicantStaNetwork.getPairwiseCipherCallback.class));
+
+        /** metadata: idstr */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String idStr) throws RemoteException {
+                mSupplicantVariables.idStr = idStr;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setIdStr(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getIdStrCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.idStr);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getIdStr(any(ISupplicantStaNetwork.getIdStrCallback.class));
+
+        /** UpdateIdentifier */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int identifier) throws RemoteException {
+                mSupplicantVariables.updateIdentifier = identifier;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setUpdateIdentifier(any(int.class));
+
+        /** EAP method */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int method) throws RemoteException {
+                mSupplicantVariables.eapMethod = method;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapMethod(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapMethodCallback cb)
+                    throws RemoteException {
+                // When not set, return failure.
+                if (mSupplicantVariables.eapMethod == -1) {
+                    cb.onValues(mStatusFailure, mSupplicantVariables.eapMethod);
+                } else {
+                    cb.onValues(mStatusSuccess, mSupplicantVariables.eapMethod);
+                }
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapMethod(any(ISupplicantStaNetwork.getEapMethodCallback.class));
+
+        /** EAP Phase 2 method */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int method) throws RemoteException {
+                mSupplicantVariables.eapPhase2Method = method;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapPhase2Method(any(int.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapPhase2MethodCallback cb)
+                    throws RemoteException {
+                // When not set, return failure.
+                if (mSupplicantVariables.eapPhase2Method == -1) {
+                    cb.onValues(mStatusFailure, mSupplicantVariables.eapPhase2Method);
+                } else {
+                    cb.onValues(mStatusSuccess, mSupplicantVariables.eapPhase2Method);
+                }
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapPhase2Method(any(ISupplicantStaNetwork.getEapPhase2MethodCallback.class));
+
+        /** EAP Identity */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> identity) throws RemoteException {
+                mSupplicantVariables.eapIdentity = identity;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapIdentity(any(ArrayList.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapIdentityCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapIdentity);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapIdentity(any(ISupplicantStaNetwork.getEapIdentityCallback.class));
+
+        /** EAP Anonymous Identity */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> identity) throws RemoteException {
+                mSupplicantVariables.eapAnonymousIdentity = identity;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapAnonymousIdentity(any(ArrayList.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapAnonymousIdentityCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapAnonymousIdentity);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapAnonymousIdentity(
+                        any(ISupplicantStaNetwork.getEapAnonymousIdentityCallback.class));
+
+        /** EAP Password */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> password) throws RemoteException {
+                mSupplicantVariables.eapPassword = password;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapPassword(any(ArrayList.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapPasswordCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapPassword);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapPassword(any(ISupplicantStaNetwork.getEapPasswordCallback.class));
+
+        /** EAP Client Cert */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String cert) throws RemoteException {
+                mSupplicantVariables.eapClientCert = cert;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapClientCert(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapClientCertCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapClientCert);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapClientCert(any(ISupplicantStaNetwork.getEapClientCertCallback.class));
+
+        /** EAP CA Cert */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String cert) throws RemoteException {
+                mSupplicantVariables.eapCACert = cert;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapCACert(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapCACertCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapCACert);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapCACert(any(ISupplicantStaNetwork.getEapCACertCallback.class));
+
+        /** EAP Subject Match */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String match) throws RemoteException {
+                mSupplicantVariables.eapSubjectMatch = match;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapSubjectMatch(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapSubjectMatchCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapSubjectMatch);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapSubjectMatch(any(ISupplicantStaNetwork.getEapSubjectMatchCallback.class));
+
+        /** EAP Engine */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(boolean enable) throws RemoteException {
+                mSupplicantVariables.eapEngine = enable;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapEngine(any(boolean.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapEngineCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapEngine);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapEngine(any(ISupplicantStaNetwork.getEapEngineCallback.class));
+
+        /** EAP Engine ID */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String id) throws RemoteException {
+                mSupplicantVariables.eapEngineID = id;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapEngineID(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapEngineIDCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapEngineID);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapEngineID(any(ISupplicantStaNetwork.getEapEngineIDCallback.class));
+
+        /** EAP Private Key */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String key) throws RemoteException {
+                mSupplicantVariables.eapPrivateKeyId = key;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapPrivateKeyId(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapPrivateKeyIdCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapPrivateKeyId);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapPrivateKeyId(any(ISupplicantStaNetwork.getEapPrivateKeyIdCallback.class));
+
+        /** EAP Alt Subject Match */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String match) throws RemoteException {
+                mSupplicantVariables.eapAltSubjectMatch = match;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapAltSubjectMatch(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapAltSubjectMatchCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapAltSubjectMatch);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapAltSubjectMatch(
+                        any(ISupplicantStaNetwork.getEapAltSubjectMatchCallback.class));
+
+        /** EAP Domain Suffix Match */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String match) throws RemoteException {
+                mSupplicantVariables.eapDomainSuffixMatch = match;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapDomainSuffixMatch(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapDomainSuffixMatchCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapDomainSuffixMatch);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapDomainSuffixMatch(
+                        any(ISupplicantStaNetwork.getEapDomainSuffixMatchCallback.class));
+
+        /** EAP CA Path*/
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(String path) throws RemoteException {
+                mSupplicantVariables.eapCAPath = path;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setEapCAPath(any(String.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantStaNetwork.getEapCAPathCallback cb)
+                    throws RemoteException {
+                cb.onValues(mStatusSuccess, mSupplicantVariables.eapCAPath);
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .getEapCAPath(any(ISupplicantStaNetwork.getEapCAPathCallback.class));
+
+        /** EAP Proactive Key Caching */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(boolean enable) throws RemoteException {
+                mSupplicantVariables.eapProactiveKeyCaching = enable;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock).setProactiveKeyCaching(any(boolean.class));
+
+        /** Callback registeration */
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ISupplicantStaNetworkCallback cb)
+                    throws RemoteException {
+                mISupplicantStaNetworkCallback = cb;
+                return mStatusSuccess;
+            }
+        }).when(mISupplicantStaNetworkMock)
+                .registerCallback(any(ISupplicantStaNetworkCallback.class));
+    }
+
+    private SupplicantStatus createSupplicantStatus(int code) {
+        SupplicantStatus status = new SupplicantStatus();
+        status.code = code;
+        return status;
+    }
+
+    /**
+     * Need this for tests which wants to manipulate context before creating the instance.
+     */
+    private void createSupplicantStaNetwork() {
+        mSupplicantNetwork =
+                new SupplicantStaNetworkHal(
+                        mISupplicantStaNetworkMock, IFACE_NAME, mContext, mWifiMonitor);
+    }
+
+    // Private class to to store/inspect values set via the HIDL mock.
+    private class SupplicantNetworkVariables {
+        public ArrayList<Byte> ssid;
+        public int networkId;
+        public byte[/* 6 */] bssid;
+        public int keyMgmtMask;
+        public int protoMask;
+        public int authAlgMask;
+        public int groupCipherMask;
+        public int pairwiseCipherMask;
+        public boolean scanSsid;
+        public boolean requirePmf;
+        public String idStr;
+        public int updateIdentifier;
+        public String pskPassphrase;
+        public byte[] psk;
+        public ArrayList<Byte>[] wepKey = new ArrayList[4];
+        public int wepTxKeyIdx;
+        public int eapMethod = -1;
+        public int eapPhase2Method = -1;
+        public ArrayList<Byte> eapIdentity;
+        public ArrayList<Byte> eapAnonymousIdentity;
+        public ArrayList<Byte> eapPassword;
+        public String eapCACert;
+        public String eapCAPath;
+        public String eapClientCert;
+        public String eapPrivateKeyId;
+        public String eapSubjectMatch;
+        public String eapAltSubjectMatch;
+        public boolean eapEngine;
+        public String eapEngineID;
+        public String eapDomainSuffixMatch;
+        public boolean eapProactiveKeyCaching;
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/SupplicantStateTrackerTest.java b/tests/wifitests/src/com/android/server/wifi/SupplicantStateTrackerTest.java
new file mode 100644
index 0000000..98fa800
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/SupplicantStateTrackerTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+/**
+ * Unit tests for {@link android.net.wifi.SupplicantStateTracker}.
+ */
+public class SupplicantStateTrackerTest {
+
+    private static final String TAG = "SupplicantStateTrackerTest";
+    private static final String   sSSID = "\"GoogleGuest\"";
+    private static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
+    private static final String   sBSSID = "01:02:03:04:05:06";
+
+    private @Mock WifiConfigManager mWcm;
+    private @Mock Context mContext;
+    private Handler mHandler;
+    private SupplicantStateTracker mSupplicantStateTracker;
+    private TestLooper mLooper;
+    private FrameworkFacade mFacade;
+
+    private FrameworkFacade getFrameworkFacade() {
+        FrameworkFacade facade = mock(FrameworkFacade.class);
+        IBatteryStats batteryStatsService = mock(IBatteryStats.class);
+        when(facade.getBatteryService()).thenReturn(batteryStatsService);
+        return facade;
+    }
+
+    private Message getSupplicantStateChangeMessage(int networkId, WifiSsid wifiSsid,
+            String bssid, SupplicantState newSupplicantState) {
+        return Message.obtain(null, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(networkId, wifiSsid, bssid, newSupplicantState));
+
+    }
+
+    @Before
+    public void setUp() {
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+        MockitoAnnotations.initMocks(this);
+        mFacade = getFrameworkFacade();
+        mSupplicantStateTracker = new SupplicantStateTracker(mContext, mWcm, mFacade, mHandler);
+    }
+
+    /**
+     * This test verifies that the SupplicantStateTracker sends a broadcast intent upon receiving
+     * a message when supplicant state changes
+     */
+    @Test
+    public void testSupplicantStateChangeIntent() {
+        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
+                SupplicantState recvdState =
+                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
+                assertEquals(SupplicantState.SCANNING, recvdState);
+            }
+        };
+        IntentFilter mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
+                sBSSID, SupplicantState.SCANNING));
+    }
+
+    /**
+     * This test verifies that the current auth status is sent in the Broadcast intent
+     */
+    @Test
+    public void testAuthPassInSupplicantStateChangeIntent() {
+        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
+                SupplicantState recvdState =
+                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
+                assertEquals(SupplicantState.AUTHENTICATING, recvdState);
+                boolean authStatus =
+                        (boolean) intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
+                assertEquals(authStatus, true);
+            }
+        };
+        IntentFilter mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
+                sBSSID, SupplicantState.AUTHENTICATING));
+        mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
+    }
+
+    /**
+     * This test verifies that the current auth status is sent in the Broadcast intent
+     */
+    @Test
+    public void testAuthFailedInSupplicantStateChangeIntent() {
+        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
+                SupplicantState recvdState =
+                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
+                assertEquals(SupplicantState.AUTHENTICATING, recvdState);
+                boolean authStatus =
+                        (boolean) intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
+                assertEquals(authStatus, false);
+            }
+        };
+        IntentFilter mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
+        mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
+                sBSSID, SupplicantState.AUTHENTICATING));
+    }
+
+    /**
+     * This test verifies the correct reasonCode for auth failure is sent in Broadcast
+     * intent.
+     */
+    @Test
+    public void testReasonCodeInSupplicantStateChangeIntent() {
+        BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                assertTrue(action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION));
+                SupplicantState recvdState =
+                        (SupplicantState) intent.getExtra(WifiManager.EXTRA_NEW_STATE, -1);
+                assertEquals(SupplicantState.AUTHENTICATING, recvdState);
+                boolean authStatus =
+                        (boolean) intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
+                assertEquals(authStatus, false);
+                int reasonCode = (int)
+                        intent.getExtra(WifiManager.EXTRA_SUPPLICANT_ERROR_REASON, -1);
+                assertEquals(reasonCode, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+            }
+        };
+        IntentFilter mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(wifiBroadcastReceiver, mIntentFilter);
+        mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, 0,
+                WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+        mSupplicantStateTracker.sendMessage(getSupplicantStateChangeMessage(0, sWifiSsid,
+                sBSSID, SupplicantState.AUTHENTICATING));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/TestUtil.java b/tests/wifitests/src/com/android/server/wifi/TestUtil.java
index 453cfa8..a0a1030 100644
--- a/tests/wifitests/src/com/android/server/wifi/TestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/TestUtil.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wifi;
 
-import static org.mockito.Mockito.when;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -25,25 +23,12 @@
 import android.net.NetworkInfo;
 import android.net.wifi.WifiManager;
 
-import java.lang.reflect.Field;
 import java.util.ArrayList;
 
 /**
  * Utils for wifi tests.
  */
 public class TestUtil {
-
-    /**
-     * Override wifi interface using {@code wifiNative}.
-     */
-    public static void installWlanWifiNative(WifiNative wifiNative) throws Exception {
-        Field field = WifiNative.class.getDeclaredField("wlanNativeInterface");
-        field.setAccessible(true);
-        field.set(null, wifiNative);
-
-        when(wifiNative.getInterfaceName()).thenReturn("mockWlan");
-    }
-
     /**
      * Send {@link WifiManager#NETWORK_STATE_CHANGED_ACTION} broadcast.
      */
@@ -86,6 +71,25 @@
     }
 
     /**
+     * Send {@link WifiManager#WIFI_AP_STATE_CHANGED} broadcast.
+     */
+    public static void sendWifiApStateChanged(BroadcastReceiver broadcastReceiver,
+            Context context, int apState, int previousState, int error, String ifaceName,
+            int mode) {
+        Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, apState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousState);
+        if (apState == WifiManager.WIFI_AP_STATE_FAILED) {
+            // only set reason number when softAP start failed
+            intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, error);
+        }
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, ifaceName);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mode);
+
+        broadcastReceiver.onReceive(context, intent);
+    }
+
+    /**
      * Send {@link ConnectivityManager#ACTION_TETHER_STATE_CHANGED} broadcast.
      */
     public static void sendTetherStateChanged(BroadcastReceiver broadcastReceiver,
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
index a942a08..2d3b066 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
@@ -49,6 +49,9 @@
     private static final String TEST_DEFAULT_2G_CHANNEL_LIST = "1,2,3,4,5,6";
     private static final String TEST_DEFAULT_AP_SSID = "TestAP";
     private static final String TEST_CONFIGURED_AP_SSID = "ConfiguredAP";
+    private static final String TEST_DEFAULT_HOTSPOT_SSID = "TestShare";
+    private static final int RAND_SSID_INT_MIN = 1000;
+    private static final int RAND_SSID_INT_MAX = 9999;
 
     @Mock Context mContext;
     @Mock BackupManagerProxy mBackupManagerProxy;
@@ -67,6 +70,8 @@
                             TEST_DEFAULT_2G_CHANNEL_LIST);
         resources.setString(R.string.wifi_tether_configure_ssid_default,
                             TEST_DEFAULT_AP_SSID);
+        resources.setString(R.string.wifi_localhotspot_configure_ssid_default,
+                            TEST_DEFAULT_HOTSPOT_SSID);
         when(mContext.getResources()).thenReturn(resources);
     }
 
@@ -105,8 +110,12 @@
         assertEquals(config1.apChannel, config2.apChannel);
     }
 
-    private void verifyDefaultApConfig(WifiConfiguration config) {
-        assertEquals(TEST_DEFAULT_AP_SSID, config.SSID);
+    private void verifyDefaultApConfig(WifiConfiguration config, String expectedSsid) {
+        String[] splitSsid = config.SSID.split("_");
+        assertEquals(2, splitSsid.length);
+        assertEquals(expectedSsid, splitSsid[0]);
+        int randomPortion = Integer.parseInt(splitSsid[1]);
+        assertTrue(randomPortion >= RAND_SSID_INT_MIN && randomPortion <= RAND_SSID_INT_MAX);
         assertTrue(config.allowedKeyManagement.get(KeyMgmt.WPA2_PSK));
     }
 
@@ -118,7 +127,7 @@
     public void initWithDefaultConfiguration() throws Exception {
         WifiApConfigStore store = new WifiApConfigStore(
                 mContext, mBackupManagerProxy, mApConfigFile.getPath());
-        verifyDefaultApConfig(store.getApConfiguration());
+        verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
     }
 
     /**
@@ -159,7 +168,7 @@
         verifyApConfig(expectedConfig, store.getApConfiguration());
 
         store.setApConfiguration(null);
-        verifyDefaultApConfig(store.getApConfiguration());
+        verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
         verify(mBackupManagerProxy).notifyDataChanged();
     }
 
@@ -171,7 +180,7 @@
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
                 mContext, mBackupManagerProxy, mApConfigFile.getPath());
-        verifyDefaultApConfig(store.getApConfiguration());
+        verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
         WifiConfiguration expectedConfig = setupApConfig(
@@ -184,4 +193,16 @@
         verifyApConfig(expectedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy).notifyDataChanged();
     }
+
+    /**
+     * Verify a proper local only hotspot config is generated when called properly with the valid
+     * context.
+     */
+    @Test
+    public void generateLocalOnlyHotspotConfigIsValid() {
+        WifiConfiguration config = WifiApConfigStore.generateLocalOnlyHotspotConfig(mContext);
+        verifyDefaultApConfig(config, TEST_DEFAULT_HOTSPOT_SSID);
+        // The LOHS config should also have a specific network id set - check that as well.
+        assertEquals(WifiConfiguration.LOCAL_ONLY_NETWORK_ID, config.networkId);
+    }
 }
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..58b8d39
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
@@ -0,0 +1,791 @@
+/*
+ * 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.os.Process;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.net.IpConfigStore;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+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 {
+
+    @Mock WifiPermissionsUtil mWifiPermissionsUtil;
+    private WifiBackupRestore mWifiBackupRestore;
+    private boolean mCheckDump = true;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        mWifiBackupRestore = new WifiBackupRestore(mWifiPermissionsUtil);
+        // 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 null network list is serialized correctly.
+     */
+    @Test
+    public void testNullNetworkListBackup() {
+        byte[] backupData = mWifiBackupRestore.retrieveBackupDataFromConfigurations(null);
+        assertTrue(backupData != null);
+        assertEquals(backupData.length, 0);
+        // No valid data to check in dump.
+        mCheckDump = false;
+    }
+
+    /**
+     * 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 multiple networks of different types except the non system app created ones are
+     * serialized and deserialized correctly.
+     */
+    @Test
+    public void testMultipleNetworksSystemAppBackupRestore() {
+        int systemAppUid = Process.SYSTEM_UID;
+        int nonSystemAppUid = Process.FIRST_APPLICATION_UID + 556;
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(eq(systemAppUid)))
+                .thenReturn(true);
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(eq(nonSystemAppUid)))
+                .thenReturn(false);
+
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        List<WifiConfiguration> expectedConfigurations = new ArrayList<>();
+
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        wepNetwork.creatorUid = systemAppUid;
+        configurations.add(wepNetwork);
+        expectedConfigurations.add(wepNetwork);
+
+        // These should not be in |expectedConfigurations|.
+        WifiConfiguration nonSystemAppWepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        nonSystemAppWepNetwork.creatorUid = nonSystemAppUid;
+        configurations.add(nonSystemAppWepNetwork);
+
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.creatorUid = systemAppUid;
+        configurations.add(pskNetwork);
+        expectedConfigurations.add(pskNetwork);
+
+        // These should not be in |expectedConfigurations|.
+        WifiConfiguration nonSystemAppPskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        nonSystemAppPskNetwork.creatorUid = nonSystemAppUid;
+        configurations.add(nonSystemAppPskNetwork);
+
+        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 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 multiple networks of different types except the non system app created ones are
+     * serialized and deserialized correctly from old backups.
+     */
+    @Test
+    public void testMultipleNetworksSystemAppSupplicantBackupRestore() {
+        List<WifiConfiguration> configurations = new ArrayList<>();
+        List<WifiConfiguration> expectedConfigurations = new ArrayList<>();
+
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        configurations.add(wepNetwork);
+        expectedConfigurations.add(wepNetwork);
+
+        // These should not be in |expectedConfigurations|.
+        WifiConfiguration nonSystemAppWepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        nonSystemAppWepNetwork.creatorUid = Process.FIRST_APPLICATION_UID;
+        configurations.add(nonSystemAppWepNetwork);
+
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        configurations.add(pskNetwork);
+        expectedConfigurations.add(pskNetwork);
+
+        // These should not be in |expectedConfigurations|.
+        WifiConfiguration nonSystemAppPskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        nonSystemAppPskNetwork.creatorUid = Process.FIRST_APPLICATION_UID + 1;
+        configurations.add(nonSystemAppPskNetwork);
+
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        configurations.add(openNetwork);
+        expectedConfigurations.add(openNetwork);
+
+        byte[] supplicantData = createWpaSupplicantConfBackupData(configurations);
+        byte[] ipConfigData = createIpConfBackupData(configurations);
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, ipConfigData);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                expectedConfigurations, retrievedConfigurations);
+    }
+
+    /**
+     * Verifying that backup data containing some unknown keys is properly restored.
+     * The backup data used here is a PII masked version of a backup data seen in a reported bug.
+     */
+    @Test
+    public void testSingleNetworkSupplicantBackupRestoreWithUnknownEAPKey() {
+        String backupSupplicantConfNetworkBlock = "network={\n"
+                + "ssid=" + WifiConfigurationTestUtil.TEST_SSID + "\n"
+                + "psk=" + WifiConfigurationTestUtil.TEST_PSK + "\n"
+                + "key_mgmt=WPA-PSK WPA-PSK-SHA256\n"
+                + "priority=18\n"
+                + "id_str=\"%7B%22creatorUid%22%3A%221000%22%2C%22configKey"
+                + "%22%3A%22%5C%22BLAH%5C%22WPA_PSK%22%7D\"\n"
+                + "eapRetryCount=6\n";
+        byte[] supplicantData = backupSupplicantConfNetworkBlock.getBytes();
+        List<WifiConfiguration> retrievedConfigurations =
+                mWifiBackupRestore.retrieveConfigurationsFromSupplicantBackupData(
+                        supplicantData, null);
+
+        final WifiConfiguration expectedConfiguration = new WifiConfiguration();
+        expectedConfiguration.SSID = WifiConfigurationTestUtil.TEST_SSID;
+        expectedConfiguration.preSharedKey = WifiConfigurationTestUtil.TEST_PSK;
+        expectedConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+
+        ArrayList<WifiConfiguration> expectedConfigurations = new ArrayList<WifiConfiguration>() {{
+                add(expectedConfiguration);
+            }};
+        WifiConfigurationTestUtil.assertConfigurationsEqualForBackup(
+                expectedConfigurations, 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(SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY, configuration.configKey());
+        extras.put(SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID,
+                Integer.toString(configuration.creatorUid));
+        String idString = "\"" + SupplicantStaNetworkHal.createNetworkExtra(extras) + "\"";
+        if (idString != null) {
+            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/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index fab06bd..9fa67a0 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -11,1627 +11,3930 @@
  * 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;
 
-import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.admin.DeviceAdminInfo;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.net.wifi.FakeKeys;
+import android.net.IpConfiguration;
+import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.AuthAlgorithm;
-import android.net.wifi.WifiConfiguration.GroupCipher;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiConfiguration.PairwiseCipher;
-import android.net.wifi.WifiConfiguration.Protocol;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
-import android.net.wifi.WifiEnterpriseConfig.Eap;
-import android.net.wifi.WifiEnterpriseConfig.Phase2;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiSsid;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.support.test.InstrumentationRegistry;
+import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
+import android.util.Pair;
 
-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;
+import com.android.internal.R;
+import com.android.server.wifi.WifiConfigStoreLegacy.WifiConfigStoreDataLegacy;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
+import org.junit.After;
 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.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.math.BigInteger;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayDeque;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Random;
 import java.util.Set;
-import java.util.TreeMap;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiConfigManager}.
  */
 @SmallTest
 public class WifiConfigManagerTest {
-    private static final List<WifiConfiguration> CONFIGS = Arrays.asList(
-            WifiConfigurationTestUtil.generateWifiConfig(
-                    0, 1000000, "\"red\"", true, true, null, null),
-            WifiConfigurationTestUtil.generateWifiConfig(
-                    1, 1000001, "\"green\"", true, true, "example.com", "Green"),
-            WifiConfigurationTestUtil.generateWifiConfig(
-                    2, 1100000, "\"blue\"", false, true, "example.org", "Blue"),
-            WifiConfigurationTestUtil.generateWifiConfig(
-                    3, 1200000, "\"cyan\"", false, true, null, null));
 
-    private static final int[] USER_IDS = {0, 10, 11};
-    private static final int MANAGED_PROFILE_USER_ID = 12;
-    private static final int MANAGED_PROFILE_PARENT_USER_ID = 0;
-    private static final SparseArray<List<UserInfo>> USER_PROFILES = new SparseArray<>();
-    static {
-        USER_PROFILES.put(0, Arrays.asList(new UserInfo(0, "Owner", 0),
-                new UserInfo(12, "Managed Profile", 0)));
-        USER_PROFILES.put(10, Arrays.asList(new UserInfo(10, "Alice", 0)));
-        USER_PROFILES.put(11, Arrays.asList(new UserInfo(11, "Bob", 0)));
-    }
+    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 = WifiConfigurationTestUtil.TEST_UID;
+    private static final int TEST_NO_PERM_UID = 7;
+    private static final int TEST_UPDATE_UID = 4;
+    private static final int TEST_SYSUI_UID = 56;
+    private static final int TEST_DEFAULT_USER = UserHandle.USER_SYSTEM;
+    private static final int TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN = 5;
+    private static final Integer[] TEST_FREQ_LIST = {2400, 2450, 5150, 5175, 5650};
+    private static final String TEST_CREATOR_NAME = "com.wificonfigmanager.creator";
+    private static final String TEST_UPDATE_NAME = "com.wificonfigmanager.update";
+    private static final String TEST_NO_PERM_NAME = "com.wificonfigmanager.noperm";
+    private static final String TEST_DEFAULT_GW_MAC_ADDRESS = "0f:67:ad:ef:09:34";
+    private static final String TEST_STATIC_PROXY_HOST_1 = "192.168.48.1";
+    private static final int    TEST_STATIC_PROXY_PORT_1 = 8000;
+    private static final String TEST_STATIC_PROXY_EXCLUSION_LIST_1 = "";
+    private static final String TEST_PAC_PROXY_LOCATION_1 = "http://bleh";
+    private static final String TEST_STATIC_PROXY_HOST_2 = "192.168.1.1";
+    private static final int    TEST_STATIC_PROXY_PORT_2 = 3000;
+    private static final String TEST_STATIC_PROXY_EXCLUSION_LIST_2 = "";
+    private static final String TEST_PAC_PROXY_LOCATION_2 = "http://blah";
 
-    private static final Map<Integer, List<WifiConfiguration>> VISIBLE_CONFIGS = new HashMap<>();
-    static {
-        for (int userId : USER_IDS) {
-            List<WifiConfiguration> configs = new ArrayList<>();
-            for (int i = 0; i < CONFIGS.size(); ++i) {
-                if (WifiConfigurationUtil.isVisibleToAnyProfile(CONFIGS.get(i),
-                        USER_PROFILES.get(userId))) {
-                    configs.add(CONFIGS.get(i));
-                }
-            }
-            VISIBLE_CONFIGS.put(userId, configs);
-        }
-    }
-
-    /**
-     * Set of WifiConfigs for HasEverConnected tests.
-     */
-    private static final int HAS_EVER_CONNECTED_USER = 20;
-    private static final WifiConfiguration BASE_HAS_EVER_CONNECTED_CONFIG =
-            WifiConfigurationTestUtil.generateWifiConfig(
-                    0, HAS_EVER_CONNECTED_USER, "testHasEverConnected", false, true, null, null, 0);
-
-    public static final String TAG = "WifiConfigManagerTest";
     @Mock private Context mContext;
-    @Mock private WifiNative mWifiNative;
-    @Mock private FrameworkFacade mFrameworkFacade;
-    @Mock private UserManager mUserManager;
-    @Mock private DelayedDiskWrite mWriter;
-    @Mock private PasspointManagementObjectManager mMOManager;
     @Mock private Clock mClock;
+    @Mock private UserManager mUserManager;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private WifiKeyStore mWifiKeyStore;
+    @Mock private WifiConfigStore mWifiConfigStore;
+    @Mock private WifiConfigStoreLegacy mWifiConfigStoreLegacy;
+    @Mock private PackageManager mPackageManager;
+    @Mock private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+    @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
+    @Mock private WifiPermissionsWrapper mWifiPermissionsWrapper;
+    @Mock private NetworkListStoreData mNetworkListStoreData;
+    @Mock private DeletedEphemeralSsidsStoreData mDeletedEphemeralSsidsStoreData;
+    @Mock private WifiConfigManager.OnSavedNetworkUpdateListener mWcmListener;
+
+    private MockResources mResources;
+    private InOrder mContextConfigStoreMockOrder;
+    private InOrder mNetworkListStoreDataMockOrder;
     private WifiConfigManager mWifiConfigManager;
-    private ConfigurationMap mConfiguredNetworks;
-    public byte[] mNetworkHistoryBytes;
-    private MockKeyStore mMockKeyStore;
-    private KeyStore mKeyStore;
+    private boolean mStoreReadTriggered = false;
 
     /**
-     * Called before each test
+     * Setup the mocks and an instance of WifiConfigManager before each test.
      */
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        final Context realContext = InstrumentationRegistry.getContext();
-        when(mContext.getPackageName()).thenReturn(realContext.getPackageName());
-        when(mContext.getResources()).thenReturn(realContext.getResources());
-        when(mContext.getPackageManager()).thenReturn(realContext.getPackageManager());
+        // 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);
+        mNetworkListStoreDataMockOrder = inOrder(mNetworkListStoreData);
 
-        when(mUserManager.getProfiles(UserHandle.USER_SYSTEM))
-                .thenReturn(USER_PROFILES.get(UserHandle.USER_SYSTEM));
+        // Set up the package name stuff & permission override.
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        mResources = new MockResources();
+        mResources.setBoolean(
+                R.bool.config_wifi_only_link_same_credential_configurations, true);
+        mResources.setInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
+                TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN);
+        when(mContext.getResources()).thenReturn(mResources);
 
-        for (int userId : USER_IDS) {
-            when(mUserManager.getProfiles(userId)).thenReturn(USER_PROFILES.get(userId));
-        }
+        // Setup UserManager profiles for the default user.
+        setupUserProfiles(TEST_DEFAULT_USER);
 
-        mMockKeyStore = new MockKeyStore();
-
-        mWifiConfigManager = new WifiConfigManager(mContext, mWifiNative, mFrameworkFacade, mClock,
-                mUserManager, mMockKeyStore.createMock());
-
-        final Field configuredNetworksField =
-                WifiConfigManager.class.getDeclaredField("mConfiguredNetworks");
-        configuredNetworksField.setAccessible(true);
-        mConfiguredNetworks = (ConfigurationMap) configuredNetworksField.get(mWifiConfigManager);
-
-        // Intercept writes to networkHistory.txt.
         doAnswer(new AnswerWithArguments() {
-            public void answer(String filePath, DelayedDiskWrite.Writer writer) throws Exception {
-                final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-                final DataOutputStream stream = new DataOutputStream(buffer);
-                writer.onWriteCalled(stream);
-                stream.close();
-                mNetworkHistoryBytes = buffer.toByteArray();
-            }}).when(mWriter).write(anyString(), (DelayedDiskWrite.Writer) anyObject());
-        final Field writerField = WifiConfigManager.class.getDeclaredField("mWriter");
-        writerField.setAccessible(true);
-        writerField.set(mWifiConfigManager, mWriter);
-        final Field networkHistoryField =
-                WifiConfigManager.class.getDeclaredField("mWifiNetworkHistory");
-        networkHistoryField.setAccessible(true);
-        WifiNetworkHistory wifiNetworkHistory =
-                (WifiNetworkHistory) networkHistoryField.get(mWifiConfigManager);
-        final Field networkHistoryWriterField =
-                WifiNetworkHistory.class.getDeclaredField("mWriter");
-        networkHistoryWriterField.setAccessible(true);
-        networkHistoryWriterField.set(wifiNetworkHistory, mWriter);
+            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;
+                } else if (uid == TEST_SYSUI_UID) {
+                    return WifiConfigManager.SYSUI_PACKAGE_NAME;
+                } else if (uid == TEST_NO_PERM_UID) {
+                    return TEST_NO_PERM_NAME;
+                }
+                fail("Unexpected UID: " + uid);
+                return "";
+            }
+        }).when(mPackageManager).getNameForUid(anyInt());
+        doAnswer(new AnswerWithArguments() {
+            public int answer(String packageName, int flags, int userId) throws Exception {
+                if (packageName.equals(WifiConfigManager.SYSUI_PACKAGE_NAME)) {
+                    return TEST_SYSUI_UID;
+                } else {
+                    return 0;
+                }
+            }
+        }).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt(), anyInt());
 
-        when(mMOManager.isEnabled()).thenReturn(true);
-        final Field moManagerField = WifiConfigManager.class.getDeclaredField("mMOManager");
-        moManagerField.setAccessible(true);
-        moManagerField.set(mWifiConfigManager, mMOManager);
-    }
-
-    private void switchUser(int newUserId) {
-        when(mUserManager.getProfiles(newUserId))
-                .thenReturn(USER_PROFILES.get(newUserId));
-        mWifiConfigManager.handleUserSwitch(newUserId);
-    }
-
-    private void switchUserToCreatorOrParentOf(WifiConfiguration config) {
-        final int creatorUserId = UserHandle.getUserId(config.creatorUid);
-        if (creatorUserId == MANAGED_PROFILE_USER_ID) {
-            switchUser(MANAGED_PROFILE_PARENT_USER_ID);
-        } else {
-            switchUser(creatorUserId);
-        }
-    }
-
-    private void addNetworks() throws Exception {
-        for (int i = 0; i < CONFIGS.size(); ++i) {
-            assertEquals(i, CONFIGS.get(i).networkId);
-            addNetwork(CONFIGS.get(i));
-        }
-    }
-
-    private void addNetwork(WifiConfiguration config) throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
-
-        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString())).thenReturn(true);
-        when(mWifiNative.setNetworkExtra(anyInt(), anyString(), (Map<String, String>) anyObject()))
+        when(mWifiKeyStore
+                .updateNetworkKeys(any(WifiConfiguration.class), any()))
                 .thenReturn(true);
 
-        switchUserToCreatorOrParentOf(config);
-        final WifiConfiguration configCopy = new WifiConfiguration(config);
-        int networkId = config.networkId;
-        config.networkId = -1;
-        when(mWifiNative.addNetwork()).thenReturn(networkId);
-        when(mWifiNative.getNetworkVariable(networkId, WifiConfiguration.ssidVarName))
-                .thenReturn(encodeConfigSSID(config));
-        mWifiConfigManager.saveNetwork(config, configCopy.creatorUid);
+        when(mWifiConfigStore.areStoresPresent()).thenReturn(true);
+        setupStoreDataForRead(new ArrayList<WifiConfiguration>(),
+                new ArrayList<WifiConfiguration>(), new HashSet<String>());
 
-        switchUser(originalUserId);
-    }
-
-    private String encodeConfigSSID(WifiConfiguration config) throws Exception {
-        return new BigInteger(1, config.SSID.substring(1, config.SSID.length() - 1)
-                .getBytes("UTF-8")).toString(16);
+        when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(anyInt(), anyInt()))
+                .thenReturn(false);
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        when(mWifiPermissionsWrapper.getDevicePolicyManagerInternal())
+                .thenReturn(mDevicePolicyManagerInternal);
+        createWifiConfigManager();
+        mWifiConfigManager.setOnSavedNetworkUpdateListener(mWcmListener);
     }
 
     /**
-     * Verifies that getConfiguredNetworksSize() returns the number of network configurations
-     * visible to the current user.
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Verifies that network retrieval via
+     * {@link WifiConfigManager#getConfiguredNetworks()} and
+     * {@link WifiConfigManager#getConfiguredNetworksWithPasswords()} works even if we have not
+     * yet loaded data from store.
      */
     @Test
-    public void testGetConfiguredNetworksSize() throws Exception {
-        addNetworks();
-        for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) {
-            switchUser(entry.getKey());
-            assertEquals(entry.getValue().size(), mWifiConfigManager.getConfiguredNetworksSize());
-        }
-    }
-
-    private void verifyNetworkConfig(WifiConfiguration expectedConfig,
-            WifiConfiguration actualConfig) {
-        assertNotNull(actualConfig);
-        assertEquals(expectedConfig.SSID, actualConfig.SSID);
-        assertEquals(expectedConfig.FQDN, actualConfig.FQDN);
-        assertEquals(expectedConfig.providerFriendlyName,
-                actualConfig.providerFriendlyName);
-        assertEquals(expectedConfig.configKey(), actualConfig.configKey(false));
-    }
-
-    private void verifyNetworkConfigs(Collection<WifiConfiguration> expectedConfigs,
-            Collection<WifiConfiguration> actualConfigs) {
-        assertEquals(expectedConfigs.size(), actualConfigs.size());
-        for (WifiConfiguration expectedConfig : expectedConfigs) {
-            WifiConfiguration actualConfig = null;
-            // Find the network configuration to test (assume that |actualConfigs| contains them in
-            // undefined order).
-            for (final WifiConfiguration candidate : actualConfigs) {
-                if (candidate.networkId == expectedConfig.networkId) {
-                    actualConfig = candidate;
-                    break;
-                }
-            }
-            verifyNetworkConfig(expectedConfig, actualConfig);
-        }
+    public void testGetConfiguredNetworksBeforeLoadFromStore() {
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+        assertTrue(mWifiConfigManager.getConfiguredNetworksWithPasswords().isEmpty());
     }
 
     /**
-     * Verifies that getConfiguredNetworksSize() returns the network configurations visible to the
-     * current user.
+     * Verifies that network addition via
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} fails if we have not
+     * yet loaded data from store.
      */
     @Test
-    public void testGetConfiguredNetworks() throws Exception {
-        addNetworks();
-        for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) {
-            switchUser(entry.getKey());
-            verifyNetworkConfigs(entry.getValue(), mWifiConfigManager.getSavedNetworks());
-        }
+    public void testAddNetworkBeforeLoadFromStore() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        assertFalse(
+                mWifiConfigManager.addOrUpdateNetwork(openNetwork, TEST_CREATOR_UID).isSuccess());
     }
 
     /**
-     * Verifies that getPrivilegedConfiguredNetworks() returns the network configurations visible to
-     * the current user.
+     * Verifies the addition of a single network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
      */
     @Test
-    public void testGetPrivilegedConfiguredNetworks() throws Exception {
-        addNetworks();
-        for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) {
-            switchUser(entry.getKey());
-            verifyNetworkConfigs(entry.getValue(),
-                    mWifiConfigManager.getPrivilegedSavedNetworks());
-        }
+    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);
+        // Ensure that the newly added network is disabled.
+        assertEquals(WifiConfiguration.Status.DISABLED, retrievedNetworks.get(0).status);
     }
 
     /**
-     * Verifies that getWifiConfiguration(int netId) can be used to access network configurations
-     * visible to the current user only.
+     * Verifies the modification of a single network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
      */
     @Test
-    public void testGetWifiConfigurationByNetworkId() throws Exception {
-        addNetworks();
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            for (WifiConfiguration expectedConfig: CONFIGS) {
-                final WifiConfiguration actualConfig =
-                        mWifiConfigManager.getWifiConfiguration(expectedConfig.networkId);
-                if (WifiConfigurationUtil.isVisibleToAnyProfile(expectedConfig,
-                        USER_PROFILES.get(userId))) {
-                    verifyNetworkConfig(expectedConfig, actualConfig);
-                } else {
-                    assertNull(actualConfig);
-                }
-            }
-        }
+    public void testUpdateSingleOpenNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
+
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+        verify(mWcmListener).onSavedNetworkAdded(openNetwork.networkId);
+        reset(mWcmListener);
+
+        // 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);
+        verify(mWcmListener).onSavedNetworkUpdated(openNetwork.networkId);
     }
 
     /**
-     * Verifies that getWifiConfiguration(String key) can be used to access network configurations
-     * visible to the current user only.
+     * Verifies the addition of a single ephemeral network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} and verifies that
+     * the {@link WifiConfigManager#getSavedNetworks()} does not return this network.
      */
     @Test
-    public void testGetWifiConfigurationByConfigKey() throws Exception {
-        addNetworks();
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            for (WifiConfiguration expectedConfig: CONFIGS) {
-                final WifiConfiguration actualConfig =
-                        mWifiConfigManager.getWifiConfiguration(expectedConfig.configKey());
-                if (WifiConfigurationUtil.isVisibleToAnyProfile(expectedConfig,
-                        USER_PROFILES.get(userId))) {
-                    verifyNetworkConfig(expectedConfig, actualConfig);
-                } else {
-                    assertNull(actualConfig);
-                }
-            }
-        }
+    public void testAddSingleEphemeralNetwork() throws Exception {
+        WifiConfiguration ephemeralNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        ephemeralNetwork.ephemeral = true;
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(ephemeralNetwork);
+
+        verifyAddEphemeralNetworkToWifiConfigManager(ephemeralNetwork);
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+
+        // Ensure that this is not returned in the saved network list.
+        assertTrue(mWifiConfigManager.getSavedNetworks().isEmpty());
+        verify(mWcmListener, never()).onSavedNetworkAdded(ephemeralNetwork.networkId);
     }
 
     /**
-     * Verifies that enableAllNetworks() enables all temporarily disabled network configurations
-     * visible to the current user.
+     * Verifies the addition of 2 networks (1 normal and 1 ephemeral) using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} and ensures that
+     * the ephemeral network configuration is not persisted in config store.
      */
     @Test
-    public void testEnableAllNetworks() throws Exception {
-        addNetworks();
-        for (int userId : USER_IDS) {
-            switchUser(userId);
+    public void testAddMultipleNetworksAndEnsureEphemeralNetworkNotPersisted() {
+        WifiConfiguration ephemeralNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        ephemeralNetwork.ephemeral = true;
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
 
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
-                final WifiConfiguration.NetworkSelectionStatus status =
-                        config.getNetworkSelectionStatus();
-                status.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
-                        .NETWORK_SELECTION_TEMPORARY_DISABLED);
-                status.setNetworkSelectionDisableReason(
-                        WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE);
-                status.setDisableTime(System.currentTimeMillis() - 60 * 60 * 1000);
-            }
+        assertTrue(addNetworkToWifiConfigManager(ephemeralNetwork).isSuccess());
+        assertTrue(addNetworkToWifiConfigManager(openNetwork).isSuccess());
 
-            mWifiConfigManager.enableAllNetworks();
-
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
-                assertEquals(WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                        USER_PROFILES.get(userId)),
-                        config.getNetworkSelectionStatus().isNetworkEnabled());
-            }
-        }
+        // The open network addition should trigger a store write.
+        Pair<List<WifiConfiguration>, List<WifiConfiguration>> networkListStoreData =
+                captureWriteNetworksListStoreData();
+        List<WifiConfiguration> networkList = new ArrayList<>();
+        networkList.addAll(networkListStoreData.first);
+        networkList.addAll(networkListStoreData.second);
+        assertFalse(isNetworkInConfigStoreData(ephemeralNetwork, networkList));
+        assertTrue(isNetworkInConfigStoreData(openNetwork, networkList));
     }
 
     /**
-     * Verifies that selectNetwork() disables all network configurations visible to the current user
-     * except the selected one.
+     * Verifies that the modification of a single open network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} with a UID which
+     * has no permission to modify the network fails.
      */
     @Test
-    public void testSelectNetwork() throws Exception {
-        addNetworks();
+    public void testUpdateSingleOpenNetworkFailedDueToPermissionDenied() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
 
-        for (int userId : USER_IDS) {
-            switchUser(userId);
+        verifyAddNetworkToWifiConfigManager(openNetwork);
 
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
-                // Enable all network configurations.
-                for (WifiConfiguration config2 : mConfiguredNetworks.valuesForAllUsers()) {
-                    config2.status = WifiConfiguration.Status.ENABLED;
-                }
+        // Now change BSSID of the network.
+        assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
 
-                // Try to select a network configuration.
-                reset(mWifiNative);
-                when(mWifiNative.selectNetwork(config.networkId)).thenReturn(true);
-                final boolean success =
-                        mWifiConfigManager.selectNetwork(config, false, config.creatorUid);
-                if (!WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                        USER_PROFILES.get(userId))) {
-                    // If the network configuration is not visible to the current user, verify that
-                    // nothing changed.
-                    assertFalse(success);
-                    verify(mWifiNative, never()).selectNetwork(anyInt());
-                    verify(mWifiNative, never()).enableNetwork(anyInt());
-                    for (WifiConfiguration config2 : mConfiguredNetworks.valuesForAllUsers()) {
-                        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.
-                    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);
-                        }
-                    }
-                }
-            }
-        }
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+
+        // Update the same configuration and ensure that the operation failed.
+        NetworkUpdateResult result = updateNetworkToWifiConfigManager(openNetwork);
+        assertTrue(result.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID);
     }
 
     /**
-     * Verifies that saveNetwork() correctly stores a network configuration in wpa_supplicant
-     * variables and the networkHistory.txt file.
-     * TODO: Test all variables. Currently, only the following variables are tested:
-     * - In the wpa_supplicant: "ssid", "id_str"
-     * - In networkHistory.txt: "CONFIG", "CREATOR_UID_KEY", "SHARED"
-     */
-    private void verifySaveNetwork(int network) throws Exception {
-        // Switch to the correct user.
-        switchUserToCreatorOrParentOf(CONFIGS.get(network));
-
-        // Set up wpa_supplicant.
-        when(mWifiNative.addNetwork()).thenReturn(0);
-        when(mWifiNative.setNetworkVariable(eq(network), anyString(), anyString()))
-                .thenReturn(true);
-        when(mWifiNative.setNetworkExtra(eq(network), anyString(),
-                (Map<String, String>) anyObject())).thenReturn(true);
-        when(mWifiNative.getNetworkVariable(network, WifiConfiguration.ssidVarName))
-                .thenReturn(encodeConfigSSID(CONFIGS.get(network)));
-        when(mWifiNative.getNetworkVariable(network, WifiConfiguration.pmfVarName))
-                .thenReturn("");
-
-        // Store a network configuration.
-        mWifiConfigManager.saveNetwork(CONFIGS.get(network), CONFIGS.get(network).creatorUid);
-
-        // Verify that wpa_supplicant variables were written correctly for the network
-        // 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(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY, CONFIGS.get(network).configKey());
-        metadata.put(WifiConfigStore.ID_STRING_KEY_CREATOR_UID,
-                Integer.toString(CONFIGS.get(network).creatorUid));
-        verify(mWifiNative).setNetworkExtra(network, WifiConfigStore.ID_STRING_VAR_NAME,
-                metadata);
-
-        // Verify that an attempt to read back the requirePMF variable was made.
-        verify(mWifiNative).getNetworkVariable(network, WifiConfiguration.pmfVarName);
-
-        // Verify that no wpa_supplicant variables were read or written for any other network
-        // configurations.
-        verify(mWifiNative, never()).setNetworkExtra(intThat(not(network)), anyString(),
-                (Map<String, String>) anyObject());
-        verify(mWifiNative, never()).setNetworkVariable(intThat(not(network)), anyString(),
-                anyString());
-        verify(mWifiNative, never()).getNetworkVariable(intThat(not(network)), anyString());
-
-        // Parse networkHistory.txt.
-        assertNotNull(mNetworkHistoryBytes);
-        final DataInputStream stream =
-                new DataInputStream(new ByteArrayInputStream(mNetworkHistoryBytes));
-        List<String> keys = new ArrayList<>();
-        List<String> values = new ArrayList<>();
-        try {
-            while (true) {
-                final String[] tokens = stream.readUTF().split(":", 2);
-                if (tokens.length == 2) {
-                    keys.add(tokens[0].trim());
-                    values.add(tokens[1].trim());
-                }
-            }
-        } catch (EOFException e) {
-            // Ignore. This is expected.
-        }
-
-        // Verify that a networkHistory.txt entry was written correctly for the network
-        // configuration.
-        assertTrue(keys.size() >= 3);
-        assertEquals(WifiNetworkHistory.CONFIG_KEY, keys.get(0));
-        assertEquals(CONFIGS.get(network).configKey(), values.get(0));
-        final int creatorUidIndex = keys.indexOf(WifiNetworkHistory.CREATOR_UID_KEY);
-        assertTrue(creatorUidIndex != -1);
-        assertEquals(Integer.toString(CONFIGS.get(network).creatorUid),
-                values.get(creatorUidIndex));
-        final int sharedIndex = keys.indexOf(WifiNetworkHistory.SHARED_KEY);
-        assertTrue(sharedIndex != -1);
-        assertEquals(Boolean.toString(CONFIGS.get(network).shared), values.get(sharedIndex));
-
-        // Verify that no networkHistory.txt entries were written for any other network
-        // configurations.
-        final int lastConfigIndex = keys.lastIndexOf(WifiNetworkHistory.CONFIG_KEY);
-        assertEquals(0, lastConfigIndex);
-    }
-
-    /**
-     * Verifies that saveNetwork() correctly stores a regular network configuration.
+     * Verifies that the modification of a single open network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} with the creator UID
+     * should always succeed.
      */
     @Test
-    public void testSaveNetworkRegular() throws Exception {
-        verifySaveNetwork(0);
+    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);
+
+        // 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 that saveNetwork() correctly stores a HotSpot 2.0 network configuration.
+     * Verifies the addition of a single PSK network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} and verifies that
+     * {@link WifiConfigManager#getSavedNetworks()} masks the password.
      */
     @Test
-    public void testSaveNetworkHotspot20() throws Exception {
-        verifySaveNetwork(1);
+    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 that saveNetwork() correctly stores a private network configuration.
+     * Verifies the addition of a single WEP network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} and verifies that
+     * {@link WifiConfigManager#getSavedNetworks()} masks the password.
      */
     @Test
-    public void testSaveNetworkPrivate() throws Exception {
-        verifySaveNetwork(2);
+    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 that loadConfiguredNetworks() correctly reads data from the wpa_supplicant, the
-     * networkHistory.txt file and the MOManager, correlating the three sources based on the
-     * configKey and the FQDN for HotSpot 2.0 networks.
-     * TODO: Test all variables. Currently, only the following variables are tested:
-     * - In the wpa_supplicant: "ssid", "id_str"
-     * - In networkHistory.txt: "CONFIG", "CREATOR_UID_KEY", "SHARED"
+     * Verifies the modification of an IpConfiguration using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
      */
     @Test
-    public void testLoadConfiguredNetworks() throws Exception {
-        // Set up list of network configurations returned by wpa_supplicant.
-        final String header = "network id / ssid / bssid / flags";
-        String networks = header;
-        for (WifiConfiguration config : CONFIGS) {
-            networks += "\n" + Integer.toString(config.networkId) + "\t" + config.SSID + "\tany";
-        }
-        when(mWifiNative.listNetworks(anyInt())).thenReturn(header);
-        when(mWifiNative.listNetworks(-1)).thenReturn(networks);
+    public void testUpdateIpConfiguration() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(openNetwork);
 
-        // Set up variables returned by wpa_supplicant for the individual network configurations.
-        for (int i = 0; i < CONFIGS.size(); ++i) {
-            when(mWifiNative.getNetworkVariable(i, WifiConfiguration.ssidVarName))
-                .thenReturn(encodeConfigSSID(CONFIGS.get(i)));
-        }
-        // Legacy regular network configuration: No "id_str".
-        when(mWifiNative.getNetworkExtra(0, WifiConfigStore.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))
-            .thenReturn(null);
-        when(mWifiNative.getNetworkVariable(1, WifiConfigStore.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,
-                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))
-            .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,
-                Integer.toString(CONFIGS.get(3).creatorUid));
-        when(mWifiNative.getNetworkExtra(3, WifiConfigStore.ID_STRING_VAR_NAME))
-            .thenReturn(metadata);
+        verifyAddNetworkToWifiConfigManager(openNetwork);
 
-        // Set up networkHistory.txt file.
-        final File file = File.createTempFile("networkHistory.txt", null);
-        file.deleteOnExit();
+        // Now change BSSID of the network.
+        assertAndSetNetworkBSSID(openNetwork, TEST_BSSID);
 
-        Field wifiNetworkHistoryConfigFile =
-                WifiNetworkHistory.class.getDeclaredField("NETWORK_HISTORY_CONFIG_FILE");
-        wifiNetworkHistoryConfigFile.setAccessible(true);
-        wifiNetworkHistoryConfigFile.set(null, file.getAbsolutePath());
+        // Update the same configuration and ensure that the IP configuration change flags
+        // are not set.
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(openNetwork);
 
-        final DataOutputStream stream = new DataOutputStream(new FileOutputStream(file));
-        for (WifiConfiguration config : CONFIGS) {
-            stream.writeUTF(WifiNetworkHistory.CONFIG_KEY + ":  " + config.configKey() + '\n');
-            stream.writeUTF(WifiNetworkHistory.CREATOR_UID_KEY + ":  "
-                    + Integer.toString(config.creatorUid) + '\n');
-            stream.writeUTF(WifiNetworkHistory.SHARED_KEY + ":  "
-                    + Boolean.toString(config.shared) + '\n');
-        }
-        stream.close();
+        // Configure mock DevicePolicyManager to give Profile Owner permission so that we can modify
+        // proxy settings on a configuration
+        when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(anyInt(),
+                eq(DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))).thenReturn(true);
 
-        // Set up list of home service providers returned by MOManager.
-        final List<HomeSP> homeSPs = new ArrayList<HomeSP>();
-        for (WifiConfiguration config : CONFIGS) {
-            if (config.FQDN != null) {
-                homeSPs.add(new HomeSP(null, config.FQDN, new HashSet<Long>(),
-                        new HashSet<String>(),
-                        new HashSet<Long>(), new ArrayList<Long>(),
-                        config.providerFriendlyName, null,
-                        new Credential(0, 0, null, false, null, null),
-                        null, 0, null, null, null, 0));
-            }
-        }
-        when(mMOManager.loadAllSPs()).thenReturn(homeSPs);
+        // Change the IpConfiguration now and ensure that the IP configuration flags are set now.
+        assertAndSetNetworkIpConfiguration(
+                openNetwork,
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy());
+        verifyUpdateNetworkToWifiConfigManagerWithIpChange(openNetwork);
 
-        // Load network configurations.
-        mWifiConfigManager.loadConfiguredNetworks();
-
-        // Verify that network configurations were loaded and correlated correctly across the three
-        // sources.
-        verifyNetworkConfigs(CONFIGS, mConfiguredNetworks.valuesForAllUsers());
+        // Now verify that all the modifications have been effective.
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
     }
 
     /**
-     * Verifies that loadConfiguredNetworks() correctly handles duplicates when reading network
-     * configurations from the wpa_supplicant: The second configuration overwrites the first.
+     * Verifies the removal of a single network using
+     * {@link WifiConfigManager#removeNetwork(int)}
      */
     @Test
-    public void testLoadConfiguredNetworksEliminatesDuplicates() throws Exception {
-        final WifiConfiguration config = new WifiConfiguration(CONFIGS.get(0));
-        config.networkId = 1;
+    public void testRemoveSingleOpenNetwork() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
 
-        // Set up list of network configurations returned by wpa_supplicant. The two configurations
-        // are identical except for their network IDs.
-        final String header = "network id / ssid / bssid / flags";
-        final String networks =
-                header + "\n0\t" + config.SSID + "\tany\n1\t" + config.SSID + "\tany";
-        when(mWifiNative.listNetworks(anyInt())).thenReturn(header);
-        when(mWifiNative.listNetworks(-1)).thenReturn(networks);
+        verifyAddNetworkToWifiConfigManager(openNetwork);
+        verify(mWcmListener).onSavedNetworkAdded(openNetwork.networkId);
+        reset(mWcmListener);
 
-        // Set up variables returned by wpa_supplicant.
-        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,
-                Integer.toString(config.creatorUid));
-        when(mWifiNative.getNetworkExtra(anyInt(), eq(WifiConfigStore.ID_STRING_VAR_NAME)))
-            .thenReturn(metadata);
+        // Ensure that configured network list is not empty.
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().isEmpty());
 
-        // Load network configurations.
-        mWifiConfigManager.loadConfiguredNetworks();
-
-        // Verify that the second network configuration (network ID 1) overwrote the first (network
-        // ID 0).
-        verifyNetworkConfigs(Arrays.asList(config), mConfiguredNetworks.valuesForAllUsers());
+        verifyRemoveNetworkFromWifiConfigManager(openNetwork);
+        // Ensure that configured network list is empty now.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+        verify(mWcmListener).onSavedNetworkRemoved(openNetwork.networkId);
     }
 
     /**
-     * Verifies that handleUserSwitch() removes ephemeral network configurations, disables network
-     * configurations that should no longer be visible and enables network configurations that
-     * should become visible.
-     */
-    private void verifyHandleUserSwitch(int oldUserId, int newUserId,
-            boolean makeOneConfigEphemeral) throws Exception {
-        addNetworks();
-        switchUser(oldUserId);
-
-        reset(mWifiNative);
-        final Field lastSelectedConfigurationField =
-                WifiConfigManager.class.getDeclaredField("mLastSelectedConfiguration");
-        lastSelectedConfigurationField.setAccessible(true);
-        WifiConfiguration removedEphemeralConfig = null;
-        final Set<WifiConfiguration> oldUserOnlyConfigs = new HashSet<>();
-        final Set<WifiConfiguration> newUserOnlyConfigs = new HashSet<>();
-        final Set<WifiConfiguration> neitherUserConfigs = new HashSet<>();
-        final Collection<WifiConfiguration> oldConfigs = mConfiguredNetworks.valuesForAllUsers();
-        int expectedNumberOfConfigs = oldConfigs.size();
-        for (WifiConfiguration config : oldConfigs) {
-            if (WifiConfigurationUtil.isVisibleToAnyProfile(config, USER_PROFILES.get(oldUserId))) {
-                config.status = WifiConfiguration.Status.ENABLED;
-                if (WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                        USER_PROFILES.get(newUserId))) {
-                    if (makeOneConfigEphemeral && removedEphemeralConfig == null) {
-                        config.ephemeral = true;
-                        lastSelectedConfigurationField.set(mWifiConfigManager, config.configKey());
-                        removedEphemeralConfig = config;
-                    }
-                } else {
-                    oldUserOnlyConfigs.add(config);
-                }
-            } else {
-                config.status = WifiConfiguration.Status.DISABLED;
-                if (WifiConfigurationUtil.isVisibleToAnyProfile(config,
-                        USER_PROFILES.get(newUserId))) {
-                    newUserOnlyConfigs.add(config);
-                } else {
-                    neitherUserConfigs.add(config);
-                }
-            }
-        }
-
-        when(mWifiNative.disableNetwork(anyInt())).thenReturn(true);
-        when(mWifiNative.removeNetwork(anyInt())).thenReturn(true);
-
-        switchUser(newUserId);
-        if (makeOneConfigEphemeral) {
-            // Verify that the ephemeral network configuration was removed.
-            assertNotNull(removedEphemeralConfig);
-            assertNull(mConfiguredNetworks.getForAllUsers(removedEphemeralConfig.networkId));
-            assertNull(lastSelectedConfigurationField.get(mWifiConfigManager));
-            verify(mWifiNative).removeNetwork(removedEphemeralConfig.networkId);
-            --expectedNumberOfConfigs;
-        } else {
-            assertNull(removedEphemeralConfig);
-        }
-
-        // Verify that the other network configurations were revealed/hidden and enabled/disabled as
-        // appropriate.
-        final Collection<WifiConfiguration> newConfigs = mConfiguredNetworks.valuesForAllUsers();
-        assertEquals(expectedNumberOfConfigs, newConfigs.size());
-        for (WifiConfiguration config : newConfigs) {
-            if (oldUserOnlyConfigs.contains(config)) {
-                verify(mWifiNative).disableNetwork(config.networkId);
-                assertEquals(WifiConfiguration.Status.DISABLED, config.status);
-            } else {
-                verify(mWifiNative, never()).disableNetwork(config.networkId);
-                if (neitherUserConfigs.contains(config)) {
-                    assertEquals(WifiConfiguration.Status.DISABLED, config.status);
-                } else {
-                    // Only enabled in networkSelection.
-                    assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
-                }
-
-            }
-        }
-    }
-
-    /**
-     * Verifies that handleUserSwitch() behaves correctly when the user switch removes an ephemeral
-     * network configuration and reveals a private network configuration.
+     * Verifies the removal of an ephemeral network using
+     * {@link WifiConfigManager#removeNetwork(int)}
      */
     @Test
-    public void testHandleUserSwitchWithEphemeral() throws Exception {
-        verifyHandleUserSwitch(USER_IDS[2], USER_IDS[0], true);
+    public void testRemoveSingleEphemeralNetwork() throws Exception {
+        WifiConfiguration ephemeralNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        ephemeralNetwork.ephemeral = true;
+
+        verifyAddEphemeralNetworkToWifiConfigManager(ephemeralNetwork);
+        // Ensure that configured network list is not empty.
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+        verify(mWcmListener, never()).onSavedNetworkAdded(ephemeralNetwork.networkId);
+
+        verifyRemoveEphemeralNetworkFromWifiConfigManager(ephemeralNetwork);
+        // Ensure that configured network list is empty now.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+        verify(mWcmListener, never()).onSavedNetworkRemoved(ephemeralNetwork.networkId);
     }
 
     /**
-     * Verifies that handleUserSwitch() behaves correctly when the user switch hides a private
-     * network configuration.
+     * Verifies the removal of a Passpoint network using
+     * {@link WifiConfigManager#removeNetwork(int)}
      */
     @Test
-    public void testHandleUserSwitchWithoutEphemeral() throws Exception {
-        verifyHandleUserSwitch(USER_IDS[0], USER_IDS[2], false);
-    }
+    public void testRemoveSinglePasspointNetwork() throws Exception {
+        WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
 
-    @Test
-    public void testSaveLoadEapNetworks() {
-        testSaveLoadSingleEapNetwork("eap network", new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0}));
-        testSaveLoadSingleEapNetwork("eap network", new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT1, FakeKeys.CA_CERT0}));
+        verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork);
+        // Ensure that configured network list is not empty.
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+        verify(mWcmListener, never()).onSavedNetworkAdded(passpointNetwork.networkId);
 
-    }
-
-    private void testSaveLoadSingleEapNetwork(String ssid, EnterpriseConfig eapConfig) {
-        final HashMap<String, String> networkVariables = new HashMap<String, String>();
-        reset(mWifiNative);
-        when(mWifiNative.addNetwork()).thenReturn(0);
-        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString())).thenAnswer(
-                new AnswerWithArguments() {
-                    public boolean answer(int netId, String name, String value) {
-                        // Verify that no wpa_supplicant variables were written for any other
-                        // network configurations.
-                        assertEquals(netId, 0);
-                        networkVariables.put(name, value);
-                        return true;
-                    }
-                });
-        when(mWifiNative.getNetworkVariable(anyInt(), anyString())).then(
-                new AnswerWithArguments() {
-                    public String answer(int netId, String name) {
-                        // Verify that no wpa_supplicant variables were read for any other
-                        // network configurations.
-                        assertEquals(netId, 0);
-                        return networkVariables.get(name);
-                    }
-                });
-        when(mWifiNative.setNetworkExtra(eq(0), anyString(), (Map<String, String>) anyObject()))
-                .thenReturn(true);
-
-        WifiConfiguration config = new WifiConfiguration();
-        config.SSID = ssid;
-        config.creatorUid = Process.WIFI_UID;
-        config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
-        config.enterpriseConfig = eapConfig.enterpriseConfig;
-
-        // Store a network configuration.
-        mWifiConfigManager.saveNetwork(config, Process.WIFI_UID);
-
-        // Verify that wpa_supplicant variables were written correctly for the network
-        // configuration.
-        verify(mWifiNative).addNetwork();
-        assertEquals(eapConfig.eap,
-                unquote(networkVariables.get(WifiEnterpriseConfig.EAP_KEY)));
-        assertEquals(eapConfig.phase2,
-                unquote(networkVariables.get(WifiEnterpriseConfig.PHASE2_KEY)));
-        assertEquals(eapConfig.identity,
-                unquote(networkVariables.get(WifiEnterpriseConfig.IDENTITY_KEY)));
-        assertEquals(eapConfig.password,
-                unquote(networkVariables.get(WifiEnterpriseConfig.PASSWORD_KEY)));
-        assertSavedCaCerts(eapConfig,
-                unquote(networkVariables.get(WifiEnterpriseConfig.CA_CERT_KEY)));
-
-        // Prepare the scan result.
-        final String header = "network id / ssid / bssid / flags";
-        String networks = header + "\n" + Integer.toString(0) + "\t" + ssid + "\tany";
-        when(mWifiNative.listNetworks(anyInt())).thenReturn(header);
-        when(mWifiNative.listNetworks(-1)).thenReturn(networks);
-
-        // Load back the configuration.
-        mWifiConfigManager.loadConfiguredNetworks();
-        List<WifiConfiguration> configs = mWifiConfigManager.getSavedNetworks();
-        assertEquals(1, configs.size());
-        WifiConfiguration loadedConfig = configs.get(0);
-        assertEquals(ssid, unquote(loadedConfig.SSID));
-        BitSet keyMgmt = new BitSet();
-        keyMgmt.set(KeyMgmt.WPA_EAP);
-        assertEquals(keyMgmt, loadedConfig.allowedKeyManagement);
-        assertEquals(eapConfig.enterpriseConfig.getEapMethod(),
-                loadedConfig.enterpriseConfig.getEapMethod());
-        assertEquals(eapConfig.enterpriseConfig.getPhase2Method(),
-                loadedConfig.enterpriseConfig.getPhase2Method());
-        assertEquals(eapConfig.enterpriseConfig.getIdentity(),
-                loadedConfig.enterpriseConfig.getIdentity());
-        assertEquals(eapConfig.enterpriseConfig.getPassword(),
-                loadedConfig.enterpriseConfig.getPassword());
-        asserCaCertsAliasesMatch(eapConfig.caCerts,
-                loadedConfig.enterpriseConfig.getCaCertificateAliases());
-    }
-
-    private String unquote(String value) {
-        if (value == null) {
-            return null;
-        }
-        int length = value.length();
-        if ((length > 1) && (value.charAt(0) == '"')
-                && (value.charAt(length - 1) == '"')) {
-            return value.substring(1, length - 1);
-        } else {
-            return value;
-        }
-    }
-
-    private void asserCaCertsAliasesMatch(X509Certificate[] certs, String[] aliases) {
-        assertEquals(certs.length, aliases.length);
-        List<String> aliasList = new ArrayList<String>(Arrays.asList(aliases));
-        try {
-            for (int i = 0; i < certs.length; i++) {
-                byte[] certPem = Credentials.convertToPem(certs[i]);
-                boolean found = false;
-                for (int j = 0; j < aliasList.size(); j++) {
-                    byte[] keystoreCert = mMockKeyStore.getKeyBlob(Process.WIFI_UID,
-                            Credentials.CA_CERTIFICATE + aliasList.get(j)).blob;
-                    if (Arrays.equals(keystoreCert, certPem)) {
-                        found = true;
-                        aliasList.remove(j);
-                        break;
-                    }
-                }
-                assertTrue(found);
-            }
-        } catch (CertificateEncodingException | IOException e) {
-            fail("Cannot convert CA certificate to encoded form.");
-        }
-    }
-
-    private void assertSavedCaCerts(EnterpriseConfig eapConfig, String caCertVariable) {
-        ArrayList<String> aliases = new ArrayList<String>();
-        if (TextUtils.isEmpty(caCertVariable)) {
-            // Do nothing.
-        } else if (caCertVariable.startsWith(WifiEnterpriseConfig.CA_CERT_PREFIX)) {
-            aliases.add(caCertVariable.substring(WifiEnterpriseConfig.CA_CERT_PREFIX.length()));
-        } else if (caCertVariable.startsWith(WifiEnterpriseConfig.KEYSTORES_URI)) {
-            String[] encodedAliases = TextUtils.split(
-                    caCertVariable.substring(WifiEnterpriseConfig.KEYSTORES_URI.length()),
-                    WifiEnterpriseConfig.CA_CERT_ALIAS_DELIMITER);
-            for (String encodedAlias : encodedAliases) {
-                String alias = WifiEnterpriseConfig.decodeCaCertificateAlias(encodedAlias);
-                assertTrue(alias.startsWith(Credentials.CA_CERTIFICATE));
-                aliases.add(alias.substring(Credentials.CA_CERTIFICATE.length()));
-            }
-        } else {
-            fail("Unrecognized ca_cert variable: " + caCertVariable);
-        }
-        asserCaCertsAliasesMatch(eapConfig.caCerts, aliases.toArray(new String[aliases.size()]));
-    }
-
-    private static class EnterpriseConfig {
-        public String eap;
-        public String phase2;
-        public String identity;
-        public String password;
-        public X509Certificate[] caCerts;
-        public WifiEnterpriseConfig enterpriseConfig;
-
-        public EnterpriseConfig(int eapMethod) {
-            enterpriseConfig = new WifiEnterpriseConfig();
-            enterpriseConfig.setEapMethod(eapMethod);
-            eap = Eap.strings[eapMethod];
-        }
-        public EnterpriseConfig setPhase2(int phase2Method) {
-            enterpriseConfig.setPhase2Method(phase2Method);
-            phase2 = "auth=" + Phase2.strings[phase2Method];
-            return this;
-        }
-        public EnterpriseConfig setIdentity(String identity, String password) {
-            enterpriseConfig.setIdentity(identity);
-            enterpriseConfig.setPassword(password);
-            this.identity = identity;
-            this.password = password;
-            return this;
-        }
-        public EnterpriseConfig setCaCerts(X509Certificate[] certs) {
-            enterpriseConfig.setCaCertificates(certs);
-            caCerts = certs;
-            return this;
-        }
+        verifyRemovePasspointNetworkFromWifiConfigManager(passpointNetwork);
+        // Ensure that configured network list is empty now.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+        verify(mWcmListener, never()).onSavedNetworkRemoved(passpointNetwork.networkId);
     }
 
     /**
-     * Generates an array of unique random numbers below the specified maxValue.
-     * Values range from 0 to maxValue-1.
-     */
-    private static ArrayDeque<Integer> getUniqueRandomNumberValues(
-            int seed,
-            int maxValue,
-            int numValues) {
-        assertTrue(numValues <= maxValue);
-        Random rand = new Random(WifiTestUtil.getTestMethod().hashCode() + seed);
-        ArrayDeque<Integer> randomNumberList = new ArrayDeque<>();
-        for (int i = 0; i < numValues; i++) {
-            int num = rand.nextInt(maxValue);
-            while (randomNumberList.contains(num)) {
-                num = rand.nextInt(maxValue);
-            }
-            randomNumberList.push(num);
-        }
-        return randomNumberList;
-    }
-
-    /**
-     * Verifies that the networks in pnoNetworkList is sorted in the same order as the
-     * network in expectedNetworkIDOrder list.
-     */
-    private static void verifyPnoNetworkListOrder(
-            ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoNetworkList,
-            ArrayList<Integer> expectedNetworkIdOrder) throws Exception  {
-        int i = 0;
-        for (WifiScanner.PnoSettings.PnoNetwork pnoNetwork : pnoNetworkList) {
-            Log.i(TAG, "PNO Network List Index: " + i + ", networkID: " + pnoNetwork.networkId);
-            assertEquals("Expected network ID: " + pnoNetwork.networkId,
-                    pnoNetwork.networkId, expectedNetworkIdOrder.get(i++).intValue());
-        }
-    }
-
-    /**
-     * Verifies the retrieveDisconnectedPnoNetworkList API. The test verifies that the list
-     * returned from the API is sorted as expected.
+     * Verify that a Passpoint network that's added by an app with {@link #TEST_CREATOR_UID} can
+     * be removed by WiFi Service with {@link Process#WIFI_UID}.
+     *
+     * @throws Exception
      */
     @Test
-    public void testDisconnectedPnoNetworkListCreation() throws Exception {
-        addNetworks();
+    public void testRemovePasspointNetworkAddedByOther() throws Exception {
+        WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
 
-        Random rand = new Random(WifiTestUtil.getTestMethod().hashCode());
+        // Passpoint network is added using TEST_CREATOR_UID.
+        verifyAddPasspointNetworkToWifiConfigManager(passpointNetwork);
+        // Ensure that configured network list is not empty.
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().isEmpty());
 
-        // First assign random |numAssociation| values and verify that the list is sorted
-        // in descending order of |numAssociation| values. Keep NetworkSelectionStatus
-        // values constant.
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            TreeMap<Integer, Integer> numAssociationToNetworkIdMap =
-                    new TreeMap<>(Collections.reverseOrder());
-            ArrayDeque<Integer> numAssociationValues =
-                    getUniqueRandomNumberValues(
-                            1, 10000, mConfiguredNetworks.valuesForCurrentUser().size());
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-                config.numAssociation = numAssociationValues.pop();
-                config.priority = rand.nextInt(10000);
-                config.getNetworkSelectionStatus().setNetworkSelectionStatus(
-                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
-                numAssociationToNetworkIdMap.put(config.numAssociation, config.networkId);
-                Log.i(TAG, "networkID: " + config.networkId + ", numAssociation: "
-                        + config.numAssociation);
-            }
-            ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoNetworkList =
-                    mWifiConfigManager.retrieveDisconnectedPnoNetworkList();
-            verifyPnoNetworkListOrder(pnoNetworkList,
-                    new ArrayList(numAssociationToNetworkIdMap.values()));
-        }
+        assertTrue(mWifiConfigManager.removeNetwork(passpointNetwork.networkId, Process.WIFI_UID));
 
-        // Assign random |priority| values and verify that the list is sorted in descending order
-        // of |priority| values. Keep numAssociation/NetworkSelectionStatus values constant.
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            TreeMap<Integer, Integer> priorityToNetworkIdMap =
-                    new TreeMap<>(Collections.reverseOrder());
-            ArrayDeque<Integer> priorityValues =
-                    getUniqueRandomNumberValues(
-                            2, 10000, mConfiguredNetworks.valuesForCurrentUser().size());
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-                config.numAssociation = 0;
-                config.priority = priorityValues.pop();
-                config.getNetworkSelectionStatus().setNetworkSelectionStatus(
-                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
-                priorityToNetworkIdMap.put(config.priority, config.networkId);
-                Log.i(TAG, "networkID: " + config.networkId + ", priority: " + config.priority);
-            }
-            ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoNetworkList =
-                    mWifiConfigManager.retrieveDisconnectedPnoNetworkList();
-            verifyPnoNetworkListOrder(pnoNetworkList,
-                    new ArrayList(priorityToNetworkIdMap.values()));
-        }
+        // Verify keys are not being removed.
+        verify(mWifiKeyStore, never()).removeKeys(any(WifiEnterpriseConfig.class));
+        verifyNetworkRemoveBroadcast(passpointNetwork);
+        // Ensure that the write was not invoked for Passpoint network remove.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
 
-        // Now assign random |NetworkSelectionStatus| values and verify that the list is sorted in
-        // ascending order of |NetworkSelectionStatus| values.
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            TreeMap<Integer, Integer> networkSelectionStatusToNetworkIdMap = new TreeMap<>();
-            ArrayDeque<Integer> networkSelectionStatusValues =
-                    getUniqueRandomNumberValues(
-                            3,
-                            WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_STATUS_MAX,
-                            mConfiguredNetworks.valuesForCurrentUser().size());
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-                config.numAssociation = rand.nextInt(10000);
-                config.priority = rand.nextInt(10000);
-                config.getNetworkSelectionStatus().setNetworkSelectionStatus(
-                        networkSelectionStatusValues.pop());
-                // Permanently disabled networks should not be present in PNO scan request.
-                if (!config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
-                    networkSelectionStatusToNetworkIdMap.put(
-                            config.getNetworkSelectionStatus().getNetworkSelectionStatus(),
-                            config.networkId);
-                }
-                Log.i(TAG, "networkID: " + config.networkId + ", NetworkSelectionStatus: "
-                        + config.getNetworkSelectionStatus().getNetworkSelectionStatus());
-            }
-            ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoNetworkList =
-                    mWifiConfigManager.retrieveDisconnectedPnoNetworkList();
-            verifyPnoNetworkListOrder(pnoNetworkList,
-                    new ArrayList(networkSelectionStatusToNetworkIdMap.values()));
-        }
+    }
+    /**
+     * Verifies the addition & update of multiple networks using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} and the
+     * removal of networks using
+     * {@link WifiConfigManager#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());
+
+        // Configure mock DevicePolicyManager to give Profile Owner permission so that we can modify
+        // proxy settings on a configuration
+        when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(anyInt(),
+                eq(DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))).thenReturn(true);
+
+        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 retrieveConnectedPnoNetworkList API. The test verifies that the list
-     * returned from the API is sorted as expected.
+     * Verifies the update of network status using
+     * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)}.
      */
     @Test
-    public void testConnectedPnoNetworkListCreation() throws Exception {
-        addNetworks();
+    public void testNetworkSelectionStatus() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
 
-        Random rand = new Random(WifiTestUtil.getTestMethod().hashCode());
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
 
-        // First assign |lastSeen| values and verify that the list is sorted
-        // in descending order of |lastSeen| values. Keep NetworkSelectionStatus
-        // values constant.
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            TreeMap<Boolean, Integer> lastSeenToNetworkIdMap =
-                    new TreeMap<>(Collections.reverseOrder());
-            ArrayDeque<Integer> lastSeenValues = getUniqueRandomNumberValues(1, 2, 2);
-            if (mConfiguredNetworks.valuesForCurrentUser().size() > 2) continue;
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-                config.numAssociation = rand.nextInt(10000);
-                config.priority = rand.nextInt(10000);
-                config.getNetworkSelectionStatus().setNetworkSelectionStatus(
-                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
-                boolean lastSeenValue = (lastSeenValues.pop()  == 1);
-                config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(
-                        lastSeenValue);
-                lastSeenToNetworkIdMap.put(lastSeenValue, config.networkId);
-                Log.i(TAG, "networkID: " + config.networkId + ", lastSeen: " + lastSeenValue);
-            }
-            ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoNetworkList =
-                    mWifiConfigManager.retrieveConnectedPnoNetworkList();
-            verifyPnoNetworkListOrder(pnoNetworkList,
-                    new ArrayList(lastSeenToNetworkIdMap.values()));
+        int networkId = result.getNetworkId();
+        // First set it to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                networkId, 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 =
+                WifiConfigManager.NETWORK_SELECTION_DISABLE_THRESHOLD[assocRejectReason];
+        for (int i = 1; i <= assocRejectThreshold; i++) {
+            verifyUpdateNetworkSelectionStatus(result.getNetworkId(), assocRejectReason, i);
         }
+        verify(mWcmListener).onSavedNetworkTemporarilyDisabled(networkId);
 
-        // Assign random |numAssociation| values and verify that the list is sorted
-        // in descending order of |numAssociation| values. Keep NetworkSelectionStatus/lastSeen
-        // values constant.
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            TreeMap<Integer, Integer> numAssociationToNetworkIdMap =
-                    new TreeMap<>(Collections.reverseOrder());
-            ArrayDeque<Integer> numAssociationValues =
-                    getUniqueRandomNumberValues(
-                            1, 10000, mConfiguredNetworks.valuesForCurrentUser().size());
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-                config.numAssociation = numAssociationValues.pop();
-                config.priority = rand.nextInt(10000);
-                config.getNetworkSelectionStatus().setNetworkSelectionStatus(
-                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
-                config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
-                numAssociationToNetworkIdMap.put(config.numAssociation, config.networkId);
-                Log.i(TAG, "networkID: " + config.networkId + ", numAssociation: "
-                        + config.numAssociation);
-            }
-            ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoNetworkList =
-                    mWifiConfigManager.retrieveConnectedPnoNetworkList();
-            verifyPnoNetworkListOrder(pnoNetworkList,
-                    new ArrayList(numAssociationToNetworkIdMap.values()));
-        }
+        // Now set it to permanently disabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER, 0);
+        verify(mWcmListener).onSavedNetworkPermanentlyDisabled(networkId);
 
-        // Now assign random |NetworkSelectionStatus| values and verify that the list is sorted in
-        // ascending order of |NetworkSelectionStatus| values.
-        for (int userId : USER_IDS) {
-            switchUser(userId);
-            TreeMap<Integer, Integer> networkSelectionStatusToNetworkIdMap = new TreeMap<>();
-            ArrayDeque<Integer> networkSelectionStatusValues =
-                    getUniqueRandomNumberValues(
-                            3,
-                            WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_STATUS_MAX,
-                            mConfiguredNetworks.valuesForCurrentUser().size());
-            for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-                config.numAssociation = rand.nextInt(10000);
-                config.priority = rand.nextInt(10000);
-                config.getNetworkSelectionStatus().setNetworkSelectionStatus(
-                        networkSelectionStatusValues.pop());
-                // Permanently disabled networks should not be present in PNO scan request.
-                if (!config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
-                    networkSelectionStatusToNetworkIdMap.put(
-                            config.getNetworkSelectionStatus().getNetworkSelectionStatus(),
-                            config.networkId);
-                }
-                Log.i(TAG, "networkID: " + config.networkId + ", NetworkSelectionStatus: "
-                        + config.getNetworkSelectionStatus().getNetworkSelectionStatus());
-            }
-            ArrayList<WifiScanner.PnoSettings.PnoNetwork> pnoNetworkList =
-                    mWifiConfigManager.retrieveConnectedPnoNetworkList();
-            verifyPnoNetworkListOrder(pnoNetworkList,
-                    new ArrayList(networkSelectionStatusToNetworkIdMap.values()));
-        }
+        // Now set it back to enabled.
+        verifyUpdateNetworkSelectionStatus(
+                result.getNetworkId(), NetworkSelectionStatus.NETWORK_SELECTION_ENABLE, 0);
+        verify(mWcmListener, times(2)).onSavedNetworkEnabled(networkId);
     }
 
     /**
-     * Verifies that hasEverConnected is false for a newly added network
+     * Verifies the update of network status using
+     * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)} and ensures that
+     * enabling a network clears out all the temporary disable counters.
      */
     @Test
-    public void testAddNetworkHasEverConnectedFalse() throws Exception {
-        addNetwork(BASE_HAS_EVER_CONNECTED_CONFIG);
-        WifiConfiguration checkConfig = mWifiConfigManager.getWifiConfiguration(
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        assertFalse("Adding a new network should not have hasEverConnected set to true.",
-                checkConfig.getNetworkSelectionStatus().getHasEverConnected());
+    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 that {@link WifiConfigManager#updateNetworkNotRecommended(int, boolean)} correctly
+     * updates the {@link NetworkSelectionStatus#mNotRecommended} bit.
+     */
+    @Test
+    public void testUpdateNetworkNotRecommended() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        // First retrieve the configuration and check this it does not have this bit set
+        WifiConfiguration retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(result.netId);
+
+        assertFalse(retrievedNetwork.getNetworkSelectionStatus().isNotRecommended());
+
+        // Update the network to be not recommended;
+        assertTrue(mWifiConfigManager.updateNetworkNotRecommended(
+                result.netId, true /* notRecommended*/));
+
+        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(result.netId);
+
+        assertTrue(retrievedNetwork.getNetworkSelectionStatus().isNotRecommended());
+
+        // Update the network to no longer be not recommended
+        assertTrue(mWifiConfigManager.updateNetworkNotRecommended(
+                result.netId, false/* notRecommended*/));
+
+        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(result.netId);
+
+        assertFalse(retrievedNetwork.getNetworkSelectionStatus().isNotRecommended());
+    }
+
+    /**
+     * Verifies the enabling of temporarily disabled network using
+     * {@link WifiConfigManager#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 =
+                WifiConfigManager.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 =
+                WifiConfigManager.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 WifiConfigManager#enableNetwork(int, boolean, int)} and
+     * {@link WifiConfigManager#disableNetwork(int, int)}.
+     */
+    @Test
+    public void testEnableDisableNetwork() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        assertTrue(mWifiConfigManager.enableNetwork(
+                result.getNetworkId(), false, TEST_CREATOR_UID));
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        NetworkSelectionStatus retrievedStatus = retrievedNetwork.getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkEnabled());
+        verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.ENABLED);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore).write(eq(true));
+
+        // 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);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore).write(eq(true));
+    }
+
+    /**
+     * Verifies the enabling of network using
+     * {@link WifiConfigManager#enableNetwork(int, boolean, 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(), false, TEST_CREATOR_UID));
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        NetworkSelectionStatus retrievedStatus = retrievedNetwork.getNetworkSelectionStatus();
+        assertTrue(retrievedStatus.isNetworkEnabled());
+        verifyUpdateNetworkStatus(retrievedNetwork, WifiConfiguration.Status.ENABLED);
+
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+
+        // 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 WifiConfigManager#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);
+
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+
+        // Now try to update the last connect UID with |TEST_UPDATE_UID|, it should fail and
+        // the lastConnectUid should remain the same.
+        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 WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}.
+     */
+    @Test
+    public void testAddOrUpdateNetworkWithNullConfig() {
+        NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(null, TEST_CREATOR_UID);
+        assertFalse(result.isSuccess());
+    }
+
+    /**
+     * Verifies that attempting to remove a network without any configs stored will return false.
+     * This tests the case where we have not loaded any configs, potentially due to a pending store
+     * read.
+     * This invokes {@link WifiConfigManager#removeNetwork(int)}.
+     */
+    @Test
+    public void testRemoveNetworkWithEmptyConfigStore() {
+        int networkId = new Random().nextInt();
+        assertFalse(mWifiConfigManager.removeNetwork(networkId, TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verifies that any configuration removal attempt with an invalid networkID is gracefully
+     * handled.
+     * This invokes {@link WifiConfigManager#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, TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verifies that any configuration update attempt with an invalid networkID is gracefully
+     * handled.
+     * This invokes {@link WifiConfigManager#enableNetwork(int, boolean, int)},
+     * {@link WifiConfigManager#disableNetwork(int, int)},
+     * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)} and
+     * {@link WifiConfigManager#checkAndUpdateLastConnectUid(int, int)}.
+     */
+    @Test
+    public void testChangeConfigurationWithInvalidNetworkId() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        assertFalse(mWifiConfigManager.enableNetwork(
+                result.getNetworkId() + 1, false, 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 WifiConfigManager#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 WifiConfigManager#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 WifiConfigManager#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.clear();
+        network.allowedProtocols.clear();
+        network.allowedKeyManagement.clear();
+        network.allowedPairwiseCiphers.clear();
+        network.allowedGroupCiphers.clear();
+        network.setIpConfiguration(null);
+        network.enterpriseConfig = null;
+
+        // Update the network.
+        NetworkUpdateResult result = updateNetworkToWifiConfigManager(network);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertFalse(result.isNewNetwork());
+
+        // Verify no changes to the original network configuration.
+        verifyNetworkUpdateBroadcast(originalNetwork);
+        verifyNetworkInConfigStoreData(originalNetwork);
+        assertFalse(result.hasIpChanged());
+        assertFalse(result.hasProxyChanged());
+
+        // 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 that the modification of a single network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} does not modify
+     * existing configuration if there is a failure.
+     */
+    @Test
+    public void testUpdateSingleNetworkFailureDoesNotModifyOriginal() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createEapNetwork();
+        network.enterpriseConfig =
+                WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Save a copy of the original network for comparison.
+        WifiConfiguration originalNetwork = new WifiConfiguration(network);
+
+        // Now modify the network's EAP method.
+        network.enterpriseConfig =
+                WifiConfigurationTestUtil.createTLSWifiEnterpriseConfigWithNonePhase2();
+
+        // Fail this update because of cert installation failure.
+        when(mWifiKeyStore
+                .updateNetworkKeys(any(WifiConfiguration.class), any(WifiConfiguration.class)))
+                .thenReturn(false);
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(network, TEST_UPDATE_UID);
+        assertTrue(result.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID);
+
+        // 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 WifiConfigManager#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 WifiConfigManager#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 ScanDetail added for a network is cached correctly.
+     */
+    @Test
+    public void testUpdateScanDetailForNetwork() {
+        // First add the provided network.
+        WifiConfiguration testNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(testNetwork);
+
+        // Now create a dummy scan detail corresponding to the network.
+        ScanDetail scanDetail = createScanDetailForNetwork(testNetwork);
+        ScanResult scanResult = scanDetail.getScanResult();
+
+        mWifiConfigManager.updateScanDetailForNetwork(result.getNetworkId(), scanDetail);
+
+        // Now retrieve the scan detail cache and ensure that the new scan detail is in cache.
+        ScanDetailCache retrievedScanDetailCache =
+                mWifiConfigManager.getScanDetailCacheForNetwork(result.getNetworkId());
+        assertEquals(1, retrievedScanDetailCache.size());
+        ScanResult retrievedScanResult = retrievedScanDetailCache.get(scanResult.BSSID);
+
+        ScanTestUtil.assertScanResultEquals(scanResult, retrievedScanResult);
+    }
+
+    /**
+     * Verifies that scan detail cache is trimmed down when the size of the cache for a network
+     * exceeds {@link WifiConfigManager#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 <= WifiConfigManager.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(WifiConfigManager.SCAN_CACHE_ENTRIES_TRIM_SIZE + 1, scanDetailCache.size());
+    }
+
+    /**
+     * Verifies that hasEverConnected is false for a newly added network.
+     */
+    @Test
+    public void testAddNetworkHasEverConnectedFalse() {
+        verifyAddNetworkHasEverConnectedFalse(WifiConfigurationTestUtil.createOpenNetwork());
+    }
 
     /**
      * Verifies that hasEverConnected is false for a newly added network even when new config has
      * mistakenly set HasEverConnected to true.
-    */
-    @Test
-    public void testAddNetworkOverridesHasEverConnectedWhenTrueInNewConfig() throws Exception {
-        WifiConfiguration newNetworkWithHasEverConnectedTrue =
-                new WifiConfiguration(BASE_HAS_EVER_CONNECTED_CONFIG);
-        newNetworkWithHasEverConnectedTrue.getNetworkSelectionStatus().setHasEverConnected(true);
-        addNetwork(newNetworkWithHasEverConnectedTrue);
-        // check if addNetwork clears the bit.
-        WifiConfiguration checkConfig = mWifiConfigManager.getWifiConfiguration(
-                newNetworkWithHasEverConnectedTrue.networkId);
-        assertFalse("Adding a new network should not have hasEverConnected set to true.",
-                checkConfig.getNetworkSelectionStatus().getHasEverConnected());
-    }
-
-
-    /**
-     * Verify that setting HasEverConnected with a config update can be read back.
      */
     @Test
-    public void testUpdateConfigToHasEverConnectedTrue() throws Exception {
-        addNetwork(BASE_HAS_EVER_CONNECTED_CONFIG);
-
-        // Get the newly saved config and update HasEverConnected
-        WifiConfiguration checkConfig = mWifiConfigManager.getWifiConfiguration(
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        assertFalse("Adding a new network should not have hasEverConnected set to true.",
-                checkConfig.getNetworkSelectionStatus().getHasEverConnected());
-        checkConfig.getNetworkSelectionStatus().setHasEverConnected(true);
-        mWifiConfigManager.addOrUpdateNetwork(checkConfig, HAS_EVER_CONNECTED_USER);
-
-        // verify that HasEverConnected was properly written and read back
-        checkHasEverConnectedTrue(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
+    public void testAddNetworkOverridesHasEverConnectedWhenTrueInNewConfig() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
+        verifyAddNetworkHasEverConnectedFalse(openNetwork);
     }
 
-
     /**
-     * Verifies that hasEverConnected is cleared when a network config preSharedKey is updated.
+     * Verify that the |HasEverConnected| is set when
+     * {@link WifiConfigManager#updateNetworkAfterConnect(int)} is invoked.
      */
     @Test
-    public void testUpdatePreSharedKeyClearsHasEverConnected() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
-
-        testUpdateConfigToHasEverConnectedTrue();
-
-        WifiConfiguration original = mWifiConfigManager.getWifiConfiguration(
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        WifiConfiguration updatePreSharedKeyConfig = new WifiConfiguration();
-        updatePreSharedKeyConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updatePreSharedKeyConfig.SSID = original.SSID;
-        updatePreSharedKeyConfig.preSharedKey = "newpassword";
-        switchUserToCreatorOrParentOf(original);
-        mWifiConfigManager.addOrUpdateNetwork(updatePreSharedKeyConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+    public void testUpdateConfigAfterConnectHasEverConnectedTrue() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        verifyAddNetworkHasEverConnectedFalse(openNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(openNetwork.networkId);
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config allowedKeyManagement is
+     * Verifies that hasEverConnected is cleared when a network config |preSharedKey| is updated.
+     */
+    @Test
+    public void testUpdatePreSharedKeyClearsHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
+
+        // Now update the same network with a different psk.
+        assertFalse(pskNetwork.preSharedKey.equals("newpassword"));
+        pskNetwork.preSharedKey = "newpassword";
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
+    }
+
+    /**
+     * Verifies that hasEverConnected is cleared when a network config |wepKeys| is updated.
+     */
+    @Test
+    public void testUpdateWepKeysClearsHasEverConnected() {
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        verifyAddNetworkHasEverConnectedFalse(wepNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(wepNetwork.networkId);
+
+        // Now update the same network with a different wep.
+        assertFalse(wepNetwork.wepKeys[0].equals("newpassword"));
+        wepNetwork.wepKeys[0] = "newpassword";
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(wepNetwork);
+    }
+
+    /**
+     * Verifies that hasEverConnected is cleared when a network config |wepTxKeyIndex| is updated.
+     */
+    @Test
+    public void testUpdateWepTxKeyClearsHasEverConnected() {
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        verifyAddNetworkHasEverConnectedFalse(wepNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(wepNetwork.networkId);
+
+        // Now update the same network with a different wep.
+        assertFalse(wepNetwork.wepTxKeyIndex == 3);
+        wepNetwork.wepTxKeyIndex = 3;
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(wepNetwork);
+    }
+
+    /**
+     * Verifies that hasEverConnected is cleared when a network config |allowedKeyManagement| is
      * updated.
      */
     @Test
-    public void testUpdateAllowedKeyManagementChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateAllowedKeyManagementClearsHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
-
-        WifiConfiguration updateAllowedKeyManagementConfig = new WifiConfiguration();
-        updateAllowedKeyManagementConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateAllowedKeyManagementConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        updateAllowedKeyManagementConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
-
-        // Set up mock to allow the new value to be read back into the config
-        String allowedKeyManagementString = makeString(
-                updateAllowedKeyManagementConfig.allowedKeyManagement,
-                    WifiConfiguration.KeyMgmt.strings);
-        when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-                KeyMgmt.varName)).thenReturn(allowedKeyManagementString);
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateAllowedKeyManagementConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        assertFalse(pskNetwork.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        pskNetwork.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config allowedProtocols is
+     * Verifies that hasEverConnected is cleared when a network config |allowedProtocol| is
      * updated.
      */
     @Test
-    public void testUpdateAllowedProtocolsChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateProtocolsClearsHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
-
-        WifiConfiguration updateAllowedProtocolsConfig = new WifiConfiguration();
-        updateAllowedProtocolsConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateAllowedProtocolsConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        updateAllowedProtocolsConfig.allowedProtocols.set(
-                WifiConfiguration.Protocol.RSN);
-
-        // Set up mock to allow the new value to be read back into the config
-        String allowedProtocolsString = makeString(
-                updateAllowedProtocolsConfig.allowedProtocols,
-                    WifiConfiguration.Protocol.strings);
-        when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-                Protocol.varName)).thenReturn(allowedProtocolsString);
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateAllowedProtocolsConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        assertFalse(pskNetwork.allowedProtocols.get(WifiConfiguration.Protocol.OSEN));
+        pskNetwork.allowedProtocols.set(WifiConfiguration.Protocol.OSEN);
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config allowedAuthAlgorithms is
+     * Verifies that hasEverConnected is cleared when a network config |allowedAuthAlgorithms| is
      * updated.
      */
     @Test
-    public void testUpdateAllowedAuthAlgorithmsChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateAllowedAuthAlgorithmsClearsHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
-
-        WifiConfiguration updateAllowedAuthAlgorithmsConfig = new WifiConfiguration();
-        updateAllowedAuthAlgorithmsConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateAllowedAuthAlgorithmsConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        updateAllowedAuthAlgorithmsConfig.allowedAuthAlgorithms.set(
-                WifiConfiguration.AuthAlgorithm.SHARED);
-
-        // Set up mock to allow the new value to be read back into the config
-        String allowedAuthAlgorithmsString = makeString(
-                updateAllowedAuthAlgorithmsConfig.allowedAuthAlgorithms,
-                    WifiConfiguration.AuthAlgorithm.strings);
-        when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-                AuthAlgorithm.varName)).thenReturn(allowedAuthAlgorithmsString);
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateAllowedAuthAlgorithmsConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        assertFalse(pskNetwork.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.LEAP));
+        pskNetwork.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.LEAP);
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config allowedPairwiseCiphers is
+     * Verifies that hasEverConnected is cleared when a network config |allowedPairwiseCiphers| is
      * updated.
      */
     @Test
-    public void testUpdateAllowedPairwiseCiphersChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateAllowedPairwiseCiphersClearsHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
-
-        WifiConfiguration updateAllowedPairwiseCiphersConfig = new WifiConfiguration();
-        updateAllowedPairwiseCiphersConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateAllowedPairwiseCiphersConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        updateAllowedPairwiseCiphersConfig.allowedPairwiseCiphers.set(
-                WifiConfiguration.PairwiseCipher.CCMP);
-
-        // Set up mock to allow the new value to be read back into the config
-        String allowedPairwiseCiphersString = makeString(
-                updateAllowedPairwiseCiphersConfig.allowedPairwiseCiphers,
-                    WifiConfiguration.PairwiseCipher.strings);
-        when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-                PairwiseCipher.varName)).thenReturn(allowedPairwiseCiphersString);
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateAllowedPairwiseCiphersConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        assertFalse(pskNetwork.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.NONE));
+        pskNetwork.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.NONE);
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config allowedGroupCiphers is
+     * Verifies that hasEverConnected is cleared when a network config |allowedGroup| is
      * updated.
      */
     @Test
-    public void testUpdateAllowedGroupCiphersChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateAllowedGroupCiphersClearsHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
-
-        WifiConfiguration updateAllowedGroupCiphersConfig = new WifiConfiguration();
-        updateAllowedGroupCiphersConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateAllowedGroupCiphersConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        updateAllowedGroupCiphersConfig.allowedGroupCiphers.set(
-                WifiConfiguration.GroupCipher.CCMP);
-
-        // Set up mock to allow the new value to be read back into the config
-        String allowedGroupCiphersString = makeString(
-                updateAllowedGroupCiphersConfig.allowedGroupCiphers,
-                    WifiConfiguration.GroupCipher.strings);
-        when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-                GroupCipher.varName)).thenReturn(allowedGroupCiphersString);
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateAllowedGroupCiphersConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        assertTrue(pskNetwork.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.WEP104));
+        pskNetwork.allowedGroupCiphers.clear(WifiConfiguration.GroupCipher.WEP104);
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config wepKeys is
+     * Verifies that hasEverConnected is cleared when a network config |hiddenSSID| is
      * updated.
      */
     @Test
-    public void testUpdateWepKeysChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateHiddenSSIDClearsHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
-
-        String tempKey = "hereisakey";
-        WifiConfiguration updateWepKeysConfig = new WifiConfiguration();
-        updateWepKeysConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateWepKeysConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        updateWepKeysConfig.wepKeys = new String[] {tempKey};
-
-        // Set up mock to allow the new value to be read back into the config
-        when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-                WifiConfiguration.wepKeyVarNames[0])).thenReturn(tempKey);
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateWepKeysConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        assertFalse(pskNetwork.hiddenSSID);
+        pskNetwork.hiddenSSID = true;
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(pskNetwork);
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config hiddenSSID is
+     * Verifies that hasEverConnected is not cleared when a network config |requirePMF| is
      * updated.
      */
     @Test
-    public void testUpdateHiddenSSIDChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateRequirePMFDoesNotClearHasEverConnected() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkHasEverConnectedFalse(pskNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(pskNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
+        assertFalse(pskNetwork.requirePMF);
+        pskNetwork.requirePMF = true;
 
-        WifiConfiguration updateHiddenSSIDConfig = new WifiConfiguration();
-        updateHiddenSSIDConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateHiddenSSIDConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        updateHiddenSSIDConfig.hiddenSSID = true;
-
-        // Set up mock to allow the new value to be read back into the config
-        when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-                WifiConfiguration.hiddenSSIDVarName)).thenReturn("1");
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateHiddenSSIDConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        NetworkUpdateResult result =
+                verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(pskNetwork);
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        assertTrue("Updating network non-credentials config should not clear hasEverConnected.",
+                retrievedNetwork.getNetworkSelectionStatus().getHasEverConnected());
+        assertFalse(result.hasCredentialChanged());
     }
 
     /**
-     * Verifies that hasEverConnected is cleared when a network config pmfVarName is
+     * Verifies that hasEverConnected is cleared when a network config |enterpriseConfig| is
      * updated.
      */
     @Test
-    public void testUpdateRequirePMFChanged() throws Exception {
-        final int originalUserId = mWifiConfigManager.getCurrentUserId();
+    public void testUpdateEnterpriseConfigClearsHasEverConnected() {
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        eapNetwork.enterpriseConfig =
+                WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
+        verifyAddNetworkHasEverConnectedFalse(eapNetwork);
+        verifyUpdateNetworkAfterConnectHasEverConnectedTrue(eapNetwork.networkId);
 
-        testUpdateConfigToHasEverConnectedTrue();
-
-        WifiConfiguration updateRequirePMFConfig = new WifiConfiguration();
-        updateRequirePMFConfig.networkId = BASE_HAS_EVER_CONNECTED_CONFIG.networkId;
-        updateRequirePMFConfig.SSID = BASE_HAS_EVER_CONNECTED_CONFIG.SSID;
-        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
-        // checkHasEverConnectedFalse below.
-        //when(mWifiNative.getNetworkVariable(BASE_HAS_EVER_CONNECTED_CONFIG.networkId,
-        //        WifiConfiguration.pmfVarName)).thenReturn("2");
-
-        switchUserToCreatorOrParentOf(BASE_HAS_EVER_CONNECTED_CONFIG);
-        mWifiConfigManager.addOrUpdateNetwork(updateRequirePMFConfig,
-                BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-
-        //checkHasEverConnectedFalse(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        checkHasEverConnectedTrue(BASE_HAS_EVER_CONNECTED_CONFIG.networkId);
-        switchUser(originalUserId);
+        assertFalse(eapNetwork.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS);
+        eapNetwork.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(eapNetwork);
     }
 
     /**
-     * Verify WifiEnterpriseConfig changes are detected in WifiConfigManager.
+     * Verifies that if the app sends back the masked passwords in an update, we ignore it.
      */
     @Test
-    public void testEnterpriseConfigAdded() {
-        EnterpriseConfig eapConfig =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0});
+    public void testUpdateIgnoresMaskedPasswords() {
+        WifiConfiguration someRandomNetworkWithAllMaskedFields =
+                WifiConfigurationTestUtil.createEapNetwork();
+        someRandomNetworkWithAllMaskedFields.wepKeys = WifiConfigurationTestUtil.TEST_WEP_KEYS;
+        someRandomNetworkWithAllMaskedFields.preSharedKey = WifiConfigurationTestUtil.TEST_PSK;
+        someRandomNetworkWithAllMaskedFields.enterpriseConfig.setPassword(
+                WifiConfigurationTestUtil.TEST_EAP_PASSWORD);
 
-        assertTrue(mWifiConfigManager.wasEnterpriseConfigChange(null, eapConfig.enterpriseConfig));
+        NetworkUpdateResult result =
+                verifyAddNetworkToWifiConfigManager(someRandomNetworkWithAllMaskedFields);
+
+        // All of these passwords must be masked in this retrieved network config.
+        WifiConfiguration retrievedNetworkWithMaskedPassword =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        assertPasswordsMaskedInWifiConfiguration(retrievedNetworkWithMaskedPassword);
+        // Ensure that the passwords are present internally.
+        WifiConfiguration retrievedNetworkWithPassword =
+                mWifiConfigManager.getConfiguredNetworkWithPassword(result.getNetworkId());
+        assertEquals(someRandomNetworkWithAllMaskedFields.preSharedKey,
+                retrievedNetworkWithPassword.preSharedKey);
+        assertEquals(someRandomNetworkWithAllMaskedFields.wepKeys,
+                retrievedNetworkWithPassword.wepKeys);
+        assertEquals(someRandomNetworkWithAllMaskedFields.enterpriseConfig.getPassword(),
+                retrievedNetworkWithPassword.enterpriseConfig.getPassword());
+
+        // Now update the same network config using the masked config.
+        verifyUpdateNetworkToWifiConfigManager(retrievedNetworkWithMaskedPassword);
+
+        // Retrieve the network config with password and ensure that they have not been overwritten
+        // with *.
+        retrievedNetworkWithPassword =
+                mWifiConfigManager.getConfiguredNetworkWithPassword(result.getNetworkId());
+        assertEquals(someRandomNetworkWithAllMaskedFields.preSharedKey,
+                retrievedNetworkWithPassword.preSharedKey);
+        assertEquals(someRandomNetworkWithAllMaskedFields.wepKeys,
+                retrievedNetworkWithPassword.wepKeys);
+        assertEquals(someRandomNetworkWithAllMaskedFields.enterpriseConfig.getPassword(),
+                retrievedNetworkWithPassword.enterpriseConfig.getPassword());
     }
 
     /**
-     * Verify WifiEnterpriseConfig eap change is detected.
+     * Verifies the ordering of network list generated using
+     * {@link WifiConfigManager#retrievePnoNetworkList()}.
      */
     @Test
-    public void testEnterpriseConfigEapChangeDetected() {
-        EnterpriseConfig eapConfig = new EnterpriseConfig(Eap.TTLS);
-        EnterpriseConfig peapConfig = new EnterpriseConfig(Eap.PEAP);
+    public void testRetrievePnoList() {
+        // Create and add 3 networks.
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createEapNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
 
-        assertTrue(mWifiConfigManager.wasEnterpriseConfigChange(eapConfig.enterpriseConfig,
-                peapConfig.enterpriseConfig));
+        // Enable all of them.
+        assertTrue(mWifiConfigManager.enableNetwork(network1.networkId, false, TEST_CREATOR_UID));
+        assertTrue(mWifiConfigManager.enableNetwork(network2.networkId, false, TEST_CREATOR_UID));
+        assertTrue(mWifiConfigManager.enableNetwork(network3.networkId, false, TEST_CREATOR_UID));
+
+        // Now set scan results in 2 of them to set the corresponding
+        // {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} field.
+        assertTrue(mWifiConfigManager.setNetworkCandidateScanResult(
+                network1.networkId, createScanDetailForNetwork(network1).getScanResult(), 54));
+        assertTrue(mWifiConfigManager.setNetworkCandidateScanResult(
+                network3.networkId, createScanDetailForNetwork(network3).getScanResult(), 54));
+
+        // Now increment |network3|'s association count. This should ensure that this network
+        // is preferred over |network1|.
+        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(network3.networkId));
+
+        // Retrieve the Pno network list & verify the order of the networks returned.
+        List<WifiScanner.PnoSettings.PnoNetwork> pnoNetworks =
+                mWifiConfigManager.retrievePnoNetworkList();
+        assertEquals(3, pnoNetworks.size());
+        assertEquals(network3.SSID, pnoNetworks.get(0).ssid);
+        assertEquals(network1.SSID, pnoNetworks.get(1).ssid);
+        assertEquals(network2.SSID, pnoNetworks.get(2).ssid);
+
+        // Now permanently disable |network3|. This should remove network 3 from the list.
+        assertTrue(mWifiConfigManager.disableNetwork(network3.networkId, TEST_CREATOR_UID));
+
+        // Retrieve the Pno network list again & verify the order of the networks returned.
+        pnoNetworks = mWifiConfigManager.retrievePnoNetworkList();
+        assertEquals(2, pnoNetworks.size());
+        assertEquals(network1.SSID, pnoNetworks.get(0).ssid);
+        assertEquals(network2.SSID, pnoNetworks.get(1).ssid);
     }
 
     /**
-     * Verify WifiEnterpriseConfig phase2 method change is detected.
+     * Verifies the linking of networks when they have the same default GW Mac address in
+     * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
      */
     @Test
-    public void testEnterpriseConfigPhase2ChangeDetected() {
-        EnterpriseConfig eapConfig = new EnterpriseConfig(Eap.TTLS).setPhase2(Phase2.MSCHAPV2);
-        EnterpriseConfig papConfig = new EnterpriseConfig(Eap.TTLS).setPhase2(Phase2.PAP);
+    public void testNetworkLinkUsingGwMacAddress() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
 
-        assertTrue(mWifiConfigManager.wasEnterpriseConfigChange(eapConfig.enterpriseConfig,
-                papConfig.enterpriseConfig));
+        // Set the same default GW mac address for all of the networks.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network3.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+
+        // Now create dummy scan detail corresponding to the networks.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1);
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2);
+        ScanDetail networkScanDetail3 = createScanDetailForNetwork(network3);
+
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail3));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertEquals(2, network.linkedConfigurations.size());
+            for (WifiConfiguration otherNetwork : retrievedNetworks) {
+                if (otherNetwork == network) {
+                    continue;
+                }
+                assertNotNull(network.linkedConfigurations.get(otherNetwork.configKey()));
+            }
+        }
     }
 
     /**
-     * Verify WifiEnterpriseConfig added Certificate is detected.
+     * Verifies the linking of networks when they have scan results with same first 16 ASCII of
+     * bssid in
+     * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
      */
     @Test
-    public void testCaCertificateAddedDetected() {
-        EnterpriseConfig eapConfigNoCerts =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password");
+    public void testNetworkLinkUsingBSSIDMatch() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
 
-        EnterpriseConfig eapConfig1Cert =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0});
+        // Create scan results with bssid which is different in only the last char.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
+        ScanDetail networkScanDetail3 = createScanDetailForNetwork(network3, "af:89:56:34:56:69");
 
-        assertTrue(mWifiConfigManager.wasEnterpriseConfigChange(eapConfigNoCerts.enterpriseConfig,
-                eapConfig1Cert.enterpriseConfig));
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail3));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertEquals(2, network.linkedConfigurations.size());
+            for (WifiConfiguration otherNetwork : retrievedNetworks) {
+                if (otherNetwork == network) {
+                    continue;
+                }
+                assertNotNull(network.linkedConfigurations.get(otherNetwork.configKey()));
+            }
+        }
     }
 
     /**
-     * Verify WifiEnterpriseConfig Certificate change is detected.
+     * Verifies the linking of networks does not happen for non WPA networks when they have scan
+     * results with same first 16 ASCII of bssid in
+     * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
      */
     @Test
-    public void testDifferentCaCertificateDetected() {
-        EnterpriseConfig eapConfig =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0});
+    public void testNoNetworkLinkUsingBSSIDMatchForNonWpaNetworks() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
 
-        EnterpriseConfig eapConfigNewCert =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT1});
+        // Create scan results with bssid which is different in only the last char.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
 
-        assertTrue(mWifiConfigManager.wasEnterpriseConfigChange(eapConfig.enterpriseConfig,
-                eapConfigNewCert.enterpriseConfig));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertNull(network.linkedConfigurations);
+        }
     }
 
     /**
-     * Verify WifiEnterpriseConfig added Certificate changes are detected.
+     * Verifies the linking of networks does not happen for networks with more than
+     * {@link WifiConfigManager#LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES} scan
+     * results with same first 16 ASCII of bssid in
+     * {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
      */
     @Test
-    public void testCaCertificateChangesDetected() {
-        EnterpriseConfig eapConfig =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0});
+    public void testNoNetworkLinkUsingBSSIDMatchForNetworksWithHighScanDetailCacheSize() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
 
-        EnterpriseConfig eapConfigAddedCert =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
-
-        assertTrue(mWifiConfigManager.wasEnterpriseConfigChange(eapConfig.enterpriseConfig,
-                eapConfigAddedCert.enterpriseConfig));
-    }
-
-    /**
-     * Verify that WifiEnterpriseConfig does not detect changes for identical configs.
-     */
-    @Test
-    public void testWifiEnterpriseConfigNoChanges() {
-        EnterpriseConfig eapConfig =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
-
-        // Just to be clear that check is not against the same object
-        EnterpriseConfig eapConfigSame =  new EnterpriseConfig(Eap.TTLS)
-                .setPhase2(Phase2.MSCHAPV2)
-                .setIdentity("username", "password")
-                .setCaCerts(new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
-
-        assertFalse(mWifiConfigManager.wasEnterpriseConfigChange(eapConfig.enterpriseConfig,
-                eapConfigSame.enterpriseConfig));
-    }
-
-
-    private void checkHasEverConnectedTrue(int networkId) {
-        WifiConfiguration checkConfig = mWifiConfigManager.getWifiConfiguration(networkId);
-        assertTrue("hasEverConnected expected to be true.",
-                checkConfig.getNetworkSelectionStatus().getHasEverConnected());
-    }
-
-    private void checkHasEverConnectedFalse(int networkId) {
-        WifiConfiguration checkConfig = mWifiConfigManager.getWifiConfiguration(networkId);
-        assertFalse("Updating credentials network config should clear hasEverConnected.",
-                checkConfig.getNetworkSelectionStatus().getHasEverConnected());
-    }
-
-    /**
-     *  Helper function to translate from WifiConfiguration BitSet to String.
-     */
-    private static String makeString(BitSet set, String[] strings) {
-        StringBuffer buf = new StringBuffer();
-        int nextSetBit = -1;
-
-        /* Make sure all set bits are in [0, strings.length) to avoid
-         * going out of bounds on strings.  (Shouldn't happen, but...) */
-        set = set.get(0, strings.length);
-
-        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
-            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
+        // Create 7 scan results with bssid which is different in only the last char.
+        String test_bssid_base = "af:89:56:34:56:6";
+        int scan_result_num = 0;
+        for (; scan_result_num < WifiConfigManager.LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES + 1;
+             scan_result_num++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network1, test_bssid_base + Integer.toString(scan_result_num));
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
         }
 
-        // remove trailing space
-        if (set.cardinality() > 0) {
-            buf.setLength(buf.length() - 1);
-        }
+        // Now add 1 scan result to the other network with bssid which is different in only the
+        // last char.
+        ScanDetail networkScanDetail2 =
+                createScanDetailForNetwork(
+                        network2, test_bssid_base + Integer.toString(scan_result_num++));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
 
-        return buf.toString();
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertNull(network.linkedConfigurations);
+        }
     }
 
+    /**
+     * Verifies the linking of networks when they have scan results with same first 16 ASCII of
+     * bssid in {@link WifiConfigManager#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}
+     * and then subsequently delinked when the networks have default gateway set which do not match.
+     */
+    @Test
+    public void testNetworkLinkUsingBSSIDMatchAndThenUnlinkDueToGwMacAddress() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        // Create scan results with bssid which is different in only the last char.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
+
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertEquals(1, network.linkedConfigurations.size());
+            for (WifiConfiguration otherNetwork : retrievedNetworks) {
+                if (otherNetwork == network) {
+                    continue;
+                }
+                assertNotNull(network.linkedConfigurations.get(otherNetwork.configKey()));
+            }
+        }
+
+        // Now Set different GW mac address for both the networks and ensure they're unlinked.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, "de:ad:fe:45:23:34"));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, "ad:de:fe:45:23:34"));
+
+        // Add some dummy scan results again to re-evaluate the linking of networks.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                createScanDetailForNetwork(network1, "af:89:56:34:45:67")));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                createScanDetailForNetwork(network1, "af:89:56:34:45:68")));
+
+        retrievedNetworks = mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertNull(network.linkedConfigurations);
+        }
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManager#fetchChannelSetForNetworkForPartialScan(int, long, int)}.
+     */
+    @Test
+    public void testFetchChannelSetForNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Create 5 scan results with different bssid's & frequencies.
+        String test_bssid_base = "af:89:56:34:56:6";
+        for (int i = 0; i < TEST_FREQ_LIST.length; i++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        assertEquals(new HashSet<Integer>(Arrays.asList(TEST_FREQ_LIST)),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(network.networkId, 1,
+                        TEST_FREQ_LIST[4]));
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManager#fetchChannelSetForNetworkForPartialScan(int, long, int)} and
+     * ensures that the frequenecy of the currently connected network is in the returned
+     * channel set.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIncludeCurrentNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Create 5 scan results with different bssid's & frequencies.
+        String test_bssid_base = "af:89:56:34:56:6";
+        for (int i = 0; i < TEST_FREQ_LIST.length; i++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+
+        // Currently connected network frequency 2427 is not in the TEST_FREQ_LIST
+        Set<Integer> freqs = mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                network.networkId, 1, 2427);
+
+        assertEquals(true, freqs.contains(2427));
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManager#fetchChannelSetForNetworkForPartialScan(int, long, int)} and
+     * ensures that scan results which have a timestamp  beyond the provided age are not used
+     * in the channel list.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIgnoresStaleScanResults() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        long wallClockBase = 0;
+        // Create 5 scan results with different bssid's & frequencies.
+        String test_bssid_base = "af:89:56:34:56:6";
+        for (int i = 0; i < TEST_FREQ_LIST.length; i++) {
+            // Increment the seen value in the scan results for each of them.
+            when(mClock.getWallClockMillis()).thenReturn(wallClockBase + i);
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        int ageInMillis = 4;
+        // Now fetch only scan results which are 4 millis stale. This should ignore the first
+        // scan result.
+        assertEquals(
+                new HashSet<>(Arrays.asList(
+                        Arrays.copyOfRange(
+                                TEST_FREQ_LIST,
+                                TEST_FREQ_LIST.length - ageInMillis, TEST_FREQ_LIST.length))),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network.networkId, ageInMillis, TEST_FREQ_LIST[4]));
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManager#fetchChannelSetForNetworkForPartialScan(int, long, int)} and
+     * ensures that the list size does not exceed the max configured for the device.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIsLimitedToConfiguredSize() {
+        // Need to recreate the WifiConfigManager instance for this test to modify the config
+        // value which is read only in the constructor.
+        int maxListSize = 3;
+        mResources.setInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
+                maxListSize);
+        createWifiConfigManager();
+
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Create 5 scan results with different bssid's & frequencies.
+        String test_bssid_base = "af:89:56:34:56:6";
+        for (int i = 0; i < TEST_FREQ_LIST.length; i++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        // Ensure that the fetched list size is limited.
+        assertEquals(maxListSize,
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network.networkId, 1, TEST_FREQ_LIST[4]).size());
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManager#fetchChannelSetForNetworkForPartialScan(int, long, int)} and
+     * ensures that scan results from linked networks are used in the channel list.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIncludesLinkedNetworks() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        String test_bssid_base = "af:89:56:34:56:6";
+        int TEST_FREQ_LISTIdx = 0;
+        // Create 3 scan results with different bssid's & frequencies for network 1.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length / 2; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network1, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        // Create 3 scan results with different bssid's & frequencies for network 2.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network2, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+        }
+
+        // Link the 2 configurations together using the GwMacAddress.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+
+        // The channel list fetched should include scan results from both the linked networks.
+        assertEquals(new HashSet<Integer>(Arrays.asList(TEST_FREQ_LIST)),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(network1.networkId, 1,
+                        TEST_FREQ_LIST[0]));
+        assertEquals(new HashSet<Integer>(Arrays.asList(TEST_FREQ_LIST)),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(network2.networkId, 1,
+                        TEST_FREQ_LIST[0]));
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManager#fetchChannelSetForNetworkForPartialScan(int, long, int)} and
+     * ensures that scan results from linked networks are used in the channel list and that the
+     * list size does not exceed the max configured for the device.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIncludesLinkedNetworksIsLimitedToConfiguredSize() {
+        // Need to recreate the WifiConfigManager instance for this test to modify the config
+        // value which is read only in the constructor.
+        int maxListSize = 3;
+        mResources.setInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
+                maxListSize);
+
+        createWifiConfigManager();
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        String test_bssid_base = "af:89:56:34:56:6";
+        int TEST_FREQ_LISTIdx = 0;
+        // Create 3 scan results with different bssid's & frequencies for network 1.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length / 2; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network1, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        // Create 3 scan results with different bssid's & frequencies for network 2.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network2, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+        }
+
+        // Link the 2 configurations together using the GwMacAddress.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+
+        // Ensure that the fetched list size is limited.
+        assertEquals(maxListSize,
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network1.networkId, 1, TEST_FREQ_LIST[0]).size());
+        assertEquals(maxListSize,
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network2.networkId, 1, TEST_FREQ_LIST[0]).size());
+    }
+
+    /**
+     * Verifies the foreground user switch using {@link WifiConfigManager#handleUserSwitch(int)}
+     * and ensures that any shared private networks networkId is not changed.
+     * Test scenario:
+     * 1. Load the shared networks from shared store and user 1 store.
+     * 2. Switch to user 2 and ensure that the shared network's Id is not changed.
+     */
+    @Test
+    public void testHandleUserSwitchDoesNotChangeSharedNetworksId() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        int appId = 674;
+
+        // Create 3 networks. 1 for user1, 1 for user2 and 1 shared.
+        final WifiConfiguration user1Network = WifiConfigurationTestUtil.createPskNetwork();
+        user1Network.shared = false;
+        user1Network.creatorUid = UserHandle.getUid(user1, appId);
+        final WifiConfiguration user2Network = WifiConfigurationTestUtil.createPskNetwork();
+        user2Network.shared = false;
+        user2Network.creatorUid = UserHandle.getUid(user2, appId);
+        final WifiConfiguration sharedNetwork1 = WifiConfigurationTestUtil.createPskNetwork();
+        final WifiConfiguration sharedNetwork2 = WifiConfigurationTestUtil.createPskNetwork();
+
+        // Set up the store data that is loaded initially.
+        List<WifiConfiguration> sharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(sharedNetwork1);
+                add(sharedNetwork2);
+            }
+        };
+        List<WifiConfiguration> user1Networks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user1Network);
+            }
+        };
+        setupStoreDataForRead(sharedNetworks, user1Networks, new HashSet<String>());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        // Fetch the network ID's assigned to the shared networks initially.
+        int sharedNetwork1Id = WifiConfiguration.INVALID_NETWORK_ID;
+        int sharedNetwork2Id = WifiConfiguration.INVALID_NETWORK_ID;
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        for (WifiConfiguration network : retrievedNetworks) {
+            if (network.configKey().equals(sharedNetwork1.configKey())) {
+                sharedNetwork1Id = network.networkId;
+            } else if (network.configKey().equals(sharedNetwork2.configKey())) {
+                sharedNetwork2Id = network.networkId;
+            }
+        }
+        assertTrue(sharedNetwork1Id != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(sharedNetwork2Id != WifiConfiguration.INVALID_NETWORK_ID);
+
+        // Set up the user 2 store data that is loaded at user switch.
+        List<WifiConfiguration> user2Networks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user2Network);
+            }
+        };
+        setupStoreDataForUserRead(user2Networks, new HashSet<String>());
+        // Now switch the user to user 2 and ensure that shared network's IDs have not changed.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        mWifiConfigManager.handleUserSwitch(user2);
+        verify(mWifiConfigStore).switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+
+        // Again fetch the network ID's assigned to the shared networks and ensure they have not
+        // changed.
+        int updatedSharedNetwork1Id = WifiConfiguration.INVALID_NETWORK_ID;
+        int updatedSharedNetwork2Id = WifiConfiguration.INVALID_NETWORK_ID;
+        retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        for (WifiConfiguration network : retrievedNetworks) {
+            if (network.configKey().equals(sharedNetwork1.configKey())) {
+                updatedSharedNetwork1Id = network.networkId;
+            } else if (network.configKey().equals(sharedNetwork2.configKey())) {
+                updatedSharedNetwork2Id = network.networkId;
+            }
+        }
+        assertEquals(sharedNetwork1Id, updatedSharedNetwork1Id);
+        assertEquals(sharedNetwork2Id, updatedSharedNetwork2Id);
+    }
+
+    /**
+     * Verifies the foreground user switch using {@link WifiConfigManager#handleUserSwitch(int)}
+     * and ensures that any old user private networks are not visible anymore.
+     * Test scenario:
+     * 1. Load the shared networks from shared store and user 1 store.
+     * 2. Switch to user 2 and ensure that the user 1's private network has been removed.
+     */
+    @Test
+    public void testHandleUserSwitchRemovesOldUserPrivateNetworks() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        int appId = 674;
+
+        // Create 3 networks. 1 for user1, 1 for user2 and 1 shared.
+        final WifiConfiguration user1Network = WifiConfigurationTestUtil.createPskNetwork();
+        user1Network.shared = false;
+        user1Network.creatorUid = UserHandle.getUid(user1, appId);
+        final WifiConfiguration user2Network = WifiConfigurationTestUtil.createPskNetwork();
+        user2Network.shared = false;
+        user2Network.creatorUid = UserHandle.getUid(user2, appId);
+        final WifiConfiguration sharedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+
+        // Set up the store data that is loaded initially.
+        List<WifiConfiguration> sharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(sharedNetwork);
+            }
+        };
+        List<WifiConfiguration> user1Networks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user1Network);
+            }
+        };
+        setupStoreDataForRead(sharedNetworks, user1Networks, new HashSet<String>());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        // Fetch the network ID assigned to the user 1 network initially.
+        int user1NetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        for (WifiConfiguration network : retrievedNetworks) {
+            if (network.configKey().equals(user1Network.configKey())) {
+                user1NetworkId = network.networkId;
+            }
+        }
+
+        // Set up the user 2 store data that is loaded at user switch.
+        List<WifiConfiguration> user2Networks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user2Network);
+            }
+        };
+        setupStoreDataForUserRead(user2Networks, new HashSet<String>());
+        // Now switch the user to user 2 and ensure that user 1's private network has been removed.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        Set<Integer> removedNetworks = mWifiConfigManager.handleUserSwitch(user2);
+        verify(mWifiConfigStore).switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        assertTrue((removedNetworks.size() == 1) && (removedNetworks.contains(user1NetworkId)));
+
+        // Set the expected networks to be |sharedNetwork| and |user2Network|.
+        List<WifiConfiguration> expectedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(sharedNetwork);
+                add(user2Network);
+            }
+        };
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                expectedNetworks, mWifiConfigManager.getConfiguredNetworksWithPasswords());
+
+        // Send another user switch  indication with the same user 2. This should be ignored and
+        // hence should not remove any new networks.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        removedNetworks = mWifiConfigManager.handleUserSwitch(user2);
+        assertTrue(removedNetworks.isEmpty());
+    }
+
+    /**
+     * Verifies the foreground user switch using {@link WifiConfigManager#handleUserSwitch(int)}
+     * and ensures that user switch from a user with no private networks is handled.
+     * Test scenario:
+     * 1. Load the shared networks from shared store and emptu user 1 store.
+     * 2. Switch to user 2 and ensure that no private networks were removed.
+     */
+    @Test
+    public void testHandleUserSwitchWithNoOldUserPrivateNetworks() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        int appId = 674;
+
+        // Create 2 networks. 1 for user2 and 1 shared.
+        final WifiConfiguration user2Network = WifiConfigurationTestUtil.createPskNetwork();
+        user2Network.shared = false;
+        user2Network.creatorUid = UserHandle.getUid(user2, appId);
+        final WifiConfiguration sharedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+
+        // Set up the store data that is loaded initially.
+        List<WifiConfiguration> sharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(sharedNetwork);
+            }
+        };
+        setupStoreDataForRead(sharedNetworks, new ArrayList<WifiConfiguration>(),
+                new HashSet<String>());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        // Set up the user 2 store data that is loaded at user switch.
+        List<WifiConfiguration> user2Networks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user2Network);
+            }
+        };
+        setupStoreDataForUserRead(user2Networks, new HashSet<String>());
+        // Now switch the user to user 2 and ensure that no private network has been removed.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        Set<Integer> removedNetworks = mWifiConfigManager.handleUserSwitch(user2);
+        verify(mWifiConfigStore).switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        assertTrue(removedNetworks.isEmpty());
+    }
+
+    /**
+     * Verifies the foreground user switch using {@link WifiConfigManager#handleUserSwitch(int)}
+     * and ensures that any non current user private networks are moved to shared store file.
+     * This test simulates the following test case:
+     * 1. Loads the shared networks from shared store at bootup.
+     * 2. Load the private networks from user store on user 1 unlock.
+     * 3. Switch to user 2 and ensure that the user 2's private network has been moved to user 2's
+     * private store file.
+     */
+    @Test
+    public void testHandleUserSwitchPushesOtherPrivateNetworksToSharedStore() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        int appId = 674;
+
+        // Create 3 networks. 1 for user1, 1 for user2 and 1 shared.
+        final WifiConfiguration user1Network = WifiConfigurationTestUtil.createPskNetwork();
+        user1Network.shared = false;
+        user1Network.creatorUid = UserHandle.getUid(user1, appId);
+        final WifiConfiguration user2Network = WifiConfigurationTestUtil.createPskNetwork();
+        user2Network.shared = false;
+        user2Network.creatorUid = UserHandle.getUid(user2, appId);
+        final WifiConfiguration sharedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+
+        // Set up the shared store data that is loaded at bootup. User 2's private network
+        // is still in shared store because they have not yet logged-in after upgrade.
+        List<WifiConfiguration> sharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(sharedNetwork);
+                add(user2Network);
+            }
+        };
+        setupStoreDataForRead(sharedNetworks, new ArrayList<WifiConfiguration>(),
+                new HashSet<String>());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+
+        // Set up the user store data that is loaded at user unlock.
+        List<WifiConfiguration> userNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user1Network);
+            }
+        };
+        setupStoreDataForUserRead(userNetworks, new HashSet<String>());
+        mWifiConfigManager.handleUserUnlock(user1);
+        verify(mWifiConfigStore).switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        // Capture the written data for the user 1 and ensure that it corresponds to what was
+        // setup.
+        Pair<List<WifiConfiguration>, List<WifiConfiguration>> writtenNetworkList =
+                captureWriteNetworksListStoreData();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                sharedNetworks, writtenNetworkList.first);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                userNetworks, writtenNetworkList.second);
+
+        // Now switch the user to user2 and ensure that user 2's private network has been moved to
+        // the user store.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        mWifiConfigManager.handleUserSwitch(user2);
+        // Set the expected network list before comparing. user1Network should be in shared data.
+        // Note: In the real world, user1Network will no longer be visible now because it should
+        // already be in user1's private store file. But, we're purposefully exposing it
+        // via |loadStoreData| to test if other user's private networks are pushed to shared store.
+        List<WifiConfiguration> expectedSharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(sharedNetwork);
+                add(user1Network);
+            }
+        };
+        List<WifiConfiguration> expectedUserNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(user2Network);
+            }
+        };
+        // Capture the first written data triggered for saving the old user's network
+        // configurations.
+        writtenNetworkList = captureWriteNetworksListStoreData();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                sharedNetworks, writtenNetworkList.first);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                userNetworks, writtenNetworkList.second);
+
+        // Now capture the next written data triggered after the switch and ensure that user 2's
+        // network is now in user store data.
+        writtenNetworkList = captureWriteNetworksListStoreData();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                expectedSharedNetworks, writtenNetworkList.first);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                expectedUserNetworks, writtenNetworkList.second);
+    }
+
+    /**
+     * Verify that unlocking an user that owns a legacy Passpoint configuration (which is stored
+     * temporarily in the share store) will migrate it to PasspointManager and removed from
+     * the list of configured networks.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testHandleUserUnlockRemovePasspointConfigFromSharedConfig() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int appId = 674;
+
+        final WifiConfiguration passpointConfig =
+                WifiConfigurationTestUtil.createPasspointNetwork();
+        passpointConfig.creatorUid = UserHandle.getUid(user1, appId);
+        passpointConfig.isLegacyPasspointConfig = true;
+
+        // Set up the shared store data to contain one legacy Passpoint configuration.
+        List<WifiConfiguration> sharedNetworks = new ArrayList<WifiConfiguration>() {
+            {
+                add(passpointConfig);
+            }
+        };
+        setupStoreDataForRead(sharedNetworks, new ArrayList<WifiConfiguration>(),
+                new HashSet<String>());
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore).read();
+        assertEquals(1, mWifiConfigManager.getConfiguredNetworks().size());
+
+        // Unlock the owner of the legacy Passpoint configuration, verify it is removed from
+        // the configured networks (migrated to PasspointManager).
+        setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+        mWifiConfigManager.handleUserUnlock(user1);
+        verify(mWifiConfigStore).switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        Pair<List<WifiConfiguration>, List<WifiConfiguration>> writtenNetworkList =
+                captureWriteNetworksListStoreData();
+        assertTrue(writtenNetworkList.first.isEmpty());
+        assertTrue(writtenNetworkList.second.isEmpty());
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+    }
+
+    /**
+     * Verifies the foreground user switch using {@link WifiConfigManager#handleUserSwitch(int)}
+     * and {@link WifiConfigManager#handleUserUnlock(int)} and ensures that the new store is
+     * read immediately if the user is unlocked during the switch.
+     */
+    @Test
+    public void testHandleUserSwitchWhenUnlocked() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        // Set up the internal data first.
+        assertTrue(mWifiConfigManager.loadFromStore());
+
+        setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+        // user2 is unlocked and switched to foreground.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        mWifiConfigManager.handleUserSwitch(user2);
+        // Ensure that the read was invoked.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore)
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+    }
+
+    /**
+     * Verifies the foreground user switch using {@link WifiConfigManager#handleUserSwitch(int)}
+     * and {@link WifiConfigManager#handleUserUnlock(int)} and ensures that the new store is not
+     * read until the user is unlocked.
+     */
+    public void testHandleUserSwitchWhenLocked() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        // Set up the internal data first.
+        assertTrue(mWifiConfigManager.loadFromStore());
+
+        // user2 is locked and switched to foreground.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(false);
+        mWifiConfigManager.handleUserSwitch(user2);
+
+        // Ensure that the read was not invoked.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+
+        // Now try unlocking some other user (user1), this should be ignored.
+        mWifiConfigManager.handleUserUnlock(user1);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+
+        setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+        // Unlock the user2 and ensure that we read the data now.
+        mWifiConfigManager.handleUserUnlock(user2);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore)
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+    }
+
+    /**
+     * Verifies that the foreground user stop using {@link WifiConfigManager#handleUserStop(int)}
+     * and ensures that the store is written only when the foreground user is stopped.
+     */
+    @Test
+    public void testHandleUserStop() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        // Try stopping background user2 first, this should not do anything.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(false);
+        mWifiConfigManager.handleUserStop(user2);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+
+        // Now try stopping the foreground user1, this should trigger a write to store.
+        mWifiConfigManager.handleUserStop(user1);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore).write(anyBoolean());
+    }
+
+    /**
+     * Verifies the foreground user unlock via {@link WifiConfigManager#handleUserUnlock(int)}
+     * results in a store read after bootup.
+     */
+    @Test
+    public void testHandleUserUnlockAfterBootup() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+
+        // Set up the internal data first.
+        assertTrue(mWifiConfigManager.loadFromStore());
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore).read();
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+
+        setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+        // Unlock the user1 (default user) for the first time and ensure that we read the data.
+        mWifiConfigManager.handleUserUnlock(user1);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).read();
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore)
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore).write(anyBoolean());
+    }
+
+    /**
+     * Verifies that the store read after bootup received after
+     * foreground user unlock via {@link WifiConfigManager#handleUserUnlock(int)}
+     * results in a user store read.
+     */
+    @Test
+    public void testHandleBootupAfterUserUnlock() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+
+        // Unlock the user1 (default user) for the first time and ensure that we don't read the
+        // data.
+        mWifiConfigManager.handleUserUnlock(user1);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).read();
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+
+        setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+        // Read from store now.
+        assertTrue(mWifiConfigManager.loadFromStore());
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore)
+                .setUserStore(any(WifiConfigStore.StoreFile.class));
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore).read();
+    }
+
+    /**
+     * Verifies the foreground user unlock via {@link WifiConfigManager#handleUserUnlock(int)} does
+     * not always result in a store read unless the user had switched or just booted up.
+     */
+    @Test
+    public void testHandleUserUnlockWithoutSwitchOrBootup() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        // Set up the internal data first.
+        assertTrue(mWifiConfigManager.loadFromStore());
+
+        setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+        // user2 is unlocked and switched to foreground.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        mWifiConfigManager.handleUserSwitch(user2);
+        // Ensure that the read was invoked.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore)
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+
+        // Unlock the user2 again and ensure that we don't read the data now.
+        mWifiConfigManager.handleUserUnlock(user2);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+    }
+
+    /**
+     * Verifies the foreground user unlock via {@link WifiConfigManager#handleUserSwitch(int)}
+     * is ignored if the legacy store migration is not complete.
+     */
+    @Test
+    public void testHandleUserSwitchAfterBootupBeforeLegacyStoreMigration() throws Exception {
+        int user2 = TEST_DEFAULT_USER + 1;
+
+        // Switch to user2 for the first time and ensure that we don't read or
+        // write the store files.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(false);
+        mWifiConfigManager.handleUserSwitch(user2);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+    }
+
+    /**
+     * Verifies the foreground user unlock via {@link WifiConfigManager#handleUserUnlock(int)}
+     * is ignored if the legacy store migration is not complete.
+     */
+    @Test
+    public void testHandleUserUnlockAfterBootupBeforeLegacyStoreMigration() throws Exception {
+        int user1 = TEST_DEFAULT_USER;
+
+        // Unlock the user1 (default user) for the first time and ensure that we don't read or
+        // write the store files.
+        mWifiConfigManager.handleUserUnlock(user1);
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never())
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+    }
+
+    /**
+     * Verifies the private network addition using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     * by a non foreground user is rejected.
+     */
+    @Test
+    public void testAddNetworkUsingBackgroundUserUId() throws Exception {
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        int creatorUid = UserHandle.getUid(user2, 674);
+
+        // Create a network for user2 try adding it. This should be rejected.
+        final WifiConfiguration user2Network = WifiConfigurationTestUtil.createPskNetwork();
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(user2Network, creatorUid);
+        assertFalse(result.isSuccess());
+    }
+
+    /**
+     * Verifies the private network addition using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)}
+     * by SysUI is always accepted.
+     */
+    @Test
+    public void testAddNetworkUsingSysUiUid() throws Exception {
+        // Set up the user profiles stuff. Needed for |WifiConfigurationUtil.isVisibleToAnyProfile|
+        int user2 = TEST_DEFAULT_USER + 1;
+        setupUserProfiles(user2);
+
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(false);
+        mWifiConfigManager.handleUserSwitch(user2);
+
+        // Create a network for user2 try adding it. This should be rejected.
+        final WifiConfiguration user2Network = WifiConfigurationTestUtil.createPskNetwork();
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(user2Network, TEST_SYSUI_UID);
+        assertTrue(result.isSuccess());
+    }
+
+    /**
+     * Verifies the loading of networks using {@link WifiConfigManager#migrateFromLegacyStore()} ()}
+     * attempts to migrate data from legacy stores when the legacy store files are present.
+     */
+    @Test
+    public void testMigrationFromLegacyStore() throws Exception {
+        // Create the store data to be returned from legacy stores.
+        List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(WifiConfigurationTestUtil.createPskNetwork());
+        networks.add(WifiConfigurationTestUtil.createEapNetwork());
+        networks.add(WifiConfigurationTestUtil.createWepNetwork());
+        String deletedEphemeralSSID = "EphemeralSSID";
+        Set<String> deletedEphermalSSIDs = new HashSet<>(Arrays.asList(deletedEphemeralSSID));
+        WifiConfigStoreDataLegacy storeData =
+                new WifiConfigStoreDataLegacy(networks, deletedEphermalSSIDs);
+
+        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(true);
+        when(mWifiConfigStoreLegacy.read()).thenReturn(storeData);
+
+        // Now trigger the migration from legacy store. This should populate the in memory list with
+        // all the networks above from the legacy store.
+        assertTrue(mWifiConfigManager.migrateFromLegacyStore());
+
+        verify(mWifiConfigStoreLegacy).read();
+        verify(mWifiConfigStoreLegacy).removeStores();
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+        assertTrue(mWifiConfigManager.wasEphemeralNetworkDeleted(deletedEphemeralSSID));
+    }
+
+    /**
+     * Verifies the loading of networks using {@link WifiConfigManager#migrateFromLegacyStore()} ()}
+     * does not attempt to migrate data from legacy stores when the legacy store files are absent
+     * (i.e migration was already done once).
+     */
+    @Test
+    public void testNoDuplicateMigrationFromLegacyStore() throws Exception {
+        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(false);
+
+        // Now trigger a migration from legacy store.
+        assertTrue(mWifiConfigManager.migrateFromLegacyStore());
+
+        verify(mWifiConfigStoreLegacy, never()).read();
+        verify(mWifiConfigStoreLegacy, never()).removeStores();
+    }
+
+    /**
+     * Verifies the loading of networks using {@link WifiConfigManager#loadFromStore()} does
+     * not attempt to read from any of the stores (new or legacy) when the store files are
+     * not present.
+     */
+    @Test
+    public void testFreshInstallDoesNotLoadFromStore() throws Exception {
+        when(mWifiConfigStore.areStoresPresent()).thenReturn(false);
+        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(false);
+
+        assertTrue(mWifiConfigManager.loadFromStore());
+
+        verify(mWifiConfigStore, never()).read();
+        verify(mWifiConfigStoreLegacy, never()).read();
+
+        assertTrue(mWifiConfigManager.getConfiguredNetworksWithPasswords().isEmpty());
+    }
+
+    /**
+     * Verifies the user switch using {@link WifiConfigManager#handleUserSwitch(int)} is handled
+     * when the store files (new or legacy) are not present.
+     */
+    @Test
+    public void testHandleUserSwitchAfterFreshInstall() throws Exception {
+        int user2 = TEST_DEFAULT_USER + 1;
+        when(mWifiConfigStore.areStoresPresent()).thenReturn(false);
+        when(mWifiConfigStoreLegacy.areStoresPresent()).thenReturn(false);
+
+        assertTrue(mWifiConfigManager.loadFromStore());
+        verify(mWifiConfigStore, never()).read();
+        verify(mWifiConfigStoreLegacy, never()).read();
+
+        setupStoreDataForUserRead(new ArrayList<WifiConfiguration>(), new HashSet<String>());
+        // Now switch the user to user 2.
+        when(mUserManager.isUserUnlockingOrUnlocked(user2)).thenReturn(true);
+        mWifiConfigManager.handleUserSwitch(user2);
+        // Ensure that the read was invoked.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore)
+                .switchUserStoreAndRead(any(WifiConfigStore.StoreFile.class));
+    }
+
+    /**
+     * Verifies that the last user selected network parameter is set when
+     * {@link WifiConfigManager#enableNetwork(int, boolean, int)} with disableOthers flag is set
+     * to true and cleared when either {@link WifiConfigManager#disableNetwork(int, int)} or
+     * {@link WifiConfigManager#removeNetwork(int, int)} is invoked using the same network ID.
+     */
+    @Test
+    public void testLastSelectedNetwork() throws Exception {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(67L);
+        assertTrue(mWifiConfigManager.enableNetwork(
+                result.getNetworkId(), true, TEST_CREATOR_UID));
+        assertEquals(result.getNetworkId(), mWifiConfigManager.getLastSelectedNetwork());
+        assertEquals(67, mWifiConfigManager.getLastSelectedTimeStamp());
+
+        // Now disable the network and ensure that the last selected flag is cleared.
+        assertTrue(mWifiConfigManager.disableNetwork(result.getNetworkId(), TEST_CREATOR_UID));
+        assertEquals(
+                WifiConfiguration.INVALID_NETWORK_ID, mWifiConfigManager.getLastSelectedNetwork());
+
+        // Enable it again and remove the network to ensure that the last selected flag was cleared.
+        assertTrue(mWifiConfigManager.enableNetwork(
+                result.getNetworkId(), true, TEST_CREATOR_UID));
+        assertEquals(result.getNetworkId(), mWifiConfigManager.getLastSelectedNetwork());
+        assertEquals(openNetwork.configKey(), mWifiConfigManager.getLastSelectedNetworkConfigKey());
+
+        assertTrue(mWifiConfigManager.removeNetwork(result.getNetworkId(), TEST_CREATOR_UID));
+        assertEquals(
+                WifiConfiguration.INVALID_NETWORK_ID, mWifiConfigManager.getLastSelectedNetwork());
+    }
+
+    /**
+     * Verifies that all the networks for the provided app is removed when
+     * {@link WifiConfigManager#removeNetworksForApp(ApplicationInfo)} is invoked.
+     */
+    @Test
+    public void testRemoveNetworksForApp() throws Exception {
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createPskNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createWepNetwork());
+
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+
+        ApplicationInfo app = new ApplicationInfo();
+        app.uid = TEST_CREATOR_UID;
+        app.packageName = TEST_CREATOR_NAME;
+        assertEquals(3, mWifiConfigManager.removeNetworksForApp(app).size());
+
+        // Ensure all the networks are removed now.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+    }
+
+    /**
+     * Verifies that all the networks for the provided user is removed when
+     * {@link WifiConfigManager#removeNetworksForUser(int)} is invoked.
+     */
+    @Test
+    public void testRemoveNetworksForUser() throws Exception {
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createOpenNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createPskNetwork());
+        verifyAddNetworkToWifiConfigManager(WifiConfigurationTestUtil.createWepNetwork());
+
+        assertFalse(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+
+        assertEquals(3, mWifiConfigManager.removeNetworksForUser(TEST_DEFAULT_USER).size());
+
+        // Ensure all the networks are removed now.
+        assertTrue(mWifiConfigManager.getConfiguredNetworks().isEmpty());
+    }
+
+    /**
+     * Verifies that the connect choice is removed from all networks when
+     * {@link WifiConfigManager#removeNetwork(int, int)} is invoked.
+     */
+    @Test
+    public void testRemoveNetworkRemovesConnectChoice() throws Exception {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
+
+        // Set connect choice of network 2 over network 1.
+        assertTrue(
+                mWifiConfigManager.setNetworkConnectChoice(
+                        network1.networkId, network2.configKey(), 78L));
+
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(network1.networkId);
+        assertEquals(
+                network2.configKey(),
+                retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
+
+        // Remove network 3 and ensure that the connect choice on network 1 is not removed.
+        assertTrue(mWifiConfigManager.removeNetwork(network3.networkId, TEST_CREATOR_UID));
+        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(network1.networkId);
+        assertEquals(
+                network2.configKey(),
+                retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
+
+        // Now remove network 2 and ensure that the connect choice on network 1 is removed..
+        assertTrue(mWifiConfigManager.removeNetwork(network2.networkId, TEST_CREATOR_UID));
+        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(network1.networkId);
+        assertNotEquals(
+                network2.configKey(),
+                retrievedNetwork.getNetworkSelectionStatus().getConnectChoice());
+
+        // This should have triggered 2 buffered writes. 1 for setting the connect choice, 1 for
+        // clearing it after network removal.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, times(2)).write(eq(false));
+    }
+
+    /**
+     * Verifies that the modification of a single network using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} and ensures that any
+     * updates to the network config in
+     * {@link WifiKeyStore#updateNetworkKeys(WifiConfiguration, WifiConfiguration)} is reflected
+     * in the internal database.
+     */
+    @Test
+    public void testUpdateSingleNetworkWithKeysUpdate() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createEapNetwork();
+        network.enterpriseConfig =
+                WifiConfigurationTestUtil.createPEAPWifiEnterpriseConfigWithGTCPhase2();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Now verify that network configurations match before we make any change.
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network,
+                mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
+
+        // Modify the network ca_cert field in updateNetworkKeys method during a network
+        // config update.
+        final String newCaCertAlias = "test";
+        assertNotEquals(newCaCertAlias, network.enterpriseConfig.getCaCertificateAlias());
+
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(WifiConfiguration newConfig, WifiConfiguration existingConfig) {
+                newConfig.enterpriseConfig.setCaCertificateAlias(newCaCertAlias);
+                return true;
+            }
+        }).when(mWifiKeyStore).updateNetworkKeys(
+                any(WifiConfiguration.class), any(WifiConfiguration.class));
+
+        verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+
+        // Now verify that the keys update is reflected in the configuration fetched from internal
+        // db.
+        network.enterpriseConfig.setCaCertificateAlias(newCaCertAlias);
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network,
+                mWifiConfigManager.getConfiguredNetworkWithPassword(network.networkId));
+    }
+
+    /**
+     * Verifies that the dump method prints out all the saved network details with passwords masked.
+     * {@link WifiConfigManager#dump(FileDescriptor, PrintWriter, String[])}.
+     */
+    @Test
+    public void testDump() {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        eapNetwork.enterpriseConfig.setPassword("blah");
+
+        verifyAddNetworkToWifiConfigManager(pskNetwork);
+        verifyAddNetworkToWifiConfigManager(eapNetwork);
+
+        StringWriter stringWriter = new StringWriter();
+        mWifiConfigManager.dump(
+                new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+        String dumpString = stringWriter.toString();
+
+        // Ensure that the network SSIDs were dumped out.
+        assertTrue(dumpString.contains(pskNetwork.SSID));
+        assertTrue(dumpString.contains(eapNetwork.SSID));
+
+        // Ensure that the network passwords were not dumped out.
+        assertFalse(dumpString.contains(pskNetwork.preSharedKey));
+        assertFalse(dumpString.contains(eapNetwork.enterpriseConfig.getPassword()));
+    }
+
+    /**
+     * Verifies the ordering of network list generated using
+     * {@link WifiConfigManager#retrieveHiddenNetworkList()}.
+     */
+    @Test
+    public void testRetrieveHiddenList() {
+        // Create and add 3 networks.
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createWepHiddenNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskHiddenNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
+
+        // Enable all of them.
+        assertTrue(mWifiConfigManager.enableNetwork(network1.networkId, false, TEST_CREATOR_UID));
+        assertTrue(mWifiConfigManager.enableNetwork(network2.networkId, false, TEST_CREATOR_UID));
+        assertTrue(mWifiConfigManager.enableNetwork(network3.networkId, false, TEST_CREATOR_UID));
+
+        // Now set scan results in 2 of them to set the corresponding
+        // {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} field.
+        assertTrue(mWifiConfigManager.setNetworkCandidateScanResult(
+                network1.networkId, createScanDetailForNetwork(network1).getScanResult(), 54));
+        assertTrue(mWifiConfigManager.setNetworkCandidateScanResult(
+                network3.networkId, createScanDetailForNetwork(network3).getScanResult(), 54));
+
+        // Now increment |network3|'s association count. This should ensure that this network
+        // is preferred over |network1|.
+        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(network3.networkId));
+
+        // Retrieve the hidden network list & verify the order of the networks returned.
+        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks =
+                mWifiConfigManager.retrieveHiddenNetworkList();
+        assertEquals(3, hiddenNetworks.size());
+        assertEquals(network3.SSID, hiddenNetworks.get(0).ssid);
+        assertEquals(network1.SSID, hiddenNetworks.get(1).ssid);
+        assertEquals(network2.SSID, hiddenNetworks.get(2).ssid);
+
+        // Now permanently disable |network3|. This should remove network 3 from the list.
+        assertTrue(mWifiConfigManager.disableNetwork(network3.networkId, TEST_CREATOR_UID));
+
+        // Retrieve the hidden network list again & verify the order of the networks returned.
+        hiddenNetworks = mWifiConfigManager.retrieveHiddenNetworkList();
+        assertEquals(2, hiddenNetworks.size());
+        assertEquals(network1.SSID, hiddenNetworks.get(0).ssid);
+        assertEquals(network2.SSID, hiddenNetworks.get(1).ssid);
+    }
+
+    /**
+     * Verifies the addition of network configurations using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} with same SSID and
+     * default key mgmt does not add duplicate network configs.
+     */
+    @Test
+    public void testAddMultipleNetworksWithSameSSIDAndDefaultKeyMgmt() {
+        final String ssid = "test_blah";
+        // Add a network with the above SSID and default key mgmt and ensure it was added
+        // successfully.
+        WifiConfiguration network1 = new WifiConfiguration();
+        network1.SSID = ssid;
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(network1);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(1, retrievedNetworks.size());
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network1, retrievedNetworks.get(0));
+
+        // Now add a second network with the same SSID and default key mgmt and ensure that it
+        // didn't add a new duplicate network.
+        WifiConfiguration network2 = new WifiConfiguration();
+        network2.SSID = ssid;
+        result = addNetworkToWifiConfigManager(network2);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertFalse(result.isNewNetwork());
+
+        retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(1, retrievedNetworks.size());
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network2, retrievedNetworks.get(0));
+    }
+
+    /**
+     * Verifies the addition of network configurations using
+     * {@link WifiConfigManager#addOrUpdateNetwork(WifiConfiguration, int)} with same SSID and
+     * different key mgmt should add different network configs.
+     */
+    @Test
+    public void testAddMultipleNetworksWithSameSSIDAndDifferentKeyMgmt() {
+        final String ssid = "test_blah";
+        // Add a network with the above SSID and WPA_PSK key mgmt and ensure it was added
+        // successfully.
+        WifiConfiguration network1 = new WifiConfiguration();
+        network1.SSID = ssid;
+        network1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(network1);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(1, retrievedNetworks.size());
+        WifiConfigurationTestUtil.assertConfigurationEqualForConfigManagerAddOrUpdate(
+                network1, retrievedNetworks.get(0));
+
+        // Now add a second network with the same SSID and NONE key mgmt and ensure that it
+        // does add a new network.
+        WifiConfiguration network2 = new WifiConfiguration();
+        network2.SSID = ssid;
+        network2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        result = addNetworkToWifiConfigManager(network2);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+
+        retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords();
+        assertEquals(2, retrievedNetworks.size());
+        List<WifiConfiguration> networks = Arrays.asList(network1, network2);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigManagerAddOrUpdate(
+                networks, retrievedNetworks);
+    }
+
+    /**
+     * Verifies that adding a network with a proxy, without having permission OVERRIDE_WIFI_CONFIG,
+     * holding device policy, or profile owner policy fails.
+     */
+    @Test
+    public void testAddNetworkWithProxyFails() {
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                false, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
+                false, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+    }
+
+    /**
+     * Verifies that adding a network with a PAC or STATIC proxy with permission
+     * OVERRIDE_WIFI_CONFIG is successful
+     */
+    @Test
+    public void testAddNetworkWithProxyWithConfOverride() {
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                true,  // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                true,  // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+    }
+
+    /**
+     * Verifies that adding a network with a PAC or STATIC proxy, while holding policy
+     * {@link DeviceAdminInfo.USES_POLICY_PROFILE_OWNER} is successful
+     */
+    @Test
+    public void testAddNetworkWithProxyAsProfileOwner() {
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false,  // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false,  // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+    }
+    /**
+     * Verifies that adding a network with a PAC or STATIC proxy, while holding policy
+     * {@link DeviceAdminInfo.USES_POLICY_DEVICE_OWNER} is successful
+     */
+    @Test
+    public void testAddNetworkWithProxyAsDeviceOwner() {
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false,  // withConfOverride
+                false, // withProfileOwnerPolicy
+                true, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false,  // withConfOverride
+                false, // withProfileOwnerPolicy
+                true, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+    }
+    /**
+     * Verifies that updating a network (that has no proxy) and adding a PAC or STATIC proxy fails
+     * without being able to override configs, or holding Device or Profile owner policies.
+     */
+    @Test
+    public void testUpdateNetworkAddProxyFails() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(network);
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                false, // assertSuccess
+                result.getNetworkId()); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithStaticProxy(),
+                false, // assertSuccess
+                result.getNetworkId()); // Update networkID
+    }
+    /**
+     * Verifies that updating a network and adding a proxy is successful in the cases where app can
+     * override configs, holds policy {@link DeviceAdminInfo.USES_POLICY_PROFILE_OWNER},
+     * and holds policy {@link DeviceAdminInfo.USES_POLICY_DEVICE_OWNER}, and that it fails
+     * otherwise.
+     */
+    @Test
+    public void testUpdateNetworkAddProxyWithPermissionAndSystem() {
+        // Testing updating network with uid permission OVERRIDE_WIFI_CONFIG
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(network, TEST_CREATOR_UID);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                true, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Testing updating network with proxy while holding Profile Owner policy
+        network = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        result = addNetworkToWifiConfigManager(network, TEST_NO_PERM_UID);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Testing updating network with proxy while holding Device Owner Policy
+        network = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        result = addNetworkToWifiConfigManager(network, TEST_NO_PERM_UID);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                true, // withDeviceOwnerPolicy
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy(),
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+    }
+
+    /**
+     * Verifies that updating a network that has a proxy without changing the proxy, can succeed
+     * without proxy specific permissions.
+     */
+    @Test
+    public void testUpdateNetworkUnchangedProxy() {
+        IpConfiguration ipConf = WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy();
+        // First create a WifiConfiguration with proxy
+        NetworkUpdateResult result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                        false, // withConfOverride
+                        true, // withProfileOwnerPolicy
+                        false, // withDeviceOwnerPolicy
+                        ipConf,
+                        true, // assertSuccess
+                        WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        // Update the network while using the same ipConf, and no proxy specific permissions
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                        false, // withConfOverride
+                        false, // withProfileOwnerPolicy
+                        false, // withDeviceOwnerPolicy
+                        ipConf,
+                        true, // assertSuccess
+                        result.getNetworkId()); // Update networkID
+    }
+
+    /**
+     * Verifies that updating a network with a different proxy succeeds in the cases where app can
+     * override configs, holds policy {@link DeviceAdminInfo.USES_POLICY_PROFILE_OWNER},
+     * and holds policy {@link DeviceAdminInfo.USES_POLICY_DEVICE_OWNER}, and that it fails
+     * otherwise.
+     */
+    @Test
+    public void testUpdateNetworkDifferentProxy() {
+        // Create two proxy configurations of the same type, but different values
+        IpConfiguration ipConf1 =
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithSpecificProxy(
+                        WifiConfigurationTestUtil.STATIC_PROXY_SETTING,
+                        TEST_STATIC_PROXY_HOST_1,
+                        TEST_STATIC_PROXY_PORT_1,
+                        TEST_STATIC_PROXY_EXCLUSION_LIST_1,
+                        TEST_PAC_PROXY_LOCATION_1);
+        IpConfiguration ipConf2 =
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithSpecificProxy(
+                        WifiConfigurationTestUtil.STATIC_PROXY_SETTING,
+                        TEST_STATIC_PROXY_HOST_2,
+                        TEST_STATIC_PROXY_PORT_2,
+                        TEST_STATIC_PROXY_EXCLUSION_LIST_2,
+                        TEST_PAC_PROXY_LOCATION_2);
+
+        // Update with Conf Override
+        NetworkUpdateResult result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                true, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                true, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf2,
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Update as Device Owner
+        result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                true, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                true, // withDeviceOwnerPolicy
+                ipConf2,
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Update as Profile Owner
+        result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf2,
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Update with no permissions (should fail)
+        result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf2,
+                false, // assertSuccess
+                result.getNetworkId()); // Update networkID
+    }
+    /**
+     * Verifies that updating a network removing its proxy succeeds in the cases where app can
+     * override configs, holds policy {@link DeviceAdminInfo.USES_POLICY_PROFILE_OWNER},
+     * and holds policy {@link DeviceAdminInfo.USES_POLICY_DEVICE_OWNER}, and that it fails
+     * otherwise.
+     */
+    @Test
+    public void testUpdateNetworkRemoveProxy() {
+        // Create two different IP configurations, one with a proxy and another without.
+        IpConfiguration ipConf1 =
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithSpecificProxy(
+                        WifiConfigurationTestUtil.STATIC_PROXY_SETTING,
+                        TEST_STATIC_PROXY_HOST_1,
+                        TEST_STATIC_PROXY_PORT_1,
+                        TEST_STATIC_PROXY_EXCLUSION_LIST_1,
+                        TEST_PAC_PROXY_LOCATION_1);
+        IpConfiguration ipConf2 =
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithSpecificProxy(
+                        WifiConfigurationTestUtil.NONE_PROXY_SETTING,
+                        TEST_STATIC_PROXY_HOST_2,
+                        TEST_STATIC_PROXY_PORT_2,
+                        TEST_STATIC_PROXY_EXCLUSION_LIST_2,
+                        TEST_PAC_PROXY_LOCATION_2);
+
+        // Update with Conf Override
+        NetworkUpdateResult result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                true, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                true, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf2,
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Update as Device Owner
+        result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                true, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                true, // withDeviceOwnerPolicy
+                ipConf2,
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Update as Profile Owner
+        result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf2,
+                true, // assertSuccess
+                result.getNetworkId()); // Update networkID
+
+        // Update with no permissions (should fail)
+        result = verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                true, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf1,
+                true, // assertSuccess
+                WifiConfiguration.INVALID_NETWORK_ID); // Update networkID
+        verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+                false, // withConfOverride
+                false, // withProfileOwnerPolicy
+                false, // withDeviceOwnerPolicy
+                ipConf2,
+                false, // assertSuccess
+                result.getNetworkId()); // Update networkID
+    }
+
+    /**
+     * Verifies that the app specified BSSID is converted and saved in lower case.
+     */
+    @Test
+    public void testAppSpecifiedBssidIsSavedInLowerCase() {
+        final String bssid = "0A:08:5C:BB:89:6D"; // upper case
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.BSSID = bssid;
+
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+
+        WifiConfiguration retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(
+                result.getNetworkId());
+
+        assertNotEquals(retrievedNetwork.BSSID, bssid);
+        assertEquals(retrievedNetwork.BSSID, bssid.toLowerCase());
+    }
+
+    private NetworkUpdateResult verifyAddOrUpdateNetworkWithProxySettingsAndPermissions(
+            boolean withConfOverride,
+            boolean withProfileOwnerPolicy,
+            boolean withDeviceOwnerPolicy,
+            IpConfiguration ipConfiguration,
+            boolean assertSuccess,
+            int networkId) {
+        WifiConfiguration network;
+        if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
+            network = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        } else {
+            network = mWifiConfigManager.getConfiguredNetwork(networkId);
+        }
+        network.setIpConfiguration(ipConfiguration);
+        when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(anyInt(),
+                eq(DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)))
+                .thenReturn(withProfileOwnerPolicy);
+        when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(anyInt(),
+                eq(DeviceAdminInfo.USES_POLICY_DEVICE_OWNER)))
+                .thenReturn(withDeviceOwnerPolicy);
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt()))
+                .thenReturn(withConfOverride);
+        int uid = withConfOverride ? TEST_CREATOR_UID : TEST_NO_PERM_UID;
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(network, uid);
+        assertEquals(assertSuccess, result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        return result;
+    }
+
+    private void createWifiConfigManager() {
+        mWifiConfigManager =
+                new WifiConfigManager(
+                        mContext, mClock, mUserManager, mTelephonyManager,
+                        mWifiKeyStore, mWifiConfigStore, mWifiConfigStoreLegacy,
+                        mWifiPermissionsUtil, mWifiPermissionsWrapper, mNetworkListStoreData,
+                        mDeletedEphemeralSsidsStoreData);
+        mWifiConfigManager.enableVerboseLogging(1);
+    }
+
+    /**
+     * 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);
+        }
+        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);
+        }
+        configuration.status = WifiConfiguration.Status.DISABLED;
+        configuration.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+    }
+
+    /**
+     * 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 =
+                WifiConfigManager.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 =
+                WifiConfigManager.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);
+    }
+
+    /**
+     * Helper method to capture the networks list store data that will be written by
+     * WifiConfigStore.write() method.
+     */
+    private Pair<List<WifiConfiguration>, List<WifiConfiguration>>
+            captureWriteNetworksListStoreData() {
+        try {
+            ArgumentCaptor<ArrayList> sharedConfigsCaptor =
+                    ArgumentCaptor.forClass(ArrayList.class);
+            ArgumentCaptor<ArrayList> userConfigsCaptor =
+                    ArgumentCaptor.forClass(ArrayList.class);
+            mNetworkListStoreDataMockOrder.verify(mNetworkListStoreData)
+                    .setSharedConfigurations(sharedConfigsCaptor.capture());
+            mNetworkListStoreDataMockOrder.verify(mNetworkListStoreData)
+                    .setUserConfigurations(userConfigsCaptor.capture());
+            mContextConfigStoreMockOrder.verify(mWifiConfigStore).write(anyBoolean());
+            return Pair.create(sharedConfigsCaptor.getValue(), userConfigsCaptor.getValue());
+        } catch (Exception e) {
+            fail("Exception encountered during write " + e);
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether the provided network was in the store data or not.
+     */
+    private boolean isNetworkInConfigStoreData(WifiConfiguration configuration) {
+        Pair<List<WifiConfiguration>, List<WifiConfiguration>> networkListStoreData =
+                captureWriteNetworksListStoreData();
+        if (networkListStoreData == null) {
+            return false;
+        }
+        List<WifiConfiguration> networkList = new ArrayList<>();
+        networkList.addAll(networkListStoreData.first);
+        networkList.addAll(networkListStoreData.second);
+        return isNetworkInConfigStoreData(configuration, networkList);
+    }
+
+    /**
+     * Returns whether the provided network was in the store data or not.
+     */
+    private boolean isNetworkInConfigStoreData(
+            WifiConfiguration configuration, List<WifiConfiguration> networkList) {
+        boolean foundNetworkInStoreData = false;
+        for (WifiConfiguration retrievedConfig : networkList) {
+            if (retrievedConfig.configKey().equals(configuration.configKey())) {
+                foundNetworkInStoreData = true;
+                break;
+            }
+        }
+        return foundNetworkInStoreData;
+    }
+
+    /**
+     * Setup expectations for WifiNetworksListStoreData and DeletedEphemeralSsidsStoreData
+     * after WifiConfigStore#read.
+     */
+    private void setupStoreDataForRead(List<WifiConfiguration> sharedConfigurations,
+            List<WifiConfiguration> userConfigurations, Set<String> deletedEphemeralSsids) {
+        when(mNetworkListStoreData.getSharedConfigurations())
+                .thenReturn(sharedConfigurations);
+        when(mNetworkListStoreData.getUserConfigurations()).thenReturn(userConfigurations);
+        when(mDeletedEphemeralSsidsStoreData.getSsidList()).thenReturn(deletedEphemeralSsids);
+    }
+
+    /**
+     * Setup expectations for WifiNetworksListStoreData and DeletedEphemeralSsidsStoreData
+     * after WifiConfigStore#switchUserStoreAndRead.
+     */
+    private void setupStoreDataForUserRead(List<WifiConfiguration> userConfigurations,
+            Set<String> deletedEphemeralSsids) {
+        when(mNetworkListStoreData.getUserConfigurations()).thenReturn(userConfigurations);
+        when(mDeletedEphemeralSsidsStoreData.getSsidList()).thenReturn(deletedEphemeralSsids);
+    }
+
+    /**
+     * 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(WifiConfigManager.PASSWORD_MASK, configuration.preSharedKey);
+        }
+        if (configuration.wepKeys != null) {
+            for (int i = 0; i < configuration.wepKeys.length; i++) {
+                if (!TextUtils.isEmpty(configuration.wepKeys[i])) {
+                    assertEquals(WifiConfigManager.PASSWORD_MASK, configuration.wepKeys[i]);
+                }
+            }
+        }
+        if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
+            assertEquals(
+                    WifiConfigManager.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);
+    }
+
+    private void verifyWifiConfigStoreRead() {
+        assertTrue(mWifiConfigManager.loadFromStore());
+        mContextConfigStoreMockOrder.verify(mContext)
+                .sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
+    }
+
+    private void triggerStoreReadIfNeeded() {
+        // Trigger a store read if not already done.
+        if (!mStoreReadTriggered) {
+            verifyWifiConfigStoreRead();
+            mStoreReadTriggered = true;
+        }
+    }
+
+    /**
+     * Adds the provided configuration to WifiConfigManager with uid = TEST_CREATOR_UID.
+     */
+    private NetworkUpdateResult addNetworkToWifiConfigManager(WifiConfiguration configuration) {
+        return addNetworkToWifiConfigManager(configuration, TEST_CREATOR_UID);
+    }
+
+    /**
+     * 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.
+     * This method also triggers a store read if not already done.
+     */
+    private NetworkUpdateResult addNetworkToWifiConfigManager(WifiConfiguration configuration,
+                                                              int uid) {
+        triggerStoreReadIfNeeded();
+        when(mClock.getWallClockMillis()).thenReturn(TEST_WALLCLOCK_CREATION_TIME_MILLIS);
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(configuration, 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) throws Exception {
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(configuration);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+        assertTrue(result.hasIpChanged());
+        assertTrue(result.hasProxyChanged());
+
+        verifyNetworkAddBroadcast(configuration);
+        // Ensure that the write was not invoked for ephemeral network addition.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+        return result;
+    }
+
+    /**
+     * Add Passpoint network to WifiConfigManager and ensure that it was successful.
+     */
+    private NetworkUpdateResult verifyAddPasspointNetworkToWifiConfigManager(
+            WifiConfiguration configuration) throws Exception {
+        NetworkUpdateResult result = addNetworkToWifiConfigManager(configuration);
+        assertTrue(result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID);
+        assertTrue(result.isNewNetwork());
+        assertTrue(result.hasIpChanged());
+        assertTrue(result.hasProxyChanged());
+
+        // Verify keys are not being installed.
+        verify(mWifiKeyStore, never()).updateNetworkKeys(any(WifiConfiguration.class),
+                any(WifiConfiguration.class));
+        verifyNetworkAddBroadcast(configuration);
+        // Ensure that the write was not invoked for Passpoint network addition.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+        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, TEST_CREATOR_UID));
+
+        verifyNetworkRemoveBroadcast(configuration);
+        // Verify if the config store write was triggered without this new configuration.
+        verifyNetworkNotInConfigStoreData(configuration);
+    }
+
+    /**
+     * Removes ephemeral network from WifiConfigManager and ensure that it was successful.
+     */
+    private void verifyRemoveEphemeralNetworkFromWifiConfigManager(
+            WifiConfiguration configuration) throws Exception {
+        assertTrue(mWifiConfigManager.removeNetwork(configuration.networkId, TEST_CREATOR_UID));
+
+        verifyNetworkRemoveBroadcast(configuration);
+        // Ensure that the write was not invoked for ephemeral network remove.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+    }
+
+    /**
+     * Removes Passpoint network from WifiConfigManager and ensure that it was successful.
+     */
+    private void verifyRemovePasspointNetworkFromWifiConfigManager(
+            WifiConfiguration configuration) throws Exception {
+        assertTrue(mWifiConfigManager.removeNetwork(configuration.networkId, TEST_CREATOR_UID));
+
+        // Verify keys are not being removed.
+        verify(mWifiKeyStore, never()).removeKeys(any(WifiEnterpriseConfig.class));
+        verifyNetworkRemoveBroadcast(configuration);
+        // Ensure that the write was not invoked for Passpoint network remove.
+        mContextConfigStoreMockOrder.verify(mWifiConfigStore, never()).write(anyBoolean());
+    }
+
+    /**
+     * 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 =
+                WifiConfigManager.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 given BSSID, level &frequency
+     * values.
+     */
+    private ScanDetail createScanDetailForNetwork(
+            WifiConfiguration configuration, String bssid, int level, int frequency) {
+        String caps;
+        if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            caps = "[WPA2-PSK-CCMP]";
+        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
+                || configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)) {
+            caps = "[WPA2-EAP-CCMP]";
+        } else if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
+                && WifiConfigurationUtil.hasAnyValidWepKey(configuration.wepKeys)) {
+            caps = "[WEP]";
+        } else {
+            caps = "[]";
+        }
+        WifiSsid ssid = WifiSsid.createFromAsciiEncoded(configuration.getPrintableSsid());
+        // Fill in 0's in the fields we don't care about.
+        return new ScanDetail(
+                ssid, bssid, caps, level, frequency, mClock.getUptimeSinceBootMillis(),
+                mClock.getWallClockMillis());
+    }
+
+    /**
+     * Creates a scan detail corresponding to the provided network and BSSID value.
+     */
+    private ScanDetail createScanDetailForNetwork(WifiConfiguration configuration, String bssid) {
+        return createScanDetailForNetwork(configuration, bssid, 0, 0);
+    }
+
+    /**
+     * 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 WifiConfigManager#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);
+        // Retrieve the network with password data for comparison.
+        retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetworkWithPassword(retrievedNetwork.networkId);
+
+        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);
+    }
+
+    /**
+     * Adds a new network and verifies that the |HasEverConnected| flag is set to false.
+     */
+    private void verifyAddNetworkHasEverConnectedFalse(WifiConfiguration network) {
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(network);
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        assertFalse("Adding a new network should not have hasEverConnected set to true.",
+                retrievedNetwork.getNetworkSelectionStatus().getHasEverConnected());
+    }
+
+    /**
+     * Updates an existing network with some credential change and verifies that the
+     * |HasEverConnected| flag is set to false.
+     */
+    private void verifyUpdateNetworkWithCredentialChangeHasEverConnectedFalse(
+            WifiConfiguration network) {
+        NetworkUpdateResult result = verifyUpdateNetworkToWifiConfigManagerWithoutIpChange(network);
+        WifiConfiguration retrievedNetwork =
+                mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
+        assertFalse("Updating network credentials config should clear hasEverConnected.",
+                retrievedNetwork.getNetworkSelectionStatus().getHasEverConnected());
+        assertTrue(result.hasCredentialChanged());
+    }
+
+    /**
+     * Updates an existing network after connection using
+     * {@link WifiConfigManager#updateNetworkAfterConnect(int)} and asserts that the
+     * |HasEverConnected| flag is set to true.
+     */
+    private void verifyUpdateNetworkAfterConnectHasEverConnectedTrue(int networkId) {
+        assertTrue(mWifiConfigManager.updateNetworkAfterConnect(networkId));
+        WifiConfiguration retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(networkId);
+        assertTrue("hasEverConnected expected to be true after connection.",
+                retrievedNetwork.getNetworkSelectionStatus().getHasEverConnected());
+    }
+
+    /**
+     * Sets up a user profiles for WifiConfigManager testing.
+     *
+     * @param userId Id of the user.
+     */
+    private void setupUserProfiles(int userId) {
+        final UserInfo userInfo =
+                new UserInfo(userId, Integer.toString(userId), UserInfo.FLAG_PRIMARY);
+        List<UserInfo> userProfiles = Arrays.asList(userInfo);
+        when(mUserManager.getProfiles(userId)).thenReturn(userProfiles);
+        when(mUserManager.isUserUnlockingOrUnlocked(userId)).thenReturn(true);
+    }
 
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreLegacyTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreLegacyTest.java
new file mode 100644
index 0000000..4b4e875
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreLegacyTest.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.net.IpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import android.util.SparseArray;
+
+import com.android.server.net.IpConfigStore;
+import com.android.server.wifi.hotspot2.LegacyPasspointConfig;
+import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiConfigStoreLegacy}.
+ */
+@SmallTest
+public class WifiConfigStoreLegacyTest {
+    private static final String MASKED_FIELD_VALUE = "*";
+
+    // Test mocks
+    @Mock private WifiNative mWifiNative;
+    @Mock private WifiNetworkHistory mWifiNetworkHistory;
+    @Mock private IpConfigStore mIpconfigStore;
+    @Mock private LegacyPasspointConfigParser mPasspointConfigParser;
+
+    /**
+     * Test instance of WifiConfigStore.
+     */
+    private WifiConfigStoreLegacy mWifiConfigStore;
+
+
+    /**
+     * Setup the test environment.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mWifiConfigStore = new WifiConfigStoreLegacy(mWifiNetworkHistory, mWifiNative,
+                mIpconfigStore, mPasspointConfigParser);
+    }
+
+    /**
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Verify loading of network configurations from legacy stores. This is verifying the population
+     * of the masked wpa_supplicant fields using wpa_supplicant.conf file.
+     */
+    @Test
+    public void testLoadFromStores() throws Exception {
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        WifiConfiguration eapNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        WifiConfiguration passpointNetwork = WifiConfigurationTestUtil.createPasspointNetwork();
+        eapNetwork.enterpriseConfig.setPassword("EapPassword");
+
+        // Initialize Passpoint configuration data.
+        int passpointNetworkId = 1234;
+        String fqdn = passpointNetwork.FQDN;
+        String providerFriendlyName = passpointNetwork.providerFriendlyName;
+        long[] roamingConsortiumIds = new long[] {0x1234, 0x5678};
+        String realm = "test.com";
+        String imsi = "214321";
+
+        // Update Passpoint network.
+        // Network ID is used for lookup network extras, so use an unique ID for passpoint network.
+        passpointNetwork.networkId = passpointNetworkId;
+        passpointNetwork.enterpriseConfig.setPassword("PaspointPassword");
+        // Reset FQDN and provider friendly name so that the derived network from #read will
+        // obtained these information from networkExtras and {@link LegacyPasspointConfigParser}.
+        passpointNetwork.FQDN = null;
+        passpointNetwork.providerFriendlyName = null;
+
+        final List<WifiConfiguration> networks = new ArrayList<>();
+        networks.add(pskNetwork);
+        networks.add(wepNetwork);
+        networks.add(eapNetwork);
+        networks.add(passpointNetwork);
+
+        // Setup legacy Passpoint configuration data.
+        Map<String, LegacyPasspointConfig> passpointConfigs = new HashMap<>();
+        LegacyPasspointConfig passpointConfig = new LegacyPasspointConfig();
+        passpointConfig.mFqdn = fqdn;
+        passpointConfig.mFriendlyName = providerFriendlyName;
+        passpointConfig.mRoamingConsortiumOis = roamingConsortiumIds;
+        passpointConfig.mRealm = realm;
+        passpointConfig.mImsi = imsi;
+        passpointConfigs.put(fqdn, passpointConfig);
+
+        // Return the config data with passwords masked from wpa_supplicant control interface.
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(Map<String, WifiConfiguration> configs,
+                    SparseArray<Map<String, String>> networkExtras) {
+                for (Map.Entry<String, WifiConfiguration> entry:
+                        createWpaSupplicantLoadData(networks).entrySet()) {
+                    configs.put(entry.getKey(), entry.getValue());
+                }
+                // Setup networkExtras for Passpoint configuration.
+                networkExtras.put(passpointNetworkId, createNetworkExtrasForPasspointConfig(fqdn));
+                return true;
+            }
+        }).when(mWifiNative).migrateNetworksFromSupplicant(any(Map.class), any(SparseArray.class));
+
+        when(mPasspointConfigParser.parseConfig(anyString())).thenReturn(passpointConfigs);
+        WifiConfigStoreLegacy.WifiConfigStoreDataLegacy storeData = mWifiConfigStore.read();
+
+        // Update the expected configuration for Passpoint network.
+        passpointNetwork.isLegacyPasspointConfig = true;
+        passpointNetwork.FQDN = fqdn;
+        passpointNetwork.providerFriendlyName = providerFriendlyName;
+        passpointNetwork.roamingConsortiumIds = roamingConsortiumIds;
+        passpointNetwork.enterpriseConfig.setRealm(realm);
+        passpointNetwork.enterpriseConfig.setPlmn(imsi);
+
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
+                networks, storeData.getConfigurations());
+    }
+
+    private SparseArray<IpConfiguration> createIpConfigStoreLoadData(
+            List<WifiConfiguration> configurations) {
+        SparseArray<IpConfiguration> newIpConfigurations = new SparseArray<>();
+        for (WifiConfiguration config : configurations) {
+            newIpConfigurations.put(
+                    config.configKey().hashCode(),
+                    new IpConfiguration(config.getIpConfiguration()));
+        }
+        return newIpConfigurations;
+    }
+
+    private Map<String, String> createPskMap(List<WifiConfiguration> configurations) {
+        Map<String, String> pskMap = new HashMap<>();
+        for (WifiConfiguration config : configurations) {
+            if (!TextUtils.isEmpty(config.preSharedKey)) {
+                pskMap.put(config.configKey(), config.preSharedKey);
+            }
+        }
+        return pskMap;
+    }
+
+    private Map<String, String> createWepKey0Map(List<WifiConfiguration> configurations) {
+        Map<String, String> wepKeyMap = new HashMap<>();
+        for (WifiConfiguration config : configurations) {
+            if (!TextUtils.isEmpty(config.wepKeys[0])) {
+                wepKeyMap.put(config.configKey(), config.wepKeys[0]);
+            }
+        }
+        return wepKeyMap;
+    }
+
+    private Map<String, String> createWepKey1Map(List<WifiConfiguration> configurations) {
+        Map<String, String> wepKeyMap = new HashMap<>();
+        for (WifiConfiguration config : configurations) {
+            if (!TextUtils.isEmpty(config.wepKeys[1])) {
+                wepKeyMap.put(config.configKey(), config.wepKeys[1]);
+            }
+        }
+        return wepKeyMap;
+    }
+
+    private Map<String, String> createWepKey2Map(List<WifiConfiguration> configurations) {
+        Map<String, String> wepKeyMap = new HashMap<>();
+        for (WifiConfiguration config : configurations) {
+            if (!TextUtils.isEmpty(config.wepKeys[2])) {
+                wepKeyMap.put(config.configKey(), config.wepKeys[2]);
+            }
+        }
+        return wepKeyMap;
+    }
+
+    private Map<String, String> createWepKey3Map(List<WifiConfiguration> configurations) {
+        Map<String, String> wepKeyMap = new HashMap<>();
+        for (WifiConfiguration config : configurations) {
+            if (!TextUtils.isEmpty(config.wepKeys[3])) {
+                wepKeyMap.put(config.configKey(), config.wepKeys[3]);
+            }
+        }
+        return wepKeyMap;
+    }
+
+    private Map<String, String> createEapPasswordMap(List<WifiConfiguration> configurations) {
+        Map<String, String> eapPasswordMap = new HashMap<>();
+        for (WifiConfiguration config : configurations) {
+            if (!TextUtils.isEmpty(config.enterpriseConfig.getPassword())) {
+                eapPasswordMap.put(config.configKey(), config.enterpriseConfig.getPassword());
+            }
+        }
+        return eapPasswordMap;
+    }
+
+    private Map<String, WifiConfiguration> createWpaSupplicantLoadData(
+            List<WifiConfiguration> configurations) {
+        Map<String, WifiConfiguration> configurationMap = new HashMap<>();
+        for (WifiConfiguration config : configurations) {
+            configurationMap.put(config.configKey(true), config);
+        }
+        return configurationMap;
+    }
+
+    private List<WifiConfiguration> createMaskedWifiConfigurations(
+            List<WifiConfiguration> configurations) {
+        List<WifiConfiguration> newConfigurations = new ArrayList<>();
+        for (WifiConfiguration config : configurations) {
+            newConfigurations.add(createMaskedWifiConfiguration(config));
+        }
+        return newConfigurations;
+    }
+
+    private WifiConfiguration createMaskedWifiConfiguration(WifiConfiguration configuration) {
+        WifiConfiguration newConfig = new WifiConfiguration(configuration);
+        if (!TextUtils.isEmpty(configuration.preSharedKey)) {
+            newConfig.preSharedKey = MASKED_FIELD_VALUE;
+        }
+        if (!TextUtils.isEmpty(configuration.wepKeys[0])) {
+            newConfig.wepKeys[0] = MASKED_FIELD_VALUE;
+        }
+        if (!TextUtils.isEmpty(configuration.wepKeys[1])) {
+            newConfig.wepKeys[1] = MASKED_FIELD_VALUE;
+        }
+        if (!TextUtils.isEmpty(configuration.wepKeys[2])) {
+            newConfig.wepKeys[2] = MASKED_FIELD_VALUE;
+        }
+        if (!TextUtils.isEmpty(configuration.wepKeys[3])) {
+            newConfig.wepKeys[3] = MASKED_FIELD_VALUE;
+        }
+        if (!TextUtils.isEmpty(configuration.enterpriseConfig.getPassword())) {
+            newConfig.enterpriseConfig.setPassword(MASKED_FIELD_VALUE);
+        }
+        return newConfig;
+    }
+
+    private Map<String, String> createNetworkExtrasForPasspointConfig(String fqdn) {
+        Map<String, String> extras = new HashMap<>();
+        extras.put(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN, fqdn);
+        return extras;
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
index 3993fe5..47efed3 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
@@ -11,233 +11,576 @@
  * 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;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+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.WifiConfigStore.StoreFile;
+import com.android.server.wifi.util.XmlUtil;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
 
-import java.io.BufferedReader;
+import java.io.File;
 import java.io.IOException;
-import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiConfigStore}.
  */
 @SmallTest
 public class WifiConfigStoreTest {
-    private static final String KEY_SSID = "ssid";
-    private static final String KEY_PSK = "psk";
-    private static final String KEY_KEY_MGMT = "key_mgmt";
-    private static final String KEY_PRIORITY = "priority";
-    private static final String KEY_DISABLED = "disabled";
-    private static final String KEY_ID_STR = "id_str";
-    // This is not actually present as a key in the wpa_supplicant.conf file, but
-    // is used in tests to conveniently access the configKey for a test network.
-    private static final String CONFIG_KEY = "configKey";
+    // Store file content without any data.
+    private static final String EMPTY_FILE_CONTENT =
+            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+            + "<WifiConfigStoreData>\n"
+            + "<int name=\"Version\" value=\"1\" />\n"
+            + "</WifiConfigStoreData>\n";
 
-    private static final HashMap<String, String> NETWORK_0_VARS = new HashMap<>();
-    static {
-        NETWORK_0_VARS.put(KEY_SSID, "\"TestNetwork0\"");
-        NETWORK_0_VARS.put(KEY_KEY_MGMT, "NONE");
-        NETWORK_0_VARS.put(KEY_PRIORITY, "2");
-        NETWORK_0_VARS.put(KEY_ID_STR, ""
-                + "\"%7B%22creatorUid%22%3A%221000%22%2C%22configKey%22%3A%22%5C%22"
-                + "TestNetwork0%5C%22NONE%22%7D\"");
-        NETWORK_0_VARS.put(CONFIG_KEY, "\"TestNetwork0\"NONE");
-    }
+    private static final String TEST_USER_DATA = "UserData";
+    private static final String TEST_SHARE_DATA = "ShareData";
 
-    private static final HashMap<String, String> NETWORK_1_VARS = new HashMap<>();
-    static {
-        NETWORK_1_VARS.put(KEY_SSID, "\"Test Network 1\"");
-        NETWORK_1_VARS.put(KEY_KEY_MGMT, "NONE");
-        NETWORK_1_VARS.put(KEY_PRIORITY, "3");
-        NETWORK_1_VARS.put(KEY_DISABLED, "1");
-        NETWORK_1_VARS.put(KEY_ID_STR, ""
-                + "\"%7B%22creatorUid%22%3A%221000%22%2C%22configKey%22%3A%22%5C%22"
-                + "Test+Network+1%5C%22NONE%22%7D\"");
-        NETWORK_1_VARS.put(CONFIG_KEY, "\"Test Network 1\"NONE");
-    }
+    private static final String TEST_DATA_XML_STRING_FORMAT =
+            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<WifiConfigStoreData>\n"
+                    + "<int name=\"Version\" value=\"1\" />\n"
+                    + "<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"
+                    + "<boolean name=\"RequirePMF\" 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"
+                    + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
+                    + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
+                    + "<boolean name=\"Shared\" value=\"%s\" />\n"
+                    + "<int name=\"Status\" value=\"2\" />\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"
+                    + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
+                    + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
+                    + "</WifiConfiguration>\n"
+                    + "<NetworkStatus>\n"
+                    + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
+                    + "<string name=\"DisableReason\">NETWORK_SELECTION_ENABLE</string>\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"
+                    + "<DeletedEphemeralSSIDList>\n"
+                    + "<set name=\"SSIDList\">\n"
+                    + "<string>%s</string>\n"
+                    + "</set>\n"
+                    + "</DeletedEphemeralSSIDList>\n"
+                    + "</WifiConfigStoreData>\n";
 
-    private static final HashMap<String, String> NETWORK_2_VARS = new HashMap<>();
-    static {
-        NETWORK_2_VARS.put(KEY_SSID, "\"testNetwork2\"");
-        NETWORK_2_VARS.put(KEY_KEY_MGMT, "NONE");
-        NETWORK_2_VARS.put(KEY_PRIORITY, "4");
-        NETWORK_2_VARS.put(KEY_DISABLED, "1");
-        NETWORK_2_VARS.put(KEY_ID_STR, ""
-                + "\"%7B%22creatorUid%22%3A%221000%22%2C%22configKey%22%3A%22%5C%22"
-                + "testNetwork2%5C%22NONE%22%7D\"");
-        NETWORK_2_VARS.put(CONFIG_KEY, "\"testNetwork2\"NONE");
-    }
-
-    private static final HashMap<String, String> NETWORK_3_VARS = new HashMap<>();
-    static {
-        NETWORK_3_VARS.put(KEY_SSID, "\"testwpa2psk\"");
-        NETWORK_3_VARS.put(KEY_PSK, "blahblah");
-        NETWORK_3_VARS.put(KEY_KEY_MGMT, "WPA-PSK");
-        NETWORK_3_VARS.put(KEY_PRIORITY, "6");
-        NETWORK_3_VARS.put(KEY_DISABLED, "1");
-        NETWORK_3_VARS.put(KEY_ID_STR, ""
-                + "\"%7B%22creatorUid%22%3A%221000%22%2C%22configKey%22%3A%22%5C%22"
-                + "testwpa2psk%5C%22WPA_PSK%22%7D\"");
-        NETWORK_3_VARS.put(CONFIG_KEY, "\"testwpa2psk\"WPA_PSK");
-    }
-
-    private static final ArrayList<HashMap<String, String>> NETWORK_VARS = new ArrayList<HashMap<String, String>>();
-    static {
-        NETWORK_VARS.add(NETWORK_0_VARS);
-        NETWORK_VARS.add(NETWORK_1_VARS);
-        NETWORK_VARS.add(NETWORK_2_VARS);
-        NETWORK_VARS.add(NETWORK_3_VARS);
-    }
-
-    // Taken from wpa_supplicant.conf actual test device Some fields modified for privacy.
-    private static final String TEST_WPA_SUPPLICANT_CONF = ""
-            + "ctrl_interface=/data/misc/wifi/sockets\n"
-            + "disable_scan_offload=1\n"
-            + "driver_param=use_p2p_group_interface=1p2p_device=1\n"
-            + "update_config=1\n"
-            + "device_name=testdevice\n"
-            + "manufacturer=TestManufacturer\n"
-            + "model_name=Testxus\n"
-            + "model_number=Testxus\n"
-            + "serial_number=1ABCD12345678912\n"
-            + "device_type=12-3456A456-7\n"
-            + "config_methods=physical_display virtual_push_button\n"
-            + "p2p_no_go_freq=5170-5740\n"
-            + "pmf=1\n"
-            + "external_sim=1\n"
-            + "wowlan_triggers=any\n"
-            + "p2p_search_delay=0\n"
-            + "network={\n"
-            + "        " + KEY_SSID + "=" + NETWORK_0_VARS.get(KEY_SSID) + "\n"
-            + "        " + KEY_KEY_MGMT + "=" + NETWORK_0_VARS.get(KEY_KEY_MGMT) + "\n"
-            + "        " + KEY_PRIORITY + "=" + NETWORK_0_VARS.get(KEY_PRIORITY) + "\n"
-            + "        " + KEY_ID_STR + "=" + NETWORK_0_VARS.get(KEY_ID_STR) + "\n"
-            + "}\n"
-            + "\n"
-            + "network={\n"
-            + "        " + KEY_SSID + "=" + NETWORK_1_VARS.get(KEY_SSID) + "\n"
-            + "        " + KEY_KEY_MGMT + "=" + NETWORK_1_VARS.get(KEY_KEY_MGMT) + "\n"
-            + "        " + KEY_PRIORITY + "=" + NETWORK_1_VARS.get(KEY_PRIORITY) + "\n"
-            + "        " + KEY_DISABLED + "=" + NETWORK_1_VARS.get(KEY_DISABLED) + "\n"
-            + "        " + KEY_ID_STR + "=" + NETWORK_1_VARS.get(KEY_ID_STR) + "\n"
-            + "}\n"
-            + "\n"
-            + "network={\n"
-            + "        " + KEY_SSID + "=" + NETWORK_2_VARS.get(KEY_SSID) + "\n"
-            + "        " + KEY_KEY_MGMT + "=" + NETWORK_2_VARS.get(KEY_KEY_MGMT) + "\n"
-            + "        " + KEY_PRIORITY + "=" + NETWORK_2_VARS.get(KEY_PRIORITY) + "\n"
-            + "        " + KEY_DISABLED + "=" + NETWORK_2_VARS.get(KEY_DISABLED) + "\n"
-            + "        " + KEY_ID_STR + "=" + NETWORK_2_VARS.get(KEY_ID_STR) + "\n"
-            + "}\n"
-            + "\n"
-            + "network={\n"
-            + "        " + KEY_SSID + "=" + NETWORK_3_VARS.get(KEY_SSID) + "\n"
-            + "        " + KEY_PSK + "=" + NETWORK_3_VARS.get(KEY_PSK) + "\n"
-            + "        " + KEY_KEY_MGMT + "=" + NETWORK_3_VARS.get(KEY_KEY_MGMT) + "\n"
-            + "        " + KEY_PRIORITY + "=" + NETWORK_3_VARS.get(KEY_PRIORITY) + "\n"
-            + "        " + KEY_DISABLED + "=" + NETWORK_3_VARS.get(KEY_DISABLED) + "\n"
-            + "        " + KEY_ID_STR + "=" + NETWORK_3_VARS.get(KEY_ID_STR) + "\n"
-            + "}\n";
-
-    @Mock private WifiNative mWifiNative;
+    // Test mocks
     @Mock private Context mContext;
-    private MockKeyStore mMockKeyStore;
+    private TestAlarmManager mAlarmManager;
+    private TestLooper mLooper;
+    @Mock private Clock mClock;
+    private MockStoreFile mSharedStore;
+    private MockStoreFile mUserStore;
+    private MockStoreData mStoreData;
+
+    /**
+     * Test instance of WifiConfigStore.
+     */
     private WifiConfigStore 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();
+        mStoreData = new MockStoreData();
+    }
+
+    /**
+     * Setup the test environment.
+     */
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        setupMocks();
 
-        mMockKeyStore = new MockKeyStore();
-        mWifiConfigStore = new WifiConfigStore(mContext, mWifiNative, mMockKeyStore.createMock(),
-                null, false, true);
+        mWifiConfigStore = new WifiConfigStore(mContext, mLooper.getLooper(), mClock, mSharedStore);
+        // Enable verbose logging before tests.
+        mWifiConfigStore.enableVerboseLogging(true);
     }
 
     /**
-     * Verifies that readNetworkVariableFromSupplicantFile() properly reads network variables from a
-     * wpa_supplicant.conf file.
+     * Called after each test
+     */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * Verify the contents of the config file with empty data.  The data content should be the
+     * same as {@link #EMPTY_FILE_CONTENT}.
+     *
+     * @throws Exception
      */
     @Test
-    public void readNetworkVariableFromSupplicantFile() throws Exception {
-        Map<String, String> ssidResults = readNetworkVariableFromSupplicantFile(KEY_SSID);
-        assertEquals(NETWORK_VARS.size(), ssidResults.size());
-        for (HashMap<String, String> single_network_vars : NETWORK_VARS) {
-            assertEquals(ssidResults.get(single_network_vars.get(CONFIG_KEY)),
-                    single_network_vars.get(KEY_SSID));
+    public void testWriteWithEmptyData() throws Exception {
+        // Perform force write to both share and user store file.
+        mWifiConfigStore.switchUserStoreAndRead(mUserStore);
+        mWifiConfigStore.write(true);
+
+        assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
+        assertTrue(mSharedStore.isStoreWritten());
+        assertTrue(mUserStore.isStoreWritten());
+        assertTrue(Arrays.equals(EMPTY_FILE_CONTENT.getBytes(StandardCharsets.UTF_8),
+                mSharedStore.getStoreBytes()));
+        assertTrue(Arrays.equals(EMPTY_FILE_CONTENT.getBytes(StandardCharsets.UTF_8),
+                mUserStore.getStoreBytes()));
+    }
+
+    /**
+     * 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.switchUserStoreAndRead(mUserStore);
+        mWifiConfigStore.write(true);
+
+        assertFalse(mAlarmManager.isPending(WifiConfigStore.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.switchUserStoreAndRead(mUserStore);
+        mWifiConfigStore.write(false);
+
+        assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
+        assertFalse(mSharedStore.isStoreWritten());
+        assertFalse(mUserStore.isStoreWritten());
+
+        // Now send the alarm and ensure that the writes happen.
+        mAlarmManager.dispatch(WifiConfigStore.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 {
+        // Register a test data container with bogus data.
+        mWifiConfigStore.registerStoreData(mStoreData);
+        mStoreData.setShareData("abcds");
+        mStoreData.setUserData("asdfa");
+
+        // Perform buffered write for both user and share store file.
+        mWifiConfigStore.switchUserStoreAndRead(mUserStore);
+        mWifiConfigStore.write(false);
+
+        assertTrue(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
+        assertFalse(mSharedStore.isStoreWritten());
+        assertFalse(mUserStore.isStoreWritten());
+
+        // Update the container with new set of data. The send a force write and ensure that the
+        // writes have been performed and alarms have been stopped and updated data are written.
+        mStoreData.setUserData(TEST_USER_DATA);
+        mStoreData.setShareData(TEST_SHARE_DATA);
+        mWifiConfigStore.write(true);
+
+        assertFalse(mAlarmManager.isPending(WifiConfigStore.BUFFERED_WRITE_ALARM_TAG));
+        assertTrue(mSharedStore.isStoreWritten());
+        assertTrue(mUserStore.isStoreWritten());
+
+        // Verify correct data are loaded to the data container after a read.
+        mWifiConfigStore.read();
+        assertEquals(TEST_USER_DATA, mStoreData.getUserData());
+        assertEquals(TEST_SHARE_DATA, mStoreData.getShareData());
+    }
+
+    /**
+     * Tests the read API behaviour after a write to the store files.
+     * Expected behaviour: The read should return the same data that was last written.
+     */
+    @Test
+    public void testReadAfterWrite() throws Exception {
+        // Register data container.
+        mWifiConfigStore.registerStoreData(mStoreData);
+
+        // Read both share and user config store.
+        mWifiConfigStore.switchUserStoreAndRead(mUserStore);
+
+        // Verify no data is read.
+        assertNull(mStoreData.getUserData());
+        assertNull(mStoreData.getShareData());
+
+        // Write share and user data.
+        mStoreData.setUserData(TEST_USER_DATA);
+        mStoreData.setShareData(TEST_SHARE_DATA);
+        mWifiConfigStore.write(true);
+
+        // Read and verify the data content in the data container.
+        mWifiConfigStore.read();
+        assertEquals(TEST_USER_DATA, mStoreData.getUserData());
+        assertEquals(TEST_SHARE_DATA, mStoreData.getShareData());
+    }
+
+    /**
+     * Tests the read API behaviour when there is no store files 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.
+        mWifiConfigStore.registerStoreData(mStoreData);
+        assertFalse(mWifiConfigStore.areStoresPresent());
+        mWifiConfigStore.read();
+
+        // Empty data.
+        assertNull(mStoreData.getUserData());
+        assertNull(mStoreData.getShareData());
+    }
+
+    /**
+     * Tests the read API behaviour after a write to the shared store file when the user
+     * store file is null.
+     * Expected behaviour: The read should return the same data that was last written.
+     */
+    @Test
+    public void testReadAfterWriteWithNoUserStore() throws Exception {
+        // Setup data container.
+        mWifiConfigStore.registerStoreData(mStoreData);
+        mStoreData.setUserData(TEST_USER_DATA);
+        mStoreData.setShareData(TEST_SHARE_DATA);
+
+        // Perform write for the share store file.
+        mWifiConfigStore.write(true);
+        mWifiConfigStore.read();
+        // Verify data content for both user and share data.
+        assertEquals(TEST_SHARE_DATA, mStoreData.getShareData());
+        assertNull(mStoreData.getUserData());
+    }
+
+    /**
+     * Verifies that a read operation will reset the data in the data container, to avoid
+     * any stale data from previous read.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testReadWillResetStoreData() throws Exception {
+        // Register and setup store data.
+        mWifiConfigStore.registerStoreData(mStoreData);
+
+        // Perform force write with empty data content to both user and share store file.
+        mWifiConfigStore.switchUserStoreAndRead(mUserStore);
+        mWifiConfigStore.write(true);
+
+        // Setup data container with some value.
+        mStoreData.setUserData(TEST_USER_DATA);
+        mStoreData.setShareData(TEST_SHARE_DATA);
+
+        // Perform read of both user and share store file and verify data in the data container
+        // is in sync (empty) with what is in the file.
+        mWifiConfigStore.read();
+        assertNull(mStoreData.getShareData());
+        assertNull(mStoreData.getUserData());
+    }
+
+    /**
+     * Verify that a store file contained WiFi configuration store data (network list and
+     * deleted ephemeral SSID list) using the predefined test XML data is read and parsed
+     * correctly.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testReadWifiConfigStoreData() throws Exception {
+        // Setup network list.
+        NetworkListStoreData networkList = new NetworkListStoreData();
+        mWifiConfigStore.registerStoreData(networkList);
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
+        List<WifiConfiguration> userConfigs = new ArrayList<>();
+        userConfigs.add(openNetwork);
+
+        // Setup deleted ephemeral SSID list.
+        DeletedEphemeralSsidsStoreData deletedEphemeralSsids =
+                new DeletedEphemeralSsidsStoreData();
+        mWifiConfigStore.registerStoreData(deletedEphemeralSsids);
+        String testSsid = "Test SSID";
+        Set<String> ssidList = new HashSet<>();
+        ssidList.add(testSsid);
+
+        // Setup user store XML bytes.
+        String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid, testSsid);
+        byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8);
+        mUserStore.storeRawDataToWrite(xmlBytes);
+
+        mWifiConfigStore.switchUserStoreAndRead(mUserStore);
+        WifiConfigurationTestUtil.assertConfigurationsEqualForConfigStore(
+                userConfigs, networkList.getUserConfigurations());
+        assertEquals(ssidList, deletedEphemeralSsids.getSsidList());
+    }
+
+    /**
+     * Verify that the WiFi configuration store data containing network list and deleted
+     * ephemeral SSID list are serialized correctly, matches the predefined test XML data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testWriteWifiConfigStoreData() throws Exception {
+        // Setup user store.
+        mWifiConfigStore.switchUserStoreAndRead(mUserStore);
+
+        // Setup network list store data.
+        NetworkListStoreData networkList = new NetworkListStoreData();
+        mWifiConfigStore.registerStoreData(networkList);
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.setIpConfiguration(
+                WifiConfigurationTestUtil.createDHCPIpConfigurationWithNoProxy());
+        List<WifiConfiguration> userConfigs = new ArrayList<>();
+        userConfigs.add(openNetwork);
+        networkList.setUserConfigurations(userConfigs);
+
+        // Setup deleted ephemeral SSID list store data.
+        DeletedEphemeralSsidsStoreData deletedEphemeralSsids =
+                new DeletedEphemeralSsidsStoreData();
+        mWifiConfigStore.registerStoreData(deletedEphemeralSsids);
+        String testSsid = "Test SSID";
+        Set<String> ssidList = new HashSet<>();
+        ssidList.add(testSsid);
+        deletedEphemeralSsids.setSsidList(ssidList);
+
+        // Setup expected XML bytes.
+        String xmlString = String.format(TEST_DATA_XML_STRING_FORMAT,
+                openNetwork.configKey().replaceAll("\"", "&quot;"),
+                openNetwork.SSID.replaceAll("\"", "&quot;"),
+                openNetwork.shared, openNetwork.creatorUid, testSsid);
+        byte[] xmlBytes = xmlString.getBytes(StandardCharsets.UTF_8);
+
+        mWifiConfigStore.write(true);
+        assertEquals(xmlBytes.length, mUserStore.getStoreBytes().length);
+        // Verify the user store content.
+        assertTrue(Arrays.equals(xmlBytes, mUserStore.getStoreBytes()));
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when reading an user store file
+     * containing unknown data.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void testReadUserStoreContainedUnknownData() throws Exception {
+        String storeFileData =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<WifiConfigStoreData>\n"
+                        + "<int name=\"Version\" value=\"1\" />\n"
+                        + "<UnknownTag>\n"    // No StoreData registered to handle this tag.
+                        + "</UnknownTag>\n"
+                        + "</WifiConfigStoreData>\n";
+        mUserStore.storeRawDataToWrite(storeFileData.getBytes(StandardCharsets.UTF_8));
+        mWifiConfigStore.switchUserStoreAndRead(mUserStore);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when reading the share store file
+     * containing unknown data.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void testReadShareStoreContainedUnknownData() throws Exception {
+        String storeFileData =
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                        + "<WifiConfigStoreData>\n"
+                        + "<int name=\"Version\" value=\"1\" />\n"
+                        + "<UnknownTag>\n"    // No StoreData registered to handle this tag.
+                        + "</UnknownTag>\n"
+                        + "</WifiConfigStoreData>\n";
+        mSharedStore.storeRawDataToWrite(storeFileData.getBytes(StandardCharsets.UTF_8));
+        mWifiConfigStore.read();
+    }
+
+    /**
+     * Mock Store File to redirect all file writes from WifiConfigStore to local buffers.
+     * This can be used to examine the data output by WifiConfigStore.
+     */
+    private class MockStoreFile extends StoreFile {
+        private byte[] mStoreBytes;
+        private boolean mStoreWritten;
+
+        public MockStoreFile() {
+            super(new File("MockStoreFile"));
         }
 
-        Map<String, String> pskResults = readNetworkVariableFromSupplicantFile(KEY_PSK);
-        // Only network 3 is secured with a password.
-        assertEquals(1, pskResults.size());
-        assertEquals(pskResults.get(NETWORK_3_VARS.get(CONFIG_KEY)),
-                    NETWORK_3_VARS.get(KEY_PSK));
-
-        Map<String, String> keyMgmtResults = readNetworkVariableFromSupplicantFile(KEY_KEY_MGMT);
-        assertEquals(NETWORK_VARS.size(), keyMgmtResults.size());
-        for (HashMap<String, String> single_network_vars : NETWORK_VARS) {
-            assertEquals(keyMgmtResults.get(single_network_vars.get(CONFIG_KEY)),
-                    single_network_vars.get(KEY_KEY_MGMT));
+        @Override
+        public byte[] readRawData() {
+            return mStoreBytes;
         }
 
-        Map<String, String> priorityResults = readNetworkVariableFromSupplicantFile(KEY_PRIORITY);
-        assertEquals(NETWORK_VARS.size(), priorityResults.size());
-        for (HashMap<String, String> single_network_vars : NETWORK_VARS) {
-            assertEquals(priorityResults.get(single_network_vars.get(CONFIG_KEY)),
-                    single_network_vars.get(KEY_PRIORITY));
+        @Override
+        public void storeRawDataToWrite(byte[] data) {
+            mStoreBytes = data;
+            mStoreWritten = false;
         }
 
-        Map<String, String> disabledResults = readNetworkVariableFromSupplicantFile(KEY_DISABLED);
-        // All but network 0 are disabled.
-        assertEquals(NETWORK_VARS.size() - 1, disabledResults.size());
-        for (int i = 1; i < NETWORK_VARS.size(); ++i) {
-            assertEquals(disabledResults.get(NETWORK_VARS.get(i).get(CONFIG_KEY)),
-                    NETWORK_VARS.get(i).get(KEY_DISABLED));
+        @Override
+        public boolean exists() {
+            return (mStoreBytes != null);
         }
 
-        Map<String, String> idStrResults = readNetworkVariableFromSupplicantFile(KEY_ID_STR);
-        assertEquals(NETWORK_VARS.size(), idStrResults.size());
-        for (HashMap<String, String> single_network_vars : NETWORK_VARS) {
-            assertEquals(idStrResults.get(single_network_vars.get(CONFIG_KEY)),
-                    single_network_vars.get(KEY_ID_STR));
+        @Override
+        public void writeBufferedRawData() {
+            mStoreWritten = true;
+        }
+
+        public byte[] getStoreBytes() {
+            return mStoreBytes;
+        }
+
+        public boolean isStoreWritten() {
+            return mStoreWritten;
         }
     }
 
     /**
-     * Inject |TEST_WPA_SUPPLICANT_CONF| via the helper method readNetworkVariablesFromReader().
+     * Mock data container for providing test data for the store file.
      */
-    public Map<String, String> readNetworkVariableFromSupplicantFile(String key) throws Exception {
-        Map<String, String> result = new HashMap<>();
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new StringReader(TEST_WPA_SUPPLICANT_CONF));
-            result = mWifiConfigStore.readNetworkVariablesFromReader(reader, key);
-        } catch (IOException e) {
-            fail("Error reading test supplicant conf string");
-        } finally {
-            try {
-                if (reader != null) {
-                    reader.close();
-                }
-            } catch (IOException e) {
-                // Just ignore if we can't close the reader.
+    private class MockStoreData implements WifiConfigStore.StoreData {
+        private static final String XML_TAG_TEST_HEADER = "TestHeader";
+        private static final String XML_TAG_TEST_DATA = "TestData";
+
+        private String mShareData;
+        private String mUserData;
+
+        MockStoreData() {}
+
+        @Override
+        public void serializeData(XmlSerializer out, boolean shared)
+                throws XmlPullParserException, IOException {
+            if (shared) {
+                XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mShareData);
+            } else {
+                XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mUserData);
             }
         }
-        return result;
+
+        @Override
+        public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+                throws XmlPullParserException, IOException {
+            if (shared) {
+                mShareData = (String) XmlUtil.readNextValueWithName(in, XML_TAG_TEST_DATA);
+            } else {
+                mUserData = (String) XmlUtil.readNextValueWithName(in, XML_TAG_TEST_DATA);
+            }
+        }
+
+        @Override
+        public void resetData(boolean shared) {
+            if (shared) {
+                mShareData = null;
+            } else {
+                mUserData = null;
+            }
+        }
+
+        @Override
+        public String getName() {
+            return XML_TAG_TEST_HEADER;
+        }
+
+        @Override
+        public boolean supportShareData() {
+            return true;
+        }
+
+        public String getShareData() {
+            return mShareData;
+        }
+
+        public void setShareData(String shareData) {
+            mShareData = shareData;
+        }
+
+        public String getUserData() {
+            return mUserData;
+        }
+
+        public void setUserData(String userData) {
+            mUserData = userData;
+        }
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
index 7117c2a..f7bf5b0 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.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.Arrays;
+import java.util.List;
 
 /**
  * Helper for creating and populating WifiConfigurations in unit tests.
@@ -33,6 +47,47 @@
     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 = 5;
+    public static final String TEST_SSID = "WifiConfigurationTestUtilSSID";
+    public static final String TEST_PSK = "\"WifiConfigurationTestUtilPsk\"";
+    public static final String[] TEST_WEP_KEYS =
+            {"\"WifiConfigurationTestUtilWep1\"", "\"WifiConfigurationTestUtilWep2\"",
+                    "45342312ab", "45342312ab45342312ab34ac12"};
+    public static final String TEST_EAP_PASSWORD = "WifiConfigurationTestUtilEapPassword";
+    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";
+
+    private static final int MAX_SSID_LENGTH = 32;
+    /**
+     * 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
@@ -52,11 +107,8 @@
         config.shared = shared;
         config.status = enabled ? WifiConfiguration.Status.ENABLED
                 : WifiConfiguration.Status.DISABLED;
-        if (fqdn != null) {
-            config.FQDN = fqdn;
-            config.providerFriendlyName = providerFriendlyName;
-            config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
-        }
+        config.FQDN = fqdn;
+        config.providerFriendlyName = providerFriendlyName;
         return config;
     }
 
@@ -78,18 +130,544 @@
         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);
             }
 
             if ((security & SECURITY_EAP) != 0) {
                 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
                 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+                config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
             }
         }
         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() {
+        String ssid = TEST_SSID + sNetworkIndex++;
+        assertTrue(ssid.length() <= MAX_SSID_LENGTH);
+        return "\"" + ssid + "\"";
+    }
+
+    /**
+     * 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 createPskNetwork(String ssid) {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, 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,
+                        null, null, SECURITY_EAP);
+        return configuration;
+    }
+
+    public static WifiConfiguration createEapNetwork(String ssid) {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, ssid, true, true,
+                        null, null, SECURITY_EAP);
+        return configuration;
+    }
+
+
+    public static WifiConfiguration createEapNetwork(int eapMethod, int phase2Method) {
+        WifiConfiguration configuration =
+                generateWifiConfig(TEST_NETWORK_ID, TEST_UID, createNewSSID(), true, true,
+                        null, null, SECURITY_EAP);
+        configuration.enterpriseConfig.setEapMethod(eapMethod);
+        configuration.enterpriseConfig.setPhase2Method(phase2Method);
+        return configuration;
+    }
+
+    public static WifiConfiguration createPasspointNetwork() {
+        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);
+    }
+
+    /**
+     * Creates an IP configuration with specific parameters.
+     * @param proxySetting Must be one of {@link WifiConfigurationTestUtil#STATIC_PROXY_SETTING},
+     * {@link WifiConfigurationTestUtil#PAC_PROXY_SETTING},
+     * {@link WifiConfigurationTestUtil#NONE_PROXY_SETTING}
+     */
+    public static IpConfiguration createDHCPIpConfigurationWithSpecificProxy(
+            int proxySetting,
+            String staticProxyHost,
+            int staticProxyPort,
+            String staticProxyExclusionList,
+            String pacProxyLocation) {
+        return generateIpConfig(
+                DHCP_IP_ASSIGNMENT, proxySetting,
+                TEST_STATIC_IP_LINK_ADDRESS, TEST_STATIC_IP_LINK_PREFIX_LENGTH,
+                TEST_STATIC_IP_GATEWAY_ADDRESS, TEST_STATIC_IP_DNS_SERVER_ADDRESSES,
+                staticProxyHost, staticProxyPort, staticProxyExclusionList,
+                pacProxyLocation);
+    }
+
+    // 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;
+    }
+
+    public static WifiEnterpriseConfig createTLSWifiEnterpriseConfigWithAkaPhase2() {
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        config.setPhase2Method(WifiEnterpriseConfig.Phase2.AKA);
+        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) {
+        assertNotNull(expected);
+        assertNotNull(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.requirePMF, actual.requirePMF);
+        assertEquals(expected.allowedKeyManagement, actual.allowedKeyManagement);
+        assertEquals(expected.allowedProtocols, actual.allowedProtocols);
+        assertEquals(expected.allowedAuthAlgorithms, actual.allowedAuthAlgorithms);
+        assertEquals(expected.allowedGroupCiphers, actual.allowedGroupCiphers);
+        assertEquals(expected.allowedPairwiseCiphers, actual.allowedPairwiseCiphers);
+        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.status, actual.status);
+        assertEquals(expected.FQDN, actual.FQDN);
+        assertEquals(expected.providerFriendlyName, actual.providerFriendlyName);
+        assertTrue(Arrays.equals(expected.roamingConsortiumIds, actual.roamingConsortiumIds));
+        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);
+        assertEquals(expected.isLegacyPasspointConfig, actual.isLegacyPasspointConfig);
+        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);
+    }
+
+    /**
+     * Asserts that the 2 WifiConfigurations are equal. This compares all the elements that are
+     * saved into wpa_supplicant by SupplicantStaNetwork.
+     */
+    public static void assertConfigurationEqualForSupplicant(
+            WifiConfiguration expected, WifiConfiguration actual) {
+        assertNotNull(expected);
+        assertNotNull(actual);
+        assertEquals(expected.SSID, actual.SSID);
+        assertEquals(expected.getNetworkSelectionStatus().getNetworkSelectionBSSID(),
+                actual.getNetworkSelectionStatus().getNetworkSelectionBSSID());
+        assertEquals(expected.preSharedKey, actual.preSharedKey);
+        assertEquals(expected.wepKeys, actual.wepKeys);
+        assertEquals(expected.wepTxKeyIndex, actual.wepTxKeyIndex);
+        assertEquals(expected.hiddenSSID, actual.hiddenSSID);
+        assertEquals(expected.requirePMF, actual.requirePMF);
+        assertEquals(expected.allowedKeyManagement, actual.allowedKeyManagement);
+        assertEquals(expected.allowedProtocols, actual.allowedProtocols);
+        assertEquals(expected.allowedAuthAlgorithms, actual.allowedAuthAlgorithms);
+        assertEquals(expected.allowedGroupCiphers, actual.allowedGroupCiphers);
+        assertEquals(expected.allowedPairwiseCiphers, actual.allowedPairwiseCiphers);
+        assertWifiEnterpriseConfigEqualForConfigStore(
+                expected.enterpriseConfig, actual.enterpriseConfig);
+    }
+
+    /**
+     * Asserts that the 2 WifiConfigurations are equal. This is a generic version of the comparator
+     * which is used in QNS tests for comparing the network selections.
+     * This importantly checks that the networkId's of the 2 configs are equal.
+     */
+    public static void assertConfigurationEqual(
+            WifiConfiguration expected, WifiConfiguration actual) {
+        assertCommonConfigurationElementsEqual(expected, actual);
+        assertEquals(expected.networkId, actual.networkId);
+    }
+
+    /**
+     * 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());
+            assertEquals(
+                    NetworkSelectionStatus.NETWORK_SELECTION_ENABLE,
+                    actual.getNetworkSelectionDisableReason());
+        } 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.getFieldValue(WifiEnterpriseConfig.REALM_KEY),
+                actual.getFieldValue(WifiEnterpriseConfig.REALM_KEY));
+        assertEquals(expected.getFieldValue(WifiEnterpriseConfig.PLMN_KEY),
+                actual.getFieldValue(WifiEnterpriseConfig.PLMN_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/WifiConfigurationUtilTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
index c5b5030..506db91 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
@@ -16,17 +16,21 @@
 
 package com.android.server.wifi;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 import android.content.pm.UserInfo;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiScanner;
 import android.os.UserHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Test;
 
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -37,6 +41,8 @@
     static final int CURRENT_USER_ID = 0;
     static final int CURRENT_USER_MANAGED_PROFILE_USER_ID = 10;
     static final int OTHER_USER_ID = 11;
+    static final String TEST_SSID = "test_ssid";
+    static final String TEST_SSID_1 = "test_ssid_1";
     static final List<UserInfo> PROFILES = Arrays.asList(
             new UserInfo(CURRENT_USER_ID, "owner", 0),
             new UserInfo(CURRENT_USER_MANAGED_PROFILE_USER_ID, "managed profile", 0));
@@ -63,4 +69,320 @@
         configuration.creatorUid = UserHandle.getUid(CURRENT_USER_MANAGED_PROFILE_USER_ID, 0);
         assertTrue(WifiConfigurationUtil.isVisibleToAnyProfile(configuration, PROFILES));
     }
+
+    /**
+     * Verify that new WifiEnterpriseConfig is detected.
+     */
+    @Test
+    public void testEnterpriseConfigAdded() {
+        EnterpriseConfig eapConfig = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT0});
+
+        assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(
+                null, eapConfig.enterpriseConfig));
+    }
+
+    /**
+     * Verify WifiEnterpriseConfig eap change is detected.
+     */
+    @Test
+    public void testEnterpriseConfigEapChangeDetected() {
+        EnterpriseConfig eapConfig = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS);
+        EnterpriseConfig peapConfig = new EnterpriseConfig(WifiEnterpriseConfig.Eap.PEAP);
+
+        assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(eapConfig.enterpriseConfig,
+                peapConfig.enterpriseConfig));
+    }
+
+    /**
+     * Verify WifiEnterpriseConfig phase2 method change is detected.
+     */
+    @Test
+    public void testEnterpriseConfigPhase2ChangeDetected() {
+        EnterpriseConfig eapConfig =
+                new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                        .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2);
+        EnterpriseConfig papConfig =
+                new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                        .setPhase2(WifiEnterpriseConfig.Phase2.PAP);
+
+        assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(eapConfig.enterpriseConfig,
+                papConfig.enterpriseConfig));
+    }
+
+    /**
+     * Verify WifiEnterpriseConfig added Certificate is detected.
+     */
+    @Test
+    public void testCaCertificateAddedDetected() {
+        EnterpriseConfig eapConfigNoCerts = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password");
+
+        EnterpriseConfig eapConfig1Cert = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT0});
+
+        assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(
+                eapConfigNoCerts.enterpriseConfig, eapConfig1Cert.enterpriseConfig));
+    }
+
+    /**
+     * Verify WifiEnterpriseConfig Certificate change is detected.
+     */
+    @Test
+    public void testDifferentCaCertificateDetected() {
+        EnterpriseConfig eapConfig = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT0});
+
+        EnterpriseConfig eapConfigNewCert = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT1});
+
+        assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(eapConfig.enterpriseConfig,
+                eapConfigNewCert.enterpriseConfig));
+    }
+
+    /**
+     * Verify WifiEnterpriseConfig added Certificate changes are detected.
+     */
+    @Test
+    public void testCaCertificateChangesDetected() {
+        EnterpriseConfig eapConfig = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT0});
+
+        EnterpriseConfig eapConfigAddedCert = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
+
+        assertTrue(WifiConfigurationUtil.hasEnterpriseConfigChanged(eapConfig.enterpriseConfig,
+                eapConfigAddedCert.enterpriseConfig));
+    }
+
+    /**
+     * Verify that WifiEnterpriseConfig does not detect changes for identical configs.
+     */
+    @Test
+    public void testWifiEnterpriseConfigNoChanges() {
+        EnterpriseConfig eapConfig = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
+
+        // Just to be clear that check is not against the same object
+        EnterpriseConfig eapConfigSame = new EnterpriseConfig(WifiEnterpriseConfig.Eap.TTLS)
+                .setPhase2(WifiEnterpriseConfig.Phase2.MSCHAPV2)
+                .setIdentity("username", "password")
+                .setCaCerts(new X509Certificate[]{FakeKeys.CA_CERT0, FakeKeys.CA_CERT1});
+
+        assertFalse(WifiConfigurationUtil.hasEnterpriseConfigChanged(eapConfig.enterpriseConfig,
+                eapConfigSame.enterpriseConfig));
+    }
+
+    /**
+     * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
+     * for an open network using {@link WifiConfigurationUtil#createPnoNetwork(
+     * WifiConfiguration, int)}.
+     */
+    @Test
+    public void testCreatePnoNetworkWithOpenNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
+                WifiConfigurationUtil.createPnoNetwork(network, 1);
+        assertEquals(network.SSID, pnoNetwork.ssid);
+        assertEquals(
+                WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND
+                        | WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND, pnoNetwork.flags);
+        assertEquals(WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN, pnoNetwork.authBitField);
+    }
+
+    /**
+     * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
+     * for an open hidden network using {@link WifiConfigurationUtil#createPnoNetwork(
+     * WifiConfiguration, int)}.
+     */
+    @Test
+    public void testCreatePnoNetworkWithOpenHiddenNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenHiddenNetwork();
+        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
+                WifiConfigurationUtil.createPnoNetwork(network, 1);
+        assertEquals(network.SSID, pnoNetwork.ssid);
+        assertEquals(
+                WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND
+                        | WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND
+                        | WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN, pnoNetwork.flags);
+        assertEquals(WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_OPEN, pnoNetwork.authBitField);
+    }
+
+    /**
+     * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
+     * for a PSK network using {@link WifiConfigurationUtil#createPnoNetwork(WifiConfiguration, int)
+     * }.
+     */
+    @Test
+    public void testCreatePnoNetworkWithPskNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
+                WifiConfigurationUtil.createPnoNetwork(network, 1);
+        assertEquals(network.SSID, pnoNetwork.ssid);
+        assertEquals(
+                WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND
+                        | WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND, pnoNetwork.flags);
+        assertEquals(WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_PSK, pnoNetwork.authBitField);
+    }
+
+    /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns true when two WifiConfiguration
+     * objects have the same parameters.
+     */
+    @Test
+    public void testIsSameNetworkReturnsTrueOnSameNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        assertTrue(WifiConfigurationUtil.isSameNetwork(network, network1));
+    }
+
+    /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns false when two WifiConfiguration
+     * objects have the different SSIDs.
+     */
+    @Test
+    public void testIsSameNetworkReturnsFalseOnDifferentSSID() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID_1);
+        assertFalse(WifiConfigurationUtil.isSameNetwork(network, network1));
+    }
+
+    /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns false when two WifiConfiguration
+     * objects have the different security type.
+     */
+    @Test
+    public void testIsSameNetworkReturnsFalseOnDifferentSecurityType() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createEapNetwork(TEST_SSID);
+        assertFalse(WifiConfigurationUtil.isSameNetwork(network, network1));
+    }
+
+
+    /**
+     * Verify the instance of {@link android.net.wifi.WifiScanner.PnoSettings.PnoNetwork} created
+     * for a EAP network using {@link WifiConfigurationUtil#createPnoNetwork(WifiConfiguration, int)
+     * }.
+     */
+    @Test
+    public void testCreatePnoNetworkWithEapNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createEapNetwork();
+        WifiScanner.PnoSettings.PnoNetwork pnoNetwork =
+                WifiConfigurationUtil.createPnoNetwork(network, 1);
+        assertEquals(network.SSID, pnoNetwork.ssid);
+        assertEquals(
+                WifiScanner.PnoSettings.PnoNetwork.FLAG_A_BAND
+                        | WifiScanner.PnoSettings.PnoNetwork.FLAG_G_BAND, pnoNetwork.flags);
+        assertEquals(WifiScanner.PnoSettings.PnoNetwork.AUTH_CODE_EAPOL, pnoNetwork.authBitField);
+    }
+
+    /**
+     * Verify that the generalized
+     * {@link com.android.server.wifi.WifiConfigurationUtil.WifiConfigurationComparator}
+     * can be used to sort a List given a 'compareNetworkWithSameStatus' method.
+     */
+    @Test
+    public void testPnoListComparator() {
+        List<WifiConfiguration> networks = new ArrayList<>();
+        final WifiConfiguration enabledNetwork1 = WifiConfigurationTestUtil.createEapNetwork();
+        enabledNetwork1.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        final WifiConfiguration enabledNetwork2 = WifiConfigurationTestUtil.createEapNetwork();
+        enabledNetwork2.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        final WifiConfiguration tempDisabledNetwork1 = WifiConfigurationTestUtil.createEapNetwork();
+        tempDisabledNetwork1.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        final WifiConfiguration tempDisabledNetwork2 = WifiConfigurationTestUtil.createEapNetwork();
+        tempDisabledNetwork2.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        WifiConfiguration permDisabledNetwork = WifiConfigurationTestUtil.createEapNetwork();
+        permDisabledNetwork.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+
+        // Add all the networks to the list.
+        networks.add(tempDisabledNetwork1);
+        networks.add(enabledNetwork1);
+        networks.add(permDisabledNetwork);
+        networks.add(tempDisabledNetwork2);
+        networks.add(enabledNetwork2);
+
+        // Prefer |enabledNetwork1| over |enabledNetwork2| and |tempDisabledNetwork1| over
+        // |tempDisabledNetwork2|.
+        WifiConfigurationUtil.WifiConfigurationComparator comparator =
+                new WifiConfigurationUtil.WifiConfigurationComparator() {
+                    @Override
+                    public int compareNetworksWithSameStatus(
+                            WifiConfiguration a, WifiConfiguration b) {
+                        if (a == enabledNetwork1 && b == enabledNetwork2) {
+                            return -1;
+                        } else if (b == enabledNetwork1 && a == enabledNetwork2) {
+                            return 1;
+                        } else if (a == tempDisabledNetwork1 && b == tempDisabledNetwork2) {
+                            return -1;
+                        } else if (b == tempDisabledNetwork1 && a == tempDisabledNetwork2) {
+                            return 1;
+                        }
+                        return 0;
+                    }
+                };
+        Collections.sort(networks, comparator);
+
+        // Now ensure that the networks were sorted correctly.
+        assertEquals(enabledNetwork1, networks.get(0));
+        assertEquals(enabledNetwork2, networks.get(1));
+        assertEquals(tempDisabledNetwork1, networks.get(2));
+        assertEquals(tempDisabledNetwork2, networks.get(3));
+        assertEquals(permDisabledNetwork, networks.get(4));
+    }
+
+    private static class EnterpriseConfig {
+        public String eap;
+        public String phase2;
+        public String identity;
+        public String password;
+        public X509Certificate[] caCerts;
+        public WifiEnterpriseConfig enterpriseConfig;
+
+        EnterpriseConfig(int eapMethod) {
+            enterpriseConfig = new WifiEnterpriseConfig();
+            enterpriseConfig.setEapMethod(eapMethod);
+            eap = WifiEnterpriseConfig.Eap.strings[eapMethod];
+        }
+
+        public EnterpriseConfig setPhase2(int phase2Method) {
+            enterpriseConfig.setPhase2Method(phase2Method);
+            phase2 = "auth=" + WifiEnterpriseConfig.Phase2.strings[phase2Method];
+            return this;
+        }
+
+        public EnterpriseConfig setIdentity(String identity, String password) {
+            enterpriseConfig.setIdentity(identity);
+            enterpriseConfig.setPassword(password);
+            this.identity = identity;
+            this.password = password;
+            return this;
+        }
+
+        public EnterpriseConfig setCaCerts(X509Certificate[] certs) {
+            enterpriseConfig.setCaCertificates(certs);
+            caCerts = certs;
+            return this;
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityHelperTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityHelperTest.java
new file mode 100644
index 0000000..4426ec1
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityHelperTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static android.net.wifi.WifiManager.WIFI_FEATURE_CONTROL_ROAMING;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiConnectivityHelper}.
+ */
+@SmallTest
+public class WifiConnectivityHelperTest {
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        setupWifiNative();
+
+        mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
+    }
+
+    /** Cleans up test. */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    private WifiConnectivityHelper mWifiConnectivityHelper;
+    @Mock private WifiNative mWifiNative;
+    @Captor ArgumentCaptor<WifiNative.RoamingConfig> mRoamingConfigCaptor;
+    private int mFeatureSetValue;
+    private static final String TAG = "WifiConnectivityHelperTest";
+    private static final int MAX_BSSID_BLACKLIST_SIZE = 16;
+    private static final int MAX_SSID_WHITELIST_SIZE = 8;
+
+    private void setupWifiNative() {
+        // Return firmware roaming feature as supported by default.
+        when(mWifiNative.getSupportedFeatureSet()).thenReturn(WIFI_FEATURE_CONTROL_ROAMING);
+
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(WifiNative.RoamingCapabilities roamCap) throws Exception {
+                roamCap.maxBlacklistSize = MAX_BSSID_BLACKLIST_SIZE;
+                roamCap.maxWhitelistSize = MAX_SSID_WHITELIST_SIZE;
+                return true;
+            }}).when(mWifiNative).getRoamingCapabilities(anyObject());
+
+        when(mWifiNative.configureRoaming(anyObject())).thenReturn(true);
+    }
+
+    private ArrayList<String> buildBssidBlacklist(int size) {
+        ArrayList<String> bssidBlacklist = new ArrayList<String>();
+
+        for (int i = 0; i < size; i++) {
+            StringBuilder bssid = new StringBuilder("11:22:33:44:55:66");
+            bssid.setCharAt(16, (char) ('0' + i));
+            bssidBlacklist.add(bssid.toString());
+        }
+
+        return bssidBlacklist;
+    }
+
+    private ArrayList<String> buildSsidWhitelist(int size) {
+        ArrayList<String> ssidWhitelist = new ArrayList<String>();
+
+        for (int i = 0; i < size; i++) {
+            StringBuilder ssid = new StringBuilder("\"Test_Ap_0\"");
+            ssid.setCharAt(9, (char) ('0' + i));
+            ssidWhitelist.add(ssid.toString());
+        }
+
+        return ssidWhitelist;
+    }
+
+    /**
+     * When WifiNative has WIFI_FEATURE_CONTROL_ROAMING set, verify that
+     * WifiConnectivityHelper#isFirmwareRoamingSupported returns true.
+     */
+    @Test
+    public void returnFirmwareRoamingSupported() {
+        //By default WifiNative has WIFI_FEATURE_CONTROL_ROAMING set in its feature set.
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        assertTrue(mWifiConnectivityHelper.isFirmwareRoamingSupported());
+    }
+
+    /**
+     * When WifiNative doesn't have WIFI_FEATURE_CONTROL_ROAMING set, verify that
+     * WifiConnectivityHelper#isFirmwareRoamingSupported returns false.
+     */
+    @Test
+    public void returnFirmwareRoamingNotSupported() {
+        when(mWifiNative.getSupportedFeatureSet()).thenReturn(~WIFI_FEATURE_CONTROL_ROAMING);
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
+    }
+
+    /**
+     * Verify that correct firmware roaming capability values are returned if querying
+     * WifiNative for roaming capability succeeded.
+     */
+    @Test
+    public void verifyFirmwareRoamingCapabilityWithSuccessfulNativeCall() {
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        assertTrue(mWifiConnectivityHelper.isFirmwareRoamingSupported());
+        assertEquals(MAX_BSSID_BLACKLIST_SIZE, mWifiConnectivityHelper.getMaxNumBlacklistBssid());
+        assertEquals(MAX_SSID_WHITELIST_SIZE, mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+    }
+
+    /**
+     * Verify that firmware roaming is set to not supported if WifiNative returned firmware roaming
+     * is supported but failed to return roaming capabilities. Firmware roaming capabilty values
+     * should be reset to INVALID_LIST_SIZE.
+     */
+    @Test
+    public void verifyFirmwareRoamingCapabilityWithFailureNativeCall() {
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(WifiNative.RoamingCapabilities roamCap) throws Exception {
+                return false;
+            }}).when(mWifiNative).getRoamingCapabilities(anyObject());
+        assertFalse(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
+        assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
+                mWifiConnectivityHelper.getMaxNumBlacklistBssid());
+        assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
+                mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+    }
+
+    /**
+     * Verify that firmware roaming is set to not supported if WifiNative returned firmware roaming
+     * is supported but returned invalid max BSSID balcklist size. Firmware roaming capabilty values
+     * should be reset to INVALID_LIST_SIZE.
+     */
+    @Test
+    public void verifyFirmwareRoamingCapabilityWithInvalidMaxBssidBlacklistSize() {
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(WifiNative.RoamingCapabilities roamCap) throws Exception {
+                roamCap.maxBlacklistSize = -5;
+                roamCap.maxWhitelistSize = MAX_SSID_WHITELIST_SIZE;
+                return true;
+            }}).when(mWifiNative).getRoamingCapabilities(anyObject());
+        assertFalse(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
+        assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
+                mWifiConnectivityHelper.getMaxNumBlacklistBssid());
+        assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
+                mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+    }
+
+    /**
+     * Verify that firmware roaming is set to not supported if WifiNative returned firmware roaming
+     * is supported but returned invalid max SSID whitelist size. Firmware roaming capabilty values
+     * should be reset to INVALID_LIST_SIZE.
+     */
+    @Test
+    public void verifyFirmwareRoamingCapabilityWithInvalidMaxSsidWhitelistSize() {
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(WifiNative.RoamingCapabilities roamCap) throws Exception {
+                roamCap.maxBlacklistSize = MAX_BSSID_BLACKLIST_SIZE;
+                roamCap.maxWhitelistSize = -2;
+                return true;
+            }}).when(mWifiNative).getRoamingCapabilities(anyObject());
+        assertFalse(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        assertFalse(mWifiConnectivityHelper.isFirmwareRoamingSupported());
+        assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
+                mWifiConnectivityHelper.getMaxNumBlacklistBssid());
+        assertEquals(WifiConnectivityHelper.INVALID_LIST_SIZE,
+                mWifiConnectivityHelper.getMaxNumWhitelistSsid());
+    }
+
+    /**
+     * Verify that correct size BSSID blacklist and SSID whitelist are accepted.
+     */
+    @Test
+    public void verifySetFirmwareRoamingConfigurationWithGoodInput() {
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE);
+        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE);
+        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+
+        blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE - 2);
+        whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE - 3);
+        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+    }
+
+    /**
+     * Verify that null BSSID blacklist or SSID whitelist is rejected.
+     */
+    @Test
+    public void verifySetFirmwareRoamingConfigurationWithNullInput() {
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE);
+        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE);
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(null, whitelist));
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, null));
+    }
+
+    /**
+     * Verify that incorrect size BSSID blacklist is rejected.
+     */
+    @Test
+    public void verifySetFirmwareRoamingConfigurationWithIncorrectBlacklist() {
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE + 1);
+        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE);
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+    }
+
+    /**
+     * Verify that incorrect size SSID whitelist is rejected.
+     */
+    @Test
+    public void verifySetFirmwareRoamingConfigurationWithIncorrectWhitelist() {
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        ArrayList<String> blacklist = buildBssidBlacklist(MAX_BSSID_BLACKLIST_SIZE);
+        ArrayList<String> whitelist = buildSsidWhitelist(MAX_SSID_WHITELIST_SIZE + 1);
+        assertFalse(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+    }
+
+    /**
+     * Verify that empty BSSID blacklist and SSID whitelist are sent to WifiNative
+     * to reset the firmware roaming configuration.
+     */
+    @Test
+    public void verifySetFirmwareRoamingConfigurationWithEmptyBlacklistAndWhitelist() {
+        assertTrue(mWifiConnectivityHelper.getFirmwareRoamingInfo());
+        ArrayList<String> blacklist = buildBssidBlacklist(0);
+        ArrayList<String> whitelist = buildSsidWhitelist(0);
+        assertTrue(mWifiConnectivityHelper.setFirmwareRoamingConfiguration(blacklist, whitelist));
+        verify(mWifiNative).configureRoaming(mRoamingConfigCaptor.capture());
+        assertEquals(0, mRoamingConfigCaptor.getValue().blacklistBssids.size());
+        assertEquals(0, mRoamingConfigCaptor.getValue().whitelistSsids.size());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 281ffa1..68c2ca3 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -22,14 +22,17 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.net.NetworkScoreManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.ScanResult.InformationElement;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
+import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiScanner.PnoScanListener;
 import android.net.wifi.WifiScanner.PnoSettings;
@@ -39,47 +42,54 @@
 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 android.util.LocalLog;
 
 import com.android.internal.R;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiConnectivityManager}.
  */
 @SmallTest
 public class WifiConnectivityManagerTest {
-
     /**
      * Called before each test
      */
     @Before
     public void setUp() throws Exception {
-        mWifiInjector = mockWifiInjector();
+        MockitoAnnotations.initMocks(this);
         mResource = mockResource();
-        mAlarmManager = new MockAlarmManager();
+        mAlarmManager = new TestAlarmManager();
         mContext = mockContext();
+        mLocalLog = new LocalLog(512);
         mWifiStateMachine = mockWifiStateMachine();
         mWifiConfigManager = mockWifiConfigManager();
         mWifiInfo = getWifiInfo();
         mScanData = mockScanData();
         mWifiScanner = mockWifiScanner();
-        mWifiQNS = mockWifiQualifiedNetworkSelector();
-        mWifiConnectivityManager = new WifiConnectivityManager(mContext, mWifiStateMachine,
-                mWifiScanner, mWifiConfigManager, mWifiInfo, mWifiQNS, mWifiInjector,
-                mLooper.getLooper(), true);
+        mWifiConnectivityHelper = mockWifiConnectivityHelper();
+        mWifiNS = mockWifiNetworkSelector();
+        mWifiConnectivityManager = createConnectivityManager();
+        verify(mWifiConfigManager).setOnSavedNetworkUpdateListener(anyObject());
         mWifiConnectivityManager.setWifiEnabled(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
     }
 
     /**
@@ -91,33 +101,50 @@
     }
 
     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 WifiNetworkSelector mWifiNS;
     private WifiStateMachine mWifiStateMachine;
     private WifiScanner mWifiScanner;
+    private WifiConnectivityHelper mWifiConnectivityHelper;
     private ScanData mScanData;
     private WifiConfigManager mWifiConfigManager;
     private WifiInfo mWifiInfo;
-    private Clock mClock = mock(Clock.class);
-    private WifiLastResortWatchdog mWifiLastResortWatchdog;
-    private WifiMetrics mWifiMetrics;
-    private WifiInjector mWifiInjector;
+    private LocalLog mLocalLog;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private NetworkScoreManager mNetworkScoreManager;
+    @Mock private Clock mClock;
+    @Mock private WifiLastResortWatchdog mWifiLastResortWatchdog;
+    @Mock private WifiMetrics mWifiMetrics;
+    @Mock private WifiNetworkScoreCache mScoreCache;
+    @Captor ArgumentCaptor<ScanResult> mCandidateScanResultCaptor;
+    @Captor ArgumentCaptor<ArrayList<String>> mBssidBlacklistCaptor;
+    @Captor ArgumentCaptor<ArrayList<String>> mSsidWhitelistCaptor;
+    private MockResources mResources;
 
     private static final int CANDIDATE_NETWORK_ID = 0;
     private static final String CANDIDATE_SSID = "\"AnSsid\"";
     private static final String CANDIDATE_BSSID = "6c:f3:7f:ae:8c:f3";
-    private static final String TAG = "WifiConnectivityManager Unit Test";
+    private static final String INVALID_SCAN_RESULT_BSSID = "6c:f3:7f:ae:8c:f4";
     private static final long CURRENT_SYSTEM_TIME_MS = 1000;
+    private static final int MAX_BSSID_BLACKLIST_SIZE = 16;
+
 
     Resources mockResource() {
         Resources resource = mock(Resources.class);
 
         when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
         when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24);
-
+        when(resource.getBoolean(
+                R.bool.config_wifi_framework_enable_associated_network_selection)).thenReturn(true);
+        when(resource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz))
+                .thenReturn(-60);
+        when(resource.getInteger(
+                R.integer.config_wifi_framework_current_network_boost)).thenReturn(16);
         return resource;
     }
 
@@ -151,17 +178,17 @@
 
         // do a synchronous answer for the ScanListener callbacks
         doAnswer(new AnswerWithArguments() {
-                public void answer(ScanSettings settings, ScanListener listener,
-                        WorkSource workSource) throws Exception {
-                    listener.onResults(scanDatas);
-                }}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject());
+            public void answer(ScanSettings settings, ScanListener listener,
+                    WorkSource workSource) throws Exception {
+                listener.onResults(scanDatas);
+            }}).when(scanner).startBackgroundScan(anyObject(), anyObject(), anyObject());
 
         doAnswer(new AnswerWithArguments() {
-                public void answer(ScanSettings settings, ScanListener listener,
-                        WorkSource workSource) throws Exception {
-                    listener.onResults(scanDatas);
-                    allSingleScanListenerCaptor.getValue().onResults(scanDatas);
-                }}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
+            public void answer(ScanSettings settings, ScanListener listener,
+                    WorkSource workSource) throws Exception {
+                listener.onResults(scanDatas);
+                allSingleScanListenerCaptor.getValue().onResults(scanDatas);
+            }}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
 
         // This unfortunately needs to be a somewhat valid scan result, otherwise
         // |ScanDetailUtil.toScanDetail| raises exceptions.
@@ -173,7 +200,7 @@
         scanResults[0].informationElements[0] = new InformationElement();
         scanResults[0].informationElements[0].id = InformationElement.EID_SSID;
         scanResults[0].informationElements[0].bytes =
-                CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8);
+            CANDIDATE_SSID.getBytes(StandardCharsets.UTF_8);
 
         doAnswer(new AnswerWithArguments() {
             public void answer(ScanSettings settings, PnoSettings pnoSettings,
@@ -190,10 +217,18 @@
         return scanner;
     }
 
+    WifiConnectivityHelper mockWifiConnectivityHelper() {
+        WifiConnectivityHelper connectivityHelper = mock(WifiConnectivityHelper.class);
+
+        when(connectivityHelper.isFirmwareRoamingSupported()).thenReturn(false);
+        when(connectivityHelper.getMaxNumBlacklistBssid()).thenReturn(MAX_BSSID_BLACKLIST_SIZE);
+
+        return connectivityHelper;
+    }
+
     WifiStateMachine mockWifiStateMachine() {
         WifiStateMachine stateMachine = mock(WifiStateMachine.class);
 
-        when(stateMachine.getFrequencyBand()).thenReturn(1);
         when(stateMachine.isLinkDebouncing()).thenReturn(false);
         when(stateMachine.isConnected()).thenReturn(false);
         when(stateMachine.isDisconnected()).thenReturn(true);
@@ -202,20 +237,20 @@
         return stateMachine;
     }
 
-    WifiQualifiedNetworkSelector mockWifiQualifiedNetworkSelector() {
-        WifiQualifiedNetworkSelector qns = mock(WifiQualifiedNetworkSelector.class);
+    WifiNetworkSelector mockWifiNetworkSelector() {
+        WifiNetworkSelector ns = mock(WifiNetworkSelector.class);
 
         WifiConfiguration candidate = generateWifiConfig(
                 0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
-        candidate.BSSID = CANDIDATE_BSSID;
+        candidate.BSSID = WifiStateMachine.SUPPLICANT_BSSID_ANY;
         ScanResult candidateScanResult = new ScanResult();
         candidateScanResult.SSID = CANDIDATE_SSID;
         candidateScanResult.BSSID = CANDIDATE_BSSID;
         candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
 
-        when(qns.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
-              anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(candidate);
-        return qns;
+        when(ns.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(candidate);
+        return ns;
     }
 
     WifiInfo getWifiInfo() {
@@ -231,38 +266,30 @@
     WifiConfigManager mockWifiConfigManager() {
         WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
 
-        when(wifiConfigManager.getWifiConfiguration(anyInt())).thenReturn(null);
-        when(wifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        wifiConfigManager.mThresholdSaturatedRssi24 = new AtomicInteger(
-                WifiQualifiedNetworkSelector.RSSI_SATURATION_2G_BAND);
-        wifiConfigManager.mCurrentNetworkBoost = new AtomicInteger(
-                WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD);
+        when(wifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(null);
 
         // Pass dummy pno network list, otherwise Pno scan requests will not be triggered.
         PnoSettings.PnoNetwork pnoNetwork = new PnoSettings.PnoNetwork(CANDIDATE_SSID);
         ArrayList<PnoSettings.PnoNetwork> pnoNetworkList = new ArrayList<>();
         pnoNetworkList.add(pnoNetwork);
-        when(wifiConfigManager.retrieveDisconnectedPnoNetworkList()).thenReturn(pnoNetworkList);
-        when(wifiConfigManager.retrieveConnectedPnoNetworkList()).thenReturn(pnoNetworkList);
+        when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
+        when(wifiConfigManager.retrievePnoNetworkList()).thenReturn(pnoNetworkList);
 
         return wifiConfigManager;
     }
 
-    WifiInjector mockWifiInjector() {
-        WifiInjector wifiInjector = mock(WifiInjector.class);
-        mWifiLastResortWatchdog = mock(WifiLastResortWatchdog.class);
-        mWifiMetrics = mock(WifiMetrics.class);
-        when(wifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
-        when(wifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
-        when(wifiInjector.getClock()).thenReturn(mClock);
-        return wifiInjector;
+    WifiConnectivityManager createConnectivityManager() {
+        return new WifiConnectivityManager(mContext, mWifiStateMachine, mWifiScanner,
+                mWifiConfigManager, mWifiInfo, mWifiNS, mWifiConnectivityHelper,
+                mWifiLastResortWatchdog, mWifiMetrics, mLooper.getLooper(), mClock,
+                mLocalLog, true, mFrameworkFacade, null, null, null);
     }
 
     /**
      *  Wifi enters disconnected state while screen is on.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * WifiStateMachine.autoConnectToNetwork() with the
+     * WifiStateMachine.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
@@ -274,15 +301,14 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        verify(mWifiStateMachine).autoConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
     /**
      *  Wifi enters connected state while screen is on.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * WifiStateMachine.autoConnectToNetwork() with the
+     * WifiStateMachine.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
@@ -294,15 +320,14 @@
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
-        verify(mWifiStateMachine).autoConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
     /**
      *  Screen turned on while WiFi in disconnected state.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * WifiStateMachine.autoConnectToNetwork() with the
+     * WifiStateMachine.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
@@ -314,7 +339,7 @@
         // Set screen to on
         mWifiConnectivityManager.handleScreenStateChanged(true);
 
-        verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork(
+        verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
@@ -322,7 +347,7 @@
      *  Screen turned on while WiFi in connected state.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * WifiStateMachine.autoConnectToNetwork() with the
+     * WifiStateMachine.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
@@ -334,7 +359,7 @@
         // Set screen to on
         mWifiConnectivityManager.handleScreenStateChanged(true);
 
-        verify(mWifiStateMachine, atLeastOnce()).autoConnectToNetwork(
+        verify(mWifiStateMachine, atLeastOnce()).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
@@ -343,29 +368,32 @@
      *  auto roaming is disabled.
      *
      * Expected behavior: WifiConnectivityManager doesn't invoke
-     * WifiStateMachine.autoConnectToNetwork() because roaming
+     * WifiStateMachine.startConnectToNetwork() because roaming
      * is turned off.
      */
     @Test
     public void turnScreenOnWhenWifiInConnectedStateRoamingDisabled() {
+        // Turn off auto roaming
+        when(mResource.getBoolean(
+                R.bool.config_wifi_framework_enable_associated_network_selection))
+                .thenReturn(false);
+        mWifiConnectivityManager = createConnectivityManager();
+
         // Set WiFi to connected state
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
-        // Turn off auto roaming
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(false);
-
         // Set screen to on
         mWifiConnectivityManager.handleScreenStateChanged(true);
 
-        verify(mWifiStateMachine, times(0)).autoConnectToNetwork(
+        verify(mWifiStateMachine, times(0)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
     /**
      * Multiple back to back connection attempts within the rate interval should be rate limited.
      *
-     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
+     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
      * with the expected candidate network ID and BSSID for only the expected number of times within
      * the given interval.
      */
@@ -382,7 +410,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);
@@ -390,13 +418,13 @@
         }
         // 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);
 
         // Verify that we attempt to connect upto the rate.
-        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
+        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
@@ -404,7 +432,7 @@
      * Multiple back to back connection attempts outside the rate interval should not be rate
      * limited.
      *
-     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
+     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
      * with the expected candidate network ID and BSSID for only the expected number of times within
      * the given interval.
      */
@@ -421,7 +449,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);
@@ -429,7 +457,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(
@@ -437,14 +465,14 @@
         numAttempts++;
 
         // Verify that all the connection attempts went through
-        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
+        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
     /**
      * Multiple back to back connection attempts after a user selection should not be rate limited.
      *
-     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
+     * Expected behavior: WifiConnectivityManager calls WifiStateMachine.startConnectToNetwork()
      * with the expected candidate network ID and BSSID for only the expected number of times within
      * the given interval.
      */
@@ -461,18 +489,19 @@
         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);
             numAttempts++;
         }
 
-        mWifiConnectivityManager.connectToUserSelectNetwork(CANDIDATE_NETWORK_ID, false);
+        mWifiConnectivityManager.setUserConnectChoice(CANDIDATE_NETWORK_ID);
+        mWifiConnectivityManager.prepareForForcedConnection(CANDIDATE_NETWORK_ID);
 
         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);
@@ -480,7 +509,7 @@
         }
 
         // Verify that all the connection attempts went through
-        verify(mWifiStateMachine, times(numAttempts)).autoConnectToNetwork(
+        verify(mWifiStateMachine, times(numAttempts)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
@@ -492,9 +521,10 @@
      * because of their low RSSI values.
      */
     @Test
-    public void PnoRetryForLowRssiNetwork() {
-        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
-              anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null);
+    @Ignore("b/32977707")
+    public void pnoRetryForLowRssiNetwork() {
+        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(null);
 
         // Set screen to off
         mWifiConnectivityManager.handleScreenStateChanged(false);
@@ -513,7 +543,7 @@
                 .getLowRssiNetworkRetryDelay();
 
         assertEquals(lowRssiNetworkRetryDelayStartValue * 2,
-            lowRssiNetworkRetryDelayAfterPnoValue);
+                lowRssiNetworkRetryDelayAfterPnoValue);
     }
 
     /**
@@ -523,6 +553,7 @@
      * a candidate while watchdog single scan did.
      */
     @Test
+    @Ignore("b/32977707")
     public void watchdogBitePnoBadIncrementsMetrics() {
         // Set screen to off
         mWifiConnectivityManager.handleScreenStateChanged(false);
@@ -546,10 +577,11 @@
      * a candidate which was the same with watchdog single scan.
      */
     @Test
+    @Ignore("b/32977707")
     public void watchdogBitePnoGoodIncrementsMetrics() {
         // Qns returns no candidate after watchdog single scan.
-        when(mWifiQNS.selectQualifiedNetwork(anyBoolean(), anyBoolean(), anyObject(),
-                anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(null);
+        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(null);
 
         // Set screen to off
         mWifiConnectivityManager.handleScreenStateChanged(false);
@@ -576,7 +608,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);
@@ -584,7 +616,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(
@@ -592,12 +624,12 @@
 
         // Get the first periodic scan interval
         long firstIntervalMs = mAlarmManager
-                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
-                    - currentTimeStamp;
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
         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);
@@ -605,14 +637,14 @@
 
         // Get the second periodic scan interval
         long secondIntervalMs = mAlarmManager
-                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
-                    - currentTimeStamp;
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
 
         // Verify the intervals are exponential back off
         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;
@@ -623,7 +655,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);
@@ -639,7 +671,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);
@@ -647,7 +679,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(
@@ -655,12 +687,12 @@
 
         // Get the first periodic scan interval
         long firstIntervalMs = mAlarmManager
-                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
-                    - currentTimeStamp;
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
         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);
@@ -668,14 +700,14 @@
 
         // Get the second periodic scan interval
         long secondIntervalMs = mAlarmManager
-                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
-                    - currentTimeStamp;
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
 
         // Verify the intervals are exponential back off
         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;
@@ -686,23 +718,23 @@
                     .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);
     }
 
     /**
-     *  When screen on trigger two connection state change events back to back to
-     *  verify that the minium scan interval is enforced.
+     *  When screen on trigger a disconnected state change event then a connected state
+     *  change event back to back to verify that the minium scan interval is enforced.
      *
      * Expected behavior: WifiConnectivityManager start the second periodic single
      * scan PERIODIC_SCAN_INTERVAL_MS after the first one.
      */
     @Test
-    public void checkMinimumPeriodicScanIntervalWhenScreenOn() {
+    public void checkMinimumPeriodicScanIntervalWhenScreenOnAndConnected() {
         long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set screen to ON
         mWifiConnectivityManager.handleScreenStateChanged(true);
@@ -710,30 +742,79 @@
         // 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;
-        long firstScanTimeStamp = currentTimeStamp;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        long scanForDisconnectedTimeStamp = currentTimeStamp;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        // Set WiFi to disconnected state which triggers a scan immediately
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+        verify(mWifiScanner, times(1)).startScan(anyObject(), anyObject(), anyObject());
+
+        // Set up time stamp for when entering CONNECTED state
+        currentTimeStamp += 2000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        // Set WiFi to connected state to trigger its periodic scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // The very first scan triggered for connected state is actually via the alarm timer
+        // and it obeys the minimum scan interval
+        long firstScanForConnectedTimeStamp = mAlarmManager
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
+
+        // Verify that the first scan for connected state is scheduled PERIODIC_SCAN_INTERVAL_MS
+        // after the scan for disconnected state
+        assertEquals(firstScanForConnectedTimeStamp, scanForDisconnectedTimeStamp
+                + WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
+    }
+
+    /**
+     *  When screen on trigger a connected state change event then a disconnected state
+     *  change event back to back to verify that a scan is fired immediately for the
+     *  disconnected state change event.
+     *
+     * Expected behavior: WifiConnectivityManager directly starts the periodic immediately
+     * for the disconnected state change event. The second scan for disconnected state is
+     * via alarm timer.
+     */
+    @Test
+    public void scanImmediatelyWhenScreenOnAndDisconnected() {
+        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        // Set screen to ON
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // 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;
+        long scanForConnectedTimeStamp = currentTimeStamp;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Set WiFi to connected state to trigger the periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
+        verify(mWifiScanner, times(1)).startScan(anyObject(), anyObject(), anyObject());
 
-        // Set the second scan attempt time stamp.
+        // Set up the time stamp for when entering DISCONNECTED state
         currentTimeStamp += 2000;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        long enteringDisconnectedStateTimeStamp = currentTimeStamp;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
-        // Set WiFi to disconnected state to trigger another periodic scan
+        // Set WiFi to disconnected state to trigger its periodic scan
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
 
-        // Get the second periodic scan actual time stamp
-        long secondScanTimeStamp = mAlarmManager
-                    .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
+        // Verify the very first scan for DISCONNECTED state is fired immediately
+        verify(mWifiScanner, times(2)).startScan(anyObject(), anyObject(), anyObject());
+        long secondScanForDisconnectedTimeStamp = mAlarmManager
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG);
 
-        // Verify that the second scan is scheduled PERIODIC_SCAN_INTERVAL_MS after the
-        // very first scan.
-        assertEquals(secondScanTimeStamp, firstScanTimeStamp
-                       + WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
-
+        // Verify that the second scan is scheduled PERIODIC_SCAN_INTERVAL_MS after
+        // entering DISCONNECTED state.
+        assertEquals(secondScanForDisconnectedTimeStamp, enteringDisconnectedStateTimeStamp
+                + WifiConnectivityManager.PERIODIC_SCAN_INTERVAL_MS);
     }
 
     /**
@@ -747,7 +828,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);
@@ -756,7 +837,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(
@@ -764,7 +845,7 @@
 
         // Set the second scan attempt time stamp
         currentTimeStamp += 2000;
-        when(mClock.elapsedRealtime()).thenReturn(currentTimeStamp);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
 
         // Allow untrusted networks so WifiConnectivityManager starts a periodic scan
         // immediately.
@@ -796,15 +877,13 @@
 
         when(mWifiStateMachine.getCurrentWifiConfiguration())
                 .thenReturn(new WifiConfiguration());
-        when(mWifiStateMachine.getFrequencyBand())
-                .thenReturn(WifiManager.WIFI_FREQUENCY_BAND_5GHZ);
-        when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt()))
-                .thenReturn(channelList);
+        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
+                anyInt())).thenReturn(channelList);
 
         doAnswer(new AnswerWithArguments() {
             public void answer(ScanSettings settings, ScanListener listener,
                     WorkSource workSource) throws Exception {
-                assertEquals(settings.band, WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS);
+                assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
                 assertNull(settings.channels);
             }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
 
@@ -827,8 +906,8 @@
      */
     @Test
     public void checkSingleScanSettingsWhenConnectedWithHighDataRate() {
-        mWifiInfo.txSuccessRate = WifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
-        mWifiInfo.rxSuccessRate = WifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
+        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
+        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
 
         final HashSet<Integer> channelList = new HashSet<>();
         channelList.add(1);
@@ -837,8 +916,8 @@
 
         when(mWifiStateMachine.getCurrentWifiConfiguration())
                 .thenReturn(new WifiConfiguration());
-        when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt()))
-                .thenReturn(channelList);
+        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
+                anyInt())).thenReturn(channelList);
 
         doAnswer(new AnswerWithArguments() {
             public void answer(ScanSettings settings, ScanListener listener,
@@ -869,22 +948,20 @@
      */
     @Test
     public void checkSingleScanSettingsWhenConnectedWithHighDataRateNotInCache() {
-        mWifiInfo.txSuccessRate = WifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
-        mWifiInfo.rxSuccessRate = WifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
+        mWifiInfo.txSuccessRate = WifiConnectivityManager.MAX_TX_PACKET_FOR_FULL_SCANS * 2;
+        mWifiInfo.rxSuccessRate = WifiConnectivityManager.MAX_RX_PACKET_FOR_FULL_SCANS * 2;
 
         final HashSet<Integer> channelList = new HashSet<>();
 
         when(mWifiStateMachine.getCurrentWifiConfiguration())
                 .thenReturn(new WifiConfiguration());
-        when(mWifiStateMachine.getFrequencyBand())
-                .thenReturn(WifiManager.WIFI_FREQUENCY_BAND_5GHZ);
-        when(mWifiConfigManager.makeChannelList(any(WifiConfiguration.class), anyInt()))
-                .thenReturn(channelList);
+        when(mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(anyInt(), anyLong(),
+                anyInt())).thenReturn(channelList);
 
         doAnswer(new AnswerWithArguments() {
             public void answer(ScanSettings settings, ScanListener listener,
                     WorkSource workSource) throws Exception {
-                assertEquals(settings.band, WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS);
+                assertEquals(settings.band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
                 assertNull(settings.channels);
             }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
 
@@ -939,7 +1016,7 @@
      * act on them.
      *
      * Expected behavior: WifiConnectivityManager calls
-     * WifiStateMachine.autoConnectToNetwork() with the
+     * WifiStateMachine.startConnectToNetwork() with the
      * expected candidate network ID and BSSID.
      */
     @Test
@@ -952,8 +1029,7 @@
 
         // Verify that WCM receives the scan results and initiates a connection
         // to the network.
-        verify(mWifiStateMachine).autoConnectToNetwork(
-                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
 
     /**
@@ -961,7 +1037,7 @@
      *  results.
      *
      * Expected behavior: WifiConnectivityManager doesn't invoke
-     * WifiStateMachine.autoConnectToNetwork() when full band scan
+     * WifiStateMachine.startConnectToNetwork() when full band scan
      * results are not available.
      */
     @Test
@@ -978,7 +1054,7 @@
         mWifiConnectivityManager.forceConnectivityScan();
 
         // No roaming because no full band scan results.
-        verify(mWifiStateMachine, times(0)).autoConnectToNetwork(
+        verify(mWifiStateMachine, times(0)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
 
         // Set up as full band scan results.
@@ -989,7 +1065,493 @@
         mWifiConnectivityManager.forceConnectivityScan();
 
         // Roaming attempt because full band scan results are available.
-        verify(mWifiStateMachine).autoConnectToNetwork(
+        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+    }
+
+    /**
+     *  Verify the BSSID blacklist implementation.
+     *
+     * Expected behavior: A BSSID gets blacklisted after being disabled
+     * for 3 times, and becomes available after being re-enabled. Firmware
+     * controlled roaming is supported, its roaming configuration needs to be
+     * updated as well.
+     */
+    @Test
+    public void blacklistAndReenableBssid() {
+        String bssid = "6c:f3:7f:ae:8c:f3";
+
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        // Verify that a BSSID gets blacklisted only after being disabled
+        // for BSSID_BLACKLIST_THRESHOLD times for reasons other than
+        // REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA.
+        for (int i = 0; i < WifiConnectivityManager.BSSID_BLACKLIST_THRESHOLD; i++) {
+            assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
+            mWifiConnectivityManager.trackBssid(bssid, false, 1);
+        }
+
+        // Verify the BSSID is now blacklisted.
+        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
+        // Verify the BSSID gets sent to firmware.
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+
+        // Re-enable the bssid.
+        mWifiConnectivityManager.trackBssid(bssid, true, 1);
+
+        // Verify the bssid is no longer blacklisted.
+        assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
+        // Verify the BSSID gets cleared from firmware.
+        verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertFalse(mBssidBlacklistCaptor.getValue().contains(bssid));
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+    }
+
+    /**
+     *  Verify that a network gets blacklisted immediately if it is unable
+     *  to handle new stations.
+     */
+    @Test
+    public void blacklistNetworkImmediatelyIfApHasNoCapacityForNewStation() {
+        String bssid = "6c:f3:7f:ae:8c:f3";
+
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        // Blacklist the BSSID
+        mWifiConnectivityManager.trackBssid(bssid, false,
+                WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
+
+        // Verify the BSSID is now blacklisted.
+        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
+        // Verify the BSSID gets sent to firmware.
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+    }
+
+    /**
+     *  Verify that a blacklisted BSSID becomes available only after
+     *  BSSID_BLACKLIST_EXPIRE_TIME_MS.
+     */
+    @Test
+    public void verifyBlacklistRefreshedAfterScanResults() {
+        String bssid = "6c:f3:7f:ae:8c:f3";
+
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+        // Blacklist the BSSID.
+        mWifiConnectivityManager.trackBssid(bssid, false,
+                WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
+
+        // Verify the BSSID is now blacklisted.
+        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
+        // Verify the BSSID gets sent to firmware.
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+
+        // Force a connectivity scan in less than BSSID_BLACKLIST_EXPIRE_TIME_MS.
+        // Arrival of scan results will trigger WifiConnectivityManager to refresh its
+        // BSSID blacklist. Verify that the blacklisted BSSId is not freed because
+        // its blacklist expiration time hasn't reached yet.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS / 2);
+        mWifiConnectivityManager.forceConnectivityScan();
+        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
+
+        // Force another connectivity scan at BSSID_BLACKLIST_EXPIRE_TIME_MS from when the
+        // BSSID was blacklisted. Verify that the blacklisted BSSId is freed.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiConnectivityManager.BSSID_BLACKLIST_EXPIRE_TIME_MS);
+        mWifiConnectivityManager.forceConnectivityScan();
+
+        // Verify the BSSID is no longer blacklisted.
+        assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
+        // Verify the BSSID gets cleared from firmware.
+        verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertFalse(mBssidBlacklistCaptor.getValue().contains(bssid));
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+    }
+
+    /**
+     *  Verify that BSSID blacklist gets cleared when exiting Wifi client mode.
+     */
+    @Test
+    public void clearBssidBlacklistWhenExitingWifiClientMode() {
+        String bssid = "6c:f3:7f:ae:8c:f3";
+
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Blacklist the BSSID.
+        mWifiConnectivityManager.trackBssid(bssid, false,
+                WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
+
+        // Verify the BSSID is now blacklisted.
+        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
+        // Verify the BSSID gets sent to firmware.
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+
+        // Exit Wifi client mode.
+        mWifiConnectivityManager.setWifiEnabled(false);
+
+        // Verify the BSSID blacklist is empty.
+        assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
+        verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertTrue(mBssidBlacklistCaptor.getValue().isEmpty());
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+    }
+
+    /**
+     *  Verify that BSSID blacklist gets cleared when preparing for a forced connection
+     *  initiated by user/app.
+     */
+    @Test
+    public void clearBssidBlacklistWhenPreparingForForcedConnection() {
+        String bssid = "6c:f3:7f:ae:8c:f3";
+
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Blacklist the BSSID.
+        mWifiConnectivityManager.trackBssid(bssid, false,
+                WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
+
+        // Verify the BSSID is now blacklisted.
+        assertTrue(mWifiConnectivityManager.isBssidDisabled(bssid));
+        // Verify the BSSID gets sent to firmware.
+        verify(mWifiConnectivityHelper).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertTrue(mBssidBlacklistCaptor.getValue().contains(bssid));
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+
+        // Prepare for a forced connection attempt.
+        mWifiConnectivityManager.prepareForForcedConnection(1);
+
+        // Verify the BSSID blacklist is empty.
+        assertFalse(mWifiConnectivityManager.isBssidDisabled(bssid));
+        verify(mWifiConnectivityHelper, times(2)).setFirmwareRoamingConfiguration(
+                mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+        assertTrue(mBssidBlacklistCaptor.getValue().isEmpty());
+        assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+    }
+
+    /**
+    /**
+     *  Verify that BSSID blacklist gets trimmed down to fit firmware capability.
+     */
+    @Test
+    public void trimDownBssidBlacklistForFirmware() {
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Blacklist more than MAX_BSSID_BLACKLIST_SIZE BSSIDs.
+        for (int i = 0; i < MAX_BSSID_BLACKLIST_SIZE + 6; i++) {
+            StringBuilder bssid = new StringBuilder("55:44:33:22:11:00");
+            bssid.setCharAt(16, (char) ('0' + i));
+            mWifiConnectivityManager.trackBssid(bssid.toString(), false,
+                    WifiConnectivityManager.REASON_CODE_AP_UNABLE_TO_HANDLE_NEW_STA);
+            // Verify that up to MAX_BSSID_BLACKLIST_SIZE BSSIDs gets sent to firmware.
+            verify(mWifiConnectivityHelper, times(i + 1)).setFirmwareRoamingConfiguration(
+                    mBssidBlacklistCaptor.capture(), mSsidWhitelistCaptor.capture());
+            assertEquals((i + 1) <  MAX_BSSID_BLACKLIST_SIZE ? (i + 1) : MAX_BSSID_BLACKLIST_SIZE,
+                    mBssidBlacklistCaptor.getValue().size());
+            assertTrue(mSsidWhitelistCaptor.getValue().isEmpty());
+        }
+    }
+
+    /**
+     * When WifiConnectivityManager is on and Wifi client mode is enabled, framework
+     * queries firmware via WifiConnectivityHelper to check if firmware roaming is
+     * supported and its capability.
+     *
+     * Expected behavior: WifiConnectivityManager#setWifiEnabled calls into
+     * WifiConnectivityHelper#getFirmwareRoamingInfo
+     */
+    @Test
+    public void verifyGetFirmwareRoamingInfoIsCalledWhenEnableWiFiAndWcmOn() {
+        reset(mWifiConnectivityHelper);
+        // WifiConnectivityManager is on by default
+        mWifiConnectivityManager.setWifiEnabled(true);
+        verify(mWifiConnectivityHelper).getFirmwareRoamingInfo();
+    }
+
+    /**
+     * When WifiConnectivityManager is off,  verify that framework does not
+     * query firmware via WifiConnectivityHelper to check if firmware roaming is
+     * supported and its capability when enabling Wifi client mode.
+     *
+     * Expected behavior: WifiConnectivityManager#setWifiEnabled does not call into
+     * WifiConnectivityHelper#getFirmwareRoamingInfo
+     */
+    @Test
+    public void verifyGetFirmwareRoamingInfoIsNotCalledWhenEnableWiFiAndWcmOff() {
+        reset(mWifiConnectivityHelper);
+        mWifiConnectivityManager.enable(false);
+        mWifiConnectivityManager.setWifiEnabled(true);
+        verify(mWifiConnectivityHelper, times(0)).getFirmwareRoamingInfo();
+    }
+
+    /*
+     * Firmware supports controlled roaming.
+     * Connect to a network which doesn't have a config specified BSSID.
+     *
+     * Expected behavior: WifiConnectivityManager calls
+     * WifiStateMachine.startConnectToNetwork() with the
+     * expected candidate network ID, and the BSSID value should be
+     * 'any' since firmware controls the roaming.
+     */
+    @Test
+    public void useAnyBssidToConnectWhenFirmwareRoamingOnAndConfigHasNoBssidSpecified() {
+        // Firmware controls roaming
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mWifiStateMachine).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, WifiStateMachine.SUPPLICANT_BSSID_ANY);
+    }
+
+    /*
+     * Firmware supports controlled roaming.
+     * Connect to a network which has a config specified BSSID.
+     *
+     * Expected behavior: WifiConnectivityManager calls
+     * WifiStateMachine.startConnectToNetwork() with the
+     * expected candidate network ID, and the BSSID value should be
+     * the config specified one.
+     */
+    @Test
+    public void useConfigSpecifiedBssidToConnectWhenFirmwareRoamingOn() {
+        // Firmware controls roaming
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Set up the candidate configuration such that it has a BSSID specified.
+        WifiConfiguration candidate = generateWifiConfig(
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+        candidate.BSSID = CANDIDATE_BSSID; // config specified
+        ScanResult candidateScanResult = new ScanResult();
+        candidateScanResult.SSID = CANDIDATE_SSID;
+        candidateScanResult.BSSID = CANDIDATE_BSSID;
+        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
+
+        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(candidate);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+    }
+
+    /*
+     * Firmware does not support controlled roaming.
+     * Connect to a network which doesn't have a config specified BSSID.
+     *
+     * Expected behavior: WifiConnectivityManager calls
+     * WifiStateMachine.startConnectToNetwork() with the expected candidate network ID,
+     * and the BSSID value should be the candidate scan result specified.
+     */
+    @Test
+    public void useScanResultBssidToConnectWhenFirmwareRoamingOffAndConfigHasNoBssidSpecified() {
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+    }
+
+    /*
+     * Firmware does not support controlled roaming.
+     * Connect to a network which has a config specified BSSID.
+     *
+     * Expected behavior: WifiConnectivityManager calls
+     * WifiStateMachine.startConnectToNetwork() with the expected candidate network ID,
+     * and the BSSID value should be the config specified one.
+     */
+    @Test
+    public void useConfigSpecifiedBssidToConnectionWhenFirmwareRoamingOff() {
+        // Set up the candidate configuration such that it has a BSSID specified.
+        WifiConfiguration candidate = generateWifiConfig(
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+        candidate.BSSID = CANDIDATE_BSSID; // config specified
+        ScanResult candidateScanResult = new ScanResult();
+        candidateScanResult.SSID = CANDIDATE_SSID;
+        candidateScanResult.BSSID = CANDIDATE_BSSID;
+        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
+
+        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(candidate);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mWifiStateMachine).startConnectToNetwork(CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+    }
+
+    /**
+     * Firmware does not support controlled roaming.
+     * WiFi in connected state, framework triggers roaming.
+     *
+     * Expected behavior: WifiConnectivityManager invokes
+     * WifiStateMachine.startRoamToNetwork().
+     */
+    @Test
+    public void frameworkInitiatedRoaming() {
+        // Mock the currently connected network which has the same networkID and
+        // SSID as the one to be selected.
+        WifiConfiguration currentNetwork = generateWifiConfig(
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
+
+        // Set WiFi to connected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        verify(mWifiStateMachine).startRoamToNetwork(eq(CANDIDATE_NETWORK_ID),
+                mCandidateScanResultCaptor.capture());
+        assertEquals(mCandidateScanResultCaptor.getValue().BSSID, CANDIDATE_BSSID);
+    }
+
+    /**
+     * Firmware supports controlled roaming.
+     * WiFi in connected state, framework does not trigger roaming
+     * as it's handed off to the firmware.
+     *
+     * Expected behavior: WifiConnectivityManager doesn't invoke
+     * WifiStateMachine.startRoamToNetwork().
+     */
+    @Test
+    public void noFrameworkRoamingIfConnectedAndFirmwareRoamingSupported() {
+        // Mock the currently connected network which has the same networkID and
+        // SSID as the one to be selected.
+        WifiConfiguration currentNetwork = generateWifiConfig(
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
+
+        // Firmware controls roaming
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Set WiFi to connected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        verify(mWifiStateMachine, times(0)).startRoamToNetwork(anyInt(), anyObject());
+    }
+
+    /*
+     * Wifi in disconnected state. Drop the connection attempt if the recommended
+     * network configuration has a BSSID specified but the scan result BSSID doesn't
+     * match it.
+     *
+     * Expected behavior: WifiConnectivityManager doesn't invoke
+     * WifiStateMachine.startConnectToNetwork().
+     */
+    @Test
+    public void dropConnectAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
+        // Set up the candidate configuration such that it has a BSSID specified.
+        WifiConfiguration candidate = generateWifiConfig(
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+        candidate.BSSID = CANDIDATE_BSSID; // config specified
+        ScanResult candidateScanResult = new ScanResult();
+        candidateScanResult.SSID = CANDIDATE_SSID;
+        // Set up the scan result BSSID to be different from the config specified one.
+        candidateScanResult.BSSID = INVALID_SCAN_RESULT_BSSID;
+        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
+
+        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(candidate);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Set WiFi to disconnected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        verify(mWifiStateMachine, times(0)).startConnectToNetwork(
                 CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
     }
+
+    /*
+     * Wifi in connected state. Drop the roaming attempt if the recommended
+     * network configuration has a BSSID specified but the scan result BSSID doesn't
+     * match it.
+     *
+     * Expected behavior: WifiConnectivityManager doesn't invoke
+     * WifiStateMachine.startRoamToNetwork().
+     */
+    @Test
+    public void dropRoamingAttemptIfConfigSpecifiedBssidDifferentFromScanResultBssid() {
+        // Mock the currently connected network which has the same networkID and
+        // SSID as the one to be selected.
+        WifiConfiguration currentNetwork = generateWifiConfig(
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(currentNetwork);
+
+        // Set up the candidate configuration such that it has a BSSID specified.
+        WifiConfiguration candidate = generateWifiConfig(
+                0, CANDIDATE_NETWORK_ID, CANDIDATE_SSID, false, true, null, null);
+        candidate.BSSID = CANDIDATE_BSSID; // config specified
+        ScanResult candidateScanResult = new ScanResult();
+        candidateScanResult.SSID = CANDIDATE_SSID;
+        // Set up the scan result BSSID to be different from the config specified one.
+        candidateScanResult.BSSID = INVALID_SCAN_RESULT_BSSID;
+        candidate.getNetworkSelectionStatus().setCandidate(candidateScanResult);
+
+        when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
+                anyBoolean(), anyBoolean())).thenReturn(candidate);
+
+        // Set WiFi to connected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        verify(mWifiStateMachine, times(0)).startRoamToNetwork(anyInt(), anyObject());
+    }
+
+    /**
+     *  Dump local log buffer.
+     *
+     * Expected behavior: Logs dumped from WifiConnectivityManager.dump()
+     * contain the message we put in mLocalLog.
+     */
+    @Test
+    public void dumpLocalLog() {
+        final String localLogMessage = "This is a message from the test";
+        mLocalLog.log(localLogMessage);
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mWifiConnectivityManager.dump(new FileDescriptor(), pw, new String[]{});
+        assertTrue(sw.toString().contains(localLogMessage));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
index c187faf..c7b6180 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
@@ -27,12 +27,14 @@
 import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.*;
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.WorkSource;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
@@ -42,6 +44,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -49,6 +52,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
 import java.lang.reflect.Method;
+import java.util.List;
 
 /**
  * Test WifiController for changes in and out of ECM and SoftAP modes.
@@ -78,13 +82,18 @@
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
     }
 
-    MockLooper mLooper;
+    TestLooper mLooper;
     @Mock Context mContext;
     @Mock WifiServiceImpl mService;
     @Mock FrameworkFacade mFacade;
     @Mock WifiSettingsStore mSettingsStore;
     @Mock WifiStateMachine mWifiStateMachine;
     @Mock WifiLockManager mWifiLockManager;
+    @Mock ContentResolver mContentResolver;
+
+    ContentObserver mStayAwakeObserver;
+    ContentObserver mWifiIdleTimeObserver;
+    ContentObserver mWifiSleepPolicyObserver;
 
     WifiController mWifiController;
 
@@ -92,14 +101,23 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         initializeSettingsStore();
 
-        when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        ArgumentCaptor<ContentObserver> observerCaptor =
+                ArgumentCaptor.forClass(ContentObserver.class);
 
         mWifiController = new WifiController(mContext, mWifiStateMachine,
                 mSettingsStore, mWifiLockManager, mLooper.getLooper(), mFacade);
+        verify(mFacade, times(3)).registerContentObserver(eq(mContext), any(Uri.class), eq(false),
+                observerCaptor.capture());
+
+        List<ContentObserver> observers = observerCaptor.getAllValues();
+        mStayAwakeObserver = observers.get(0);
+        mWifiIdleTimeObserver = observers.get(1);
+        mWifiSleepPolicyObserver = observers.get(2);
 
         mWifiController.start();
         mLooper.dispatchAll();
@@ -267,7 +285,6 @@
         InOrder inOrder = inOrder(mWifiStateMachine);
         inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
         inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        inOrder.verify(mWifiStateMachine).setDriverStart(true);
         assertEquals("DeviceActiveState", getCurrentState().getName());
     }
 
@@ -295,7 +312,6 @@
         InOrder inOrder = inOrder(mWifiStateMachine);
         inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
         inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        inOrder.verify(mWifiStateMachine).setDriverStart(true);
         assertEquals("DeviceActiveState", getCurrentState().getName());
     }
 
@@ -329,7 +345,6 @@
         InOrder inOrder = inOrder(mWifiStateMachine);
         inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
         inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        inOrder.verify(mWifiStateMachine).setDriverStart(true);
         assertEquals("FullLockHeldState", getCurrentState().getName());
     }
 
@@ -404,7 +419,6 @@
         inOrder.verify(mWifiStateMachine).setSupplicantRunning(false);
         inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
         inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        inOrder.verify(mWifiStateMachine).setDriverStart(true);
         assertEquals("DeviceActiveState", getCurrentState().getName());
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
index faa2f71..33aab60 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -39,7 +38,6 @@
     private static final String TAG = "WifiCountryCodeTest";
     private String mDefaultCountryCode = "US";
     private String mTelephonyCountryCode = "JP";
-    private String mPersistCountryCode = "";
     private boolean mRevertCountryCodeOnCellularLoss = true;
     @Mock WifiNative mWifiNative;
     private WifiCountryCode mWifiCountryCode;
@@ -56,7 +54,6 @@
         mWifiCountryCode = new WifiCountryCode(
                 mWifiNative,
                 mDefaultCountryCode,
-                mPersistCountryCode,
                 mRevertCountryCodeOnCellularLoss);
     }
 
@@ -80,7 +77,7 @@
      */
     @Test
     public void useTelephonyCountryCode() throws Exception {
-        mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
+        mWifiCountryCode.setCountryCode(mTelephonyCountryCode);
         assertEquals(null, mWifiCountryCode.getCountryCodeSentToDriver());
         // Supplicant started.
         mWifiCountryCode.setReadyForChange(true);
@@ -100,7 +97,7 @@
         mWifiCountryCode.setReadyForChange(true);
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         // Telephony country code arrives.
-        mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
+        mWifiCountryCode.setCountryCode(mTelephonyCountryCode);
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
         verify(mWifiNative, times(2)).setCountryCode(anyString());
@@ -118,7 +115,7 @@
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
         // Telephony country code arrives.
-        mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
+        mWifiCountryCode.setCountryCode(mTelephonyCountryCode);
         // Telephony coutry code won't be applied at this time.
         assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         mWifiCountryCode.setReadyForChange(true);
@@ -133,7 +130,7 @@
      */
     @Test
     public void resetCountryCodeWhenSIMCardRemoved() throws Exception {
-        mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
+        mWifiCountryCode.setCountryCode(mTelephonyCountryCode);
         // Supplicant started.
         mWifiCountryCode.setReadyForChange(true);
         // Wifi get L2 connected.
@@ -155,7 +152,7 @@
      */
     @Test
     public void resetCountryCodeWhenAirplaneModeEnabled() throws Exception {
-        mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
+        mWifiCountryCode.setCountryCode(mTelephonyCountryCode);
         // Supplicant started.
         mWifiCountryCode.setReadyForChange(true);
         // Wifi get L2 connected.
@@ -172,22 +169,19 @@
     }
 
     /**
-     * Test if we will set the persistent country code if it is not empty.
+     * Test if we can reset to the default country code when phone is out of service.
+     * Telephony service calls |setCountryCode| with an empty string when phone is out of service.
+     * In this case we should fall back to the default country code.
      * @throws Exception
      */
     @Test
-    public void usePersistentCountryCode() throws Exception {
-        String persistentCountryCode = "CH";
-        mWifiCountryCode = new WifiCountryCode(
-                mWifiNative,
-                mDefaultCountryCode,
-                persistentCountryCode,
-                mRevertCountryCodeOnCellularLoss);
-        // Supplicant started.
-        mWifiCountryCode.setReadyForChange(true);
-        // Wifi get L2 connected.
-        mWifiCountryCode.setReadyForChange(false);
-        verify(mWifiNative).setCountryCode(anyString());
-        assertEquals(persistentCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
+    public void resetCountryCodeWhenOutOfService() throws Exception {
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCode());
+        mWifiCountryCode.setCountryCode(mTelephonyCountryCode);
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCode());
+        // Out of service.
+        mWifiCountryCode.setCountryCode("");
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCode());
     }
+
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
similarity index 68%
rename from tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
rename to tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
index d915ff3..f4b710e 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
@@ -16,15 +16,12 @@
 
 package com.android.server.wifi;
 
-import android.content.Context;
-import android.test.suitebuilder.annotation.SmallTest;
-import com.android.internal.R;
-import android.util.LocalLog;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.contains;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyObject;
 import static org.mockito.Mockito.eq;
@@ -33,36 +30,45 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.R;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
+import java.io.ByteArrayInputStream;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.regex.Pattern;
 
 /**
- * Unit tests for {@link com.android.server.wifi.WifiLogger}.
+ * Unit tests for {@link WifiDiagnostics}.
  */
 @SmallTest
-public class WifiLoggerTest {
-    public static final String TAG = "WifiLoggerTest";
-
+public class WifiDiagnosticsTest {
     @Mock WifiStateMachine mWsm;
     @Mock WifiNative mWifiNative;
     @Mock BuildProperties mBuildProperties;
     @Mock Context mContext;
-    WifiLogger mWifiLogger;
+    @Mock WifiInjector mWifiInjector;
+    @Spy FakeWifiLog mLog;
+    @Mock LastMileLogger mLastMileLogger;
+    @Mock Runtime mJavaRuntime;
+    @Mock Process mExternalProcess;
+    WifiDiagnostics mWifiDiagnostics;
 
     private static final String FAKE_RING_BUFFER_NAME = "fake-ring-buffer";
     private static final int SMALL_RING_BUFFER_SIZE_KB = 32;
     private static final int LARGE_RING_BUFFER_SIZE_KB = 1024;
     private static final int BYTES_PER_KBYTE = 1024;
-    private LocalLog mWifiNativeLocalLog;
+    private static final long FAKE_CONNECTION_ID = 1;
 
     private WifiNative.RingBufferStatus mFakeRbs;
     /**
@@ -71,7 +77,7 @@
      * dimension is the byte index within that record.
      */
     private final byte[][] getLoggerRingBufferData() throws Exception {
-        return mWifiLogger.getBugReports().get(0).ringBuffers.get(FAKE_RING_BUFFER_NAME);
+        return mWifiDiagnostics.getBugReports().get(0).ringBuffers.get(FAKE_RING_BUFFER_NAME);
     }
 
     /**
@@ -86,14 +92,15 @@
         WifiNative.RingBufferStatus[] ringBufferStatuses = new WifiNative.RingBufferStatus[] {
                 mFakeRbs
         };
-        mWifiNativeLocalLog = new LocalLog(8192);
 
         when(mWifiNative.getRingBufferStatus()).thenReturn(ringBufferStatuses);
         when(mWifiNative.readKernelLog()).thenReturn("");
-        when(mWifiNative.getLocalLog()).thenReturn(mWifiNativeLocalLog);
         when(mBuildProperties.isEngBuild()).thenReturn(false);
         when(mBuildProperties.isUserdebugBuild()).thenReturn(false);
         when(mBuildProperties.isUserBuild()).thenReturn(true);
+        when(mExternalProcess.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[0]));
+        when(mExternalProcess.getErrorStream()).thenReturn(new ByteArrayInputStream(new byte[0]));
+        when(mJavaRuntime.exec(anyString())).thenReturn(mExternalProcess);
 
         MockResources resources = new MockResources();
         resources.setInteger(R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb,
@@ -101,8 +108,11 @@
         resources.setInteger(R.integer.config_wifi_logger_ring_buffer_verbose_size_limit_kb,
                 LARGE_RING_BUFFER_SIZE_KB);
         when(mContext.getResources()).thenReturn(resources);
+        when(mWifiInjector.makeLog(anyString())).thenReturn(mLog);
+        when(mWifiInjector.getJavaRuntime()).thenReturn(mJavaRuntime);
 
-        mWifiLogger = new WifiLogger(mContext, mWsm, mWifiNative, mBuildProperties);
+        mWifiDiagnostics = new WifiDiagnostics(
+                mContext, mWifiInjector, mWsm, mWifiNative, mBuildProperties, mLastMileLogger);
         mWifiNative.enableVerboseLogging(0);
     }
 
@@ -110,7 +120,7 @@
     @Test
     public void startLoggingRegistersLogEventHandler() throws Exception {
         final boolean verbosityToggle = false;  // even default mode wants log events from HAL
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
     }
 
@@ -124,12 +134,12 @@
         final boolean verbosityToggle = false;  // even default mode wants log events from HAL
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(false);
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
         reset(mWifiNative);
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
     }
 
@@ -140,11 +150,11 @@
         final boolean verbosityToggle = false;  // even default mode wants log events from HAL
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
         reset(mWifiNative);
 
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative, never()).setLoggingEventHandler(anyObject());
     }
 
@@ -158,25 +168,34 @@
     @Test
     public void startLoggingStopsAndRestartsRingBufferLogging() throws Exception {
         final boolean verbosityToggle = false;
-        mWifiLogger.startLogging(verbosityToggle);
+        setBuildPropertiesToEnableRingBuffers();
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative).startLoggingRingBuffer(
-                eq(WifiLogger.VERBOSE_NO_LOG), anyInt(), anyInt(), anyInt(),
+                eq(WifiDiagnostics.VERBOSE_NO_LOG), anyInt(), anyInt(), anyInt(),
                 eq(FAKE_RING_BUFFER_NAME));
         verify(mWifiNative).startLoggingRingBuffer(
-                eq(WifiLogger.VERBOSE_NORMAL_LOG), anyInt(), anyInt(), anyInt(),
+                eq(WifiDiagnostics.VERBOSE_NORMAL_LOG), anyInt(), anyInt(), anyInt(),
                 eq(FAKE_RING_BUFFER_NAME));
     }
 
+    @Test
+    public void startLoggingDoesNotStartRingBuffersOnUserBuilds() throws Exception {
+        final boolean verbosityToggle = true;
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        verify(mWifiNative, never()).startLoggingRingBuffer(
+                anyInt(), anyInt(), anyInt(), anyInt(), anyString());
+    }
+
     /** Verifies that, if a log handler was registered, then stopLogging() resets it. */
     @Test
     public void stopLoggingResetsLogHandlerIfHandlerWasRegistered() throws Exception {
         final boolean verbosityToggle = false;  // even default mode wants log events from HAL
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         reset(mWifiNative);
 
-        mWifiLogger.stopLogging();
+        mWifiDiagnostics.stopLogging();
         verify(mWifiNative).resetLogHandler();
     }
 
@@ -184,7 +203,7 @@
     @Test
     public void stopLoggingOnlyResetsLogHandlerIfHandlerWasRegistered() throws Exception {
         final boolean verbosityToggle = false;  // even default mode wants log events from HAL
-        mWifiLogger.stopLogging();
+        mWifiDiagnostics.stopLogging();
         verify(mWifiNative, never()).resetLogHandler();
     }
 
@@ -194,15 +213,15 @@
         final boolean verbosityToggle = false;  // even default mode wants log events from HAL
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         reset(mWifiNative);
 
         when(mWifiNative.resetLogHandler()).thenReturn(true);
-        mWifiLogger.stopLogging();
+        mWifiDiagnostics.stopLogging();
         verify(mWifiNative).resetLogHandler();
         reset(mWifiNative);
 
-        mWifiLogger.stopLogging();
+        mWifiDiagnostics.stopLogging();
         verify(mWifiNative, never()).resetLogHandler();
     }
 
@@ -212,11 +231,12 @@
     @Test
     public void canCaptureAndStoreRingBufferData() throws Exception {
         final boolean verbosityToggle = false;
-        mWifiLogger.startLogging(verbosityToggle);
+        setBuildPropertiesToEnableRingBuffers();
+        mWifiDiagnostics.startLogging(verbosityToggle);
 
         final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
-        mWifiLogger.onRingBufferData(mFakeRbs, data);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
 
         byte[][] ringBufferData = getLoggerRingBufferData();
         assertEquals(1, ringBufferData.length);
@@ -229,13 +249,14 @@
     @Test
     public void loggerDiscardsExtraneousData() throws Exception {
         final boolean verbosityToggle = false;
-        mWifiLogger.startLogging(verbosityToggle);
+        setBuildPropertiesToEnableRingBuffers();
+        mWifiDiagnostics.startLogging(verbosityToggle);
 
         final byte[] data1 = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         final byte[] data2 = {1, 2, 3};
-        mWifiLogger.onRingBufferData(mFakeRbs, data1);
-        mWifiLogger.onRingBufferData(mFakeRbs, data2);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data1);
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data2);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
 
         byte[][] ringBufferData = getLoggerRingBufferData();
         assertEquals(1, ringBufferData.length);
@@ -249,7 +270,7 @@
     @Test
     public void startLoggingStartsPacketFateWithoutVerboseMode() {
         final boolean verbosityToggle = false;
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative).startPktFateMonitoring();
     }
 
@@ -260,19 +281,29 @@
     @Test
     public void startLoggingStartsPacketFateInVerboseMode() {
         final boolean verbosityToggle = true;
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         verify(mWifiNative).startPktFateMonitoring();
     }
 
+    // Verifies that startLogging() reports failure of startPktFateMonitoring().
+    @Test
+    public void startLoggingReportsFailureOfStartPktFateMonitoring() {
+        final boolean verbosityToggle = true;
+        when(mWifiNative.startPktFateMonitoring()).thenReturn(false);
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        verify(mLog).wC(contains("Failed"));
+    }
+
     /**
-     * Verifies that, when verbose mode is not enabled, reportConnectionFailure() still
-     * fetches packet fates.
+     * Verifies that, when verbose mode is not enabled,
+     * reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED) still fetches packet fates.
      */
     @Test
     public void reportConnectionFailureIsIgnoredWithoutVerboseMode() {
         final boolean verbosityToggle = false;
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.reportConnectionFailure();
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(anyObject());
         verify(mWifiNative).getRxPktFates(anyObject());
     }
@@ -283,12 +314,43 @@
     @Test
     public void reportConnectionFailureFetchesFatesInVerboseMode() {
         final boolean verbosityToggle = true;
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.reportConnectionFailure();
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(anyObject());
         verify(mWifiNative).getRxPktFates(anyObject());
     }
 
+    @Test
+    public void reportConnectionEventPropagatesStartToLastMileLogger() {
+        final boolean verbosityToggle = false;
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+        verify(mLastMileLogger).reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_STARTED);
+    }
+
+    @Test
+    public void reportConnectionEventPropagatesSuccessToLastMileLogger() {
+        final boolean verbosityToggle = false;
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+        verify(mLastMileLogger).reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
+    }
+
+    @Test
+    public void reportConnectionEventPropagatesFailureToLastMileLogger() {
+        final boolean verbosityToggle = false;
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
+        verify(mLastMileLogger).reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
+    }
+
     /**
      * Verifies that we try to fetch TX fates, even if fetching RX fates failed.
      */
@@ -296,8 +358,9 @@
     public void loggerFetchesTxFatesEvenIfFetchingRxFatesFails() {
         final boolean verbosityToggle = true;
         when(mWifiNative.getRxPktFates(anyObject())).thenReturn(false);
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.reportConnectionFailure();
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(anyObject());
         verify(mWifiNative).getRxPktFates(anyObject());
     }
@@ -309,8 +372,9 @@
     public void loggerFetchesRxFatesEvenIfFetchingTxFatesFails() {
         final boolean verbosityToggle = true;
         when(mWifiNative.getTxPktFates(anyObject())).thenReturn(false);
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.reportConnectionFailure();
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(anyObject());
         verify(mWifiNative).getRxPktFates(anyObject());
     }
@@ -321,8 +385,8 @@
         final boolean verbosityToggle = false;
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
         verify(mWifiNative).getTxPktFates(anyObject());
         verify(mWifiNative).getRxPktFates(anyObject());
     }
@@ -335,7 +399,7 @@
     public void dumpSucceedsWhenNoFatesHaveNotBeenFetched() {
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
 
         String fateDumpString = sw.toString();
         assertTrue(fateDumpString.contains("Last failed"));
@@ -351,14 +415,15 @@
     @Test
     public void dumpSucceedsWhenFatesHaveBeenFetchedButAreEmpty() {
         final boolean verbosityToggle = true;
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.reportConnectionFailure();
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(anyObject());
         verify(mWifiNative).getRxPktFates(anyObject());
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
 
         String fateDumpString = sw.toString();
         assertTrue(fateDumpString.contains("Last failed"));
@@ -368,7 +433,7 @@
     }
 
     private String getDumpString(boolean verbose) {
-        mWifiLogger.startLogging(verbose);
+        mWifiDiagnostics.startLogging(verbose);
         mWifiNative.enableVerboseLogging(verbose ? 1 : 0);
         when(mWifiNative.getTxPktFates(anyObject())).then(new AnswerWithArguments() {
             public boolean answer(WifiNative.TxFateReport[] fates) {
@@ -396,11 +461,12 @@
                 return true;
             }
         });
-        mWifiLogger.reportConnectionFailure();
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
         return sw.toString();
     }
 
@@ -502,7 +568,7 @@
     @Test
     public void dumpOmitsFatesIfVerboseIsDisabledAfterFetch() {
         final boolean verbosityToggle = true;
-        mWifiLogger.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(verbosityToggle);
         when(mWifiNative.getTxPktFates(anyObject())).then(new AnswerWithArguments() {
             public boolean answer(WifiNative.TxFateReport[] fates) {
                 fates[0] = new WifiNative.TxFateReport(
@@ -521,31 +587,33 @@
                 return true;
             }
         });
-        mWifiLogger.reportConnectionFailure();
+        mWifiDiagnostics.reportConnectionEvent(
+                FAKE_CONNECTION_ID, WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(anyObject());
         verify(mWifiNative).getRxPktFates(anyObject());
 
         final boolean newVerbosityToggle = false;
-        mWifiLogger.startLogging(newVerbosityToggle);
+        mWifiDiagnostics.startLogging(newVerbosityToggle);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
 
         String fateDumpString = sw.toString();
         assertFalse(fateDumpString.contains("VERBOSE PACKET FATE DUMP"));
         assertFalse(fateDumpString.contains("Frame bytes"));
     }
 
-    /** Verifies that the default size of our ring buffers is small. */
     @Test
-    public void ringBufferSizeIsSmallByDefault() throws Exception {
-        final boolean verbosityToggle = false;
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.onRingBufferData(
-                mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
-        assertEquals(0, getLoggerRingBufferData().length);
+    public void dumpSucceedsEvenIfRingBuffersAreDisabled() {
+        final boolean verbosityToggle = true;
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        verify(mWifiNative, never()).startLoggingRingBuffer(
+                anyInt(), anyInt(), anyInt(), anyInt(), anyString());
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
     }
 
     /** Verifies that we use small ring buffers by default, on userdebug builds. */
@@ -555,10 +623,10 @@
         when(mBuildProperties.isUserdebugBuild()).thenReturn(true);
         when(mBuildProperties.isEngBuild()).thenReturn(false);
         when(mBuildProperties.isUserBuild()).thenReturn(false);
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.onRingBufferData(
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
     }
 
@@ -569,10 +637,10 @@
         when(mBuildProperties.isEngBuild()).thenReturn(true);
         when(mBuildProperties.isUserdebugBuild()).thenReturn(false);
         when(mBuildProperties.isUserBuild()).thenReturn(false);
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.onRingBufferData(
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
     }
 
@@ -580,47 +648,53 @@
     @Test
     public void ringBufferSizeIsLargeInVerboseMode() throws Exception {
         final boolean verbosityToggle = true;
-        mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.onRingBufferData(
+        setBuildPropertiesToEnableRingBuffers();
+
+        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         assertEquals(1, getLoggerRingBufferData().length);
     }
 
     /** Verifies that we use large ring buffers when switched from normal to verbose mode. */
     @Test
     public void startLoggingGrowsRingBuffersIfNeeded() throws Exception {
-        mWifiLogger.startLogging(false  /* verbose disabled */);
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.onRingBufferData(
+        setBuildPropertiesToEnableRingBuffers();
+
+        mWifiDiagnostics.startLogging(false  /* verbose disabled */);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         assertEquals(1, getLoggerRingBufferData().length);
     }
 
     /** Verifies that we use small ring buffers when switched from verbose to normal mode. */
     @Test
     public void startLoggingShrinksRingBuffersIfNeeded() throws Exception {
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.onRingBufferData(
+        setBuildPropertiesToEnableRingBuffers();
+
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
 
         // Existing data is nuked (too large).
-        mWifiLogger.startLogging(false  /* verbose disabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(false  /* verbose disabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
 
         // New data must obey limit as well.
-        mWifiLogger.onRingBufferData(
+        mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
     }
 
     /** Verifies that we skip the firmware and driver dumps if verbose is not enabled. */
     @Test
     public void captureBugReportSkipsFirmwareAndDriverDumpsByDefault() {
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative, never()).getFwMemoryDump();
         verify(mWifiNative, never()).getDriverStateDump();
     }
@@ -628,8 +702,8 @@
     /** Verifies that we capture the firmware and driver dumps if verbose is enabled. */
     @Test
     public void captureBugReportTakesFirmwareAndDriverDumpsInVerboseMode() {
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
         verify(mWifiNative).getDriverStateDump();
     }
@@ -639,27 +713,27 @@
     public void dumpIncludesDriverStateDumpIfAvailable() {
         when(mWifiNative.getDriverStateDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getDriverStateDump();
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
-        assertTrue(sw.toString().contains(WifiLogger.DRIVER_DUMP_SECTION_HEADER));
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{});
+        assertTrue(sw.toString().contains(WifiDiagnostics.DRIVER_DUMP_SECTION_HEADER));
     }
 
     /** Verifies that the dump skips driver state, if driver state was not provided by HAL. */
     @Test
     public void dumpOmitsDriverStateDumpIfUnavailable() {
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getDriverStateDump();
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
-        assertFalse(sw.toString().contains(WifiLogger.DRIVER_DUMP_SECTION_HEADER));
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{});
+        assertFalse(sw.toString().contains(WifiDiagnostics.DRIVER_DUMP_SECTION_HEADER));
     }
 
     /** Verifies that the dump omits driver state, if verbose was disabled after capture. */
@@ -667,16 +741,16 @@
     public void dumpOmitsDriverStateDumpIfVerboseDisabledAfterCapture() {
         when(mWifiNative.getDriverStateDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getDriverStateDump();
 
-        mWifiLogger.startLogging(false  /* verbose no longer enabled */);
+        mWifiDiagnostics.startLogging(false  /* verbose no longer enabled */);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
-        assertFalse(sw.toString().contains(WifiLogger.DRIVER_DUMP_SECTION_HEADER));
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{});
+        assertFalse(sw.toString().contains(WifiDiagnostics.DRIVER_DUMP_SECTION_HEADER));
     }
 
     /** Verifies that the dump includes firmware dump, if firmware dump was provided by HAL. */
@@ -684,27 +758,27 @@
     public void dumpIncludesFirmwareMemoryDumpIfAvailable() {
         when(mWifiNative.getFwMemoryDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
-        assertTrue(sw.toString().contains(WifiLogger.FIRMWARE_DUMP_SECTION_HEADER));
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{});
+        assertTrue(sw.toString().contains(WifiDiagnostics.FIRMWARE_DUMP_SECTION_HEADER));
     }
 
     /** Verifies that the dump skips firmware memory, if firmware memory was not provided by HAL. */
     @Test
     public void dumpOmitsFirmwareMemoryDumpIfUnavailable() {
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
-        assertFalse(sw.toString().contains(WifiLogger.FIRMWARE_DUMP_SECTION_HEADER));
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{});
+        assertFalse(sw.toString().contains(WifiDiagnostics.FIRMWARE_DUMP_SECTION_HEADER));
     }
 
     /** Verifies that the dump omits firmware memory, if verbose was disabled after capture. */
@@ -712,28 +786,28 @@
     public void dumpOmitsFirmwareMemoryDumpIfVerboseDisabledAfterCapture() {
         when(mWifiNative.getFwMemoryDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
+        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
 
-        mWifiLogger.startLogging(false  /* verbose no longer enabled */);
+        mWifiDiagnostics.startLogging(false  /* verbose no longer enabled */);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
-        assertFalse(sw.toString().contains(WifiLogger.FIRMWARE_DUMP_SECTION_HEADER));
+        mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{});
+        assertFalse(sw.toString().contains(WifiDiagnostics.FIRMWARE_DUMP_SECTION_HEADER));
     }
 
-    /** Verifies that the dump() includes contents of WifiNative's LocalLog. */
     @Test
-    public void dumpIncludesContentOfWifiNativeLocalLog() {
-        final String wifiNativeLogMessage = "This is a message";
-        mWifiNativeLocalLog.log(wifiNativeLogMessage);
+    public void dumpRequestsLastMileLoggerDump() {
+        mWifiDiagnostics.dump(
+                new FileDescriptor(), new PrintWriter(new StringWriter()), new String[]{});
+        verify(mLastMileLogger).dump(anyObject());
+    }
 
-        mWifiLogger.startLogging(false  /* verbose disabled */);
-        StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
-        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
-        assertTrue(sw.toString().contains(wifiNativeLogMessage));
+    private void setBuildPropertiesToEnableRingBuffers() {
+        when(mBuildProperties.isEngBuild()).thenReturn(false);
+        when(mBuildProperties.isUserdebugBuild()).thenReturn(true);
+        when(mBuildProperties.isUserBuild()).thenReturn(false);
     }
 }
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 08163e7..0ecd53d 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
@@ -41,7 +41,7 @@
 public class WifiLastResortWatchdogTest {
     WifiLastResortWatchdog mLastResortWatchdog;
     @Mock WifiMetrics mWifiMetrics;
-    @Mock WifiController mWifiController;
+    @Mock SelfRecovery mSelfRecovery;
     private String[] mSsids = {"\"test1\"", "\"test2\"", "\"test3\"", "\"test4\""};
     private String[] mBssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "de:ad:ba:b1:e5:55",
             "c0:ff:ee:ee:e3:ee"};
@@ -55,8 +55,7 @@
     @Before
     public void setUp() throws Exception {
         initMocks(this);
-        mLastResortWatchdog = new WifiLastResortWatchdog(mWifiMetrics);
-        mLastResortWatchdog.setWifiController(mWifiController);
+        mLastResortWatchdog = new WifiLastResortWatchdog(mSelfRecovery, mWifiMetrics);
     }
 
     private List<Pair<ScanDetail, WifiConfiguration>> createFilteredQnsCandidates(String[] ssids,
@@ -1280,8 +1279,8 @@
                     ssids[ssids.length - 1], bssids[ssids.length - 1],
                     WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
         assertEquals(true, watchdogTriggered);
-        verify(mWifiController).sendMessage(WifiController.CMD_RESTART_WIFI);
-        reset(mWifiController);
+        verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_LAST_RESORT_WATCHDOG));
+        reset(mSelfRecovery);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
index 1bbdda9..d5d79b8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
@@ -18,7 +18,6 @@
 
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.*;
-import static org.mockito.Matchers.*;
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 15a5327..1c09cac 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -21,11 +21,19 @@
 
 import android.net.NetworkAgent;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
 
+
 import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -33,8 +41,11 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -47,14 +58,16 @@
 
     WifiMetrics mWifiMetrics;
     WifiMetricsProto.WifiLog mDeserializedWifiMetrics;
+    TestLooper mTestLooper;
     @Mock Clock mClock;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mDeserializedWifiMetrics = null;
-        when(mClock.elapsedRealtime()).thenReturn((long) 0);
-        mWifiMetrics = new WifiMetrics(mClock);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 0);
+        mTestLooper = new TestLooper();
+        mWifiMetrics = new WifiMetrics(mClock, mTestLooper.getLooper());
     }
 
     /**
@@ -92,7 +105,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);
@@ -116,7 +129,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, WifiMetrics.CLEAN_DUMP_ARG};
         mWifiMetrics.dump(null, writer, args);
@@ -161,16 +174,18 @@
         assertDeserializedMetricsCorrect();
     }
 
-    private static final int NUM_SAVED_NETWORKS = 1;
     private static final int NUM_OPEN_NETWORKS = 2;
     private static final int NUM_PERSONAL_NETWORKS = 3;
     private static final int NUM_ENTERPRISE_NETWORKS = 5;
-    private static final int NUM_HIDDEN_NETWORKS = 3;
-    private static final int NUM_PASSPOINT_NETWORKS = 4;
+    private static final int NUM_SAVED_NETWORKS = NUM_OPEN_NETWORKS + NUM_PERSONAL_NETWORKS
+            + NUM_ENTERPRISE_NETWORKS;
+    private static final int NUM_HIDDEN_NETWORKS = NUM_OPEN_NETWORKS;
+    private static final int NUM_PASSPOINT_NETWORKS = NUM_ENTERPRISE_NETWORKS;
+    private static final int NUM_NETWORKS_ADDED_BY_USER = 1;
+    private static final int NUM_NETWORKS_ADDED_BY_APPS = NUM_SAVED_NETWORKS
+            - NUM_NETWORKS_ADDED_BY_USER;
     private static final boolean TEST_VAL_IS_LOCATION_ENABLED = true;
     private static final boolean IS_SCANNING_ALWAYS_ENABLED = true;
-    private static final int NUM_NEWTORKS_ADDED_BY_USER = 13;
-    private static final int NUM_NEWTORKS_ADDED_BY_APPS = 17;
     private static final int NUM_EMPTY_SCAN_RESULTS = 19;
     private static final int NUM_NON_EMPTY_SCAN_RESULTS = 23;
     private static final int NUM_SCAN_UNKNOWN = 1;
@@ -212,6 +227,14 @@
     private static final int NUM_WIFI_SCORES_TO_INCREMENT = 20;
     private static final int WIFI_SCORE_RANGE_MAX = 60;
     private static final int NUM_OUT_OF_BOUND_ENTRIES = 10;
+    private static final int MAX_NUM_SOFTAP_RETURN_CODES = 3;
+    private static final int NUM_SOFTAP_START_SUCCESS = 3;
+    private static final int NUM_SOFTAP_FAILED_GENERAL_ERROR = 2;
+    private static final int NUM_SOFTAP_FAILED_NO_CHANNEL = 1;
+    private static final int NUM_HAL_CRASHES = 11;
+    private static final int NUM_WIFICOND_CRASHES = 12;
+    private static final int NUM_WIFI_ON_FAILURE_DUE_TO_HAL = 13;
+    private static final int NUM_WIFI_ON_FAILURE_DUE_TO_WIFICOND = 14;
 
     private ScanDetail buildMockScanDetail(boolean hidden, NetworkDetail.HSRelease hSRelease,
             String capabilities) {
@@ -242,18 +265,27 @@
         return mockScanDetails;
     }
 
+    private List<WifiConfiguration> buildSavedNetworkList() {
+        List<WifiConfiguration> testSavedNetworks = new ArrayList<WifiConfiguration>();
+        for (int i = 0; i < NUM_OPEN_NETWORKS; i++) {
+            testSavedNetworks.add(WifiConfigurationTestUtil.createOpenHiddenNetwork());
+        }
+        for (int i = 0; i < NUM_PERSONAL_NETWORKS; i++) {
+            testSavedNetworks.add(WifiConfigurationTestUtil.createPskNetwork());
+        }
+        for (int i = 0; i < NUM_ENTERPRISE_NETWORKS; i++) {
+            // Passpoint networks are counted in both Passpoint and Enterprise counters
+            testSavedNetworks.add(WifiConfigurationTestUtil.createPasspointNetwork());
+        }
+        testSavedNetworks.get(0).selfAdded = true;
+        return testSavedNetworks;
+    }
+
     /**
      * Set simple metrics, increment others
      */
     public void setAndIncrementMetrics() throws Exception {
-        mWifiMetrics.setNumSavedNetworks(NUM_SAVED_NETWORKS);
-        mWifiMetrics.setNumOpenNetworks(NUM_OPEN_NETWORKS);
-        mWifiMetrics.setNumPersonalNetworks(NUM_PERSONAL_NETWORKS);
-        mWifiMetrics.setNumEnterpriseNetworks(NUM_ENTERPRISE_NETWORKS);
-        mWifiMetrics.setNumHiddenNetworks(NUM_HIDDEN_NETWORKS);
-        mWifiMetrics.setNumPasspointNetworks(NUM_PASSPOINT_NETWORKS);
-        mWifiMetrics.setNumNetworksAddedByUser(NUM_NEWTORKS_ADDED_BY_USER);
-        mWifiMetrics.setNumNetworksAddedByApps(NUM_NEWTORKS_ADDED_BY_APPS);
+        mWifiMetrics.updateSavedNetworks(buildSavedNetworkList());
         mWifiMetrics.setIsLocationEnabled(TEST_VAL_IS_LOCATION_ENABLED);
         mWifiMetrics.setIsScanningAlwaysEnabled(IS_SCANNING_ALWAYS_ENABLED);
 
@@ -363,6 +395,30 @@
         for (int i = 1; i < NUM_OUT_OF_BOUND_ENTRIES; i++) {
             mWifiMetrics.incrementWifiScoreCount(WIFI_SCORE_RANGE_MAX + i);
         }
+
+        // increment soft ap start return codes
+        for (int i = 0; i < NUM_SOFTAP_START_SUCCESS; i++) {
+            mWifiMetrics.incrementSoftApStartResult(true, 0);
+        }
+        for (int i = 0; i < NUM_SOFTAP_FAILED_GENERAL_ERROR; i++) {
+            mWifiMetrics.incrementSoftApStartResult(false, WifiManager.SAP_START_FAILURE_GENERAL);
+        }
+        for (int i = 0; i < NUM_SOFTAP_FAILED_NO_CHANNEL; i++) {
+            mWifiMetrics.incrementSoftApStartResult(false,
+                    WifiManager.SAP_START_FAILURE_NO_CHANNEL);
+        }
+        for (int i = 0; i < NUM_HAL_CRASHES; i++) {
+            mWifiMetrics.incrementNumHalCrashes();
+        }
+        for (int i = 0; i < NUM_WIFICOND_CRASHES; i++) {
+            mWifiMetrics.incrementNumWificondCrashes();
+        }
+        for (int i = 0; i < NUM_WIFI_ON_FAILURE_DUE_TO_HAL; i++) {
+            mWifiMetrics.incrementNumWifiOnFailureDueToHal();
+        }
+        for (int i = 0; i < NUM_WIFI_ON_FAILURE_DUE_TO_WIFICOND; i++) {
+            mWifiMetrics.incrementNumWifiOnFailureDueToWificond();
+        }
     }
 
     /**
@@ -379,13 +435,13 @@
                         + "== NUM_ENTERPRISE_NETWORKS",
                 mDeserializedWifiMetrics.numEnterpriseNetworks, NUM_ENTERPRISE_NETWORKS);
         assertEquals("mDeserializedWifiMetrics.numNetworksAddedByUser "
-                        + "== NUM_NEWTORKS_ADDED_BY_USER",
-                mDeserializedWifiMetrics.numNetworksAddedByUser, NUM_NEWTORKS_ADDED_BY_USER);
+                        + "== NUM_NETWORKS_ADDED_BY_USER",
+                mDeserializedWifiMetrics.numNetworksAddedByUser, NUM_NETWORKS_ADDED_BY_USER);
         assertEquals(NUM_HIDDEN_NETWORKS, mDeserializedWifiMetrics.numHiddenNetworks);
         assertEquals(NUM_PASSPOINT_NETWORKS, mDeserializedWifiMetrics.numPasspointNetworks);
         assertEquals("mDeserializedWifiMetrics.numNetworksAddedByApps "
-                        + "== NUM_NEWTORKS_ADDED_BY_APPS",
-                mDeserializedWifiMetrics.numNetworksAddedByApps, NUM_NEWTORKS_ADDED_BY_APPS);
+                        + "== NUM_NETWORKS_ADDED_BY_APPS",
+                mDeserializedWifiMetrics.numNetworksAddedByApps, NUM_NETWORKS_ADDED_BY_APPS);
         assertEquals("mDeserializedWifiMetrics.isLocationEnabled == TEST_VAL_IS_LOCATION_ENABLED",
                 mDeserializedWifiMetrics.isLocationEnabled, TEST_VAL_IS_LOCATION_ENABLED);
         assertEquals("mDeserializedWifiMetrics.isScanningAlwaysEnabled "
@@ -487,6 +543,24 @@
         sb_wifi_limits.append("Wifi Score limit is " +  NetworkAgent.WIFI_BASE_SCORE
                 + ">= " + WIFI_SCORE_RANGE_MAX);
         assertTrue(sb_wifi_limits.toString(), NetworkAgent.WIFI_BASE_SCORE <= WIFI_SCORE_RANGE_MAX);
+        assertEquals(MAX_NUM_SOFTAP_RETURN_CODES, mDeserializedWifiMetrics.softApReturnCode.length);
+        assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_STARTED_SUCCESSFULLY,
+                     mDeserializedWifiMetrics.softApReturnCode[0].startResult);
+        assertEquals(NUM_SOFTAP_START_SUCCESS, mDeserializedWifiMetrics.softApReturnCode[0].count);
+        assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_GENERAL_ERROR,
+                     mDeserializedWifiMetrics.softApReturnCode[1].startResult);
+        assertEquals(NUM_SOFTAP_FAILED_GENERAL_ERROR,
+                     mDeserializedWifiMetrics.softApReturnCode[1].count);
+        assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_NO_CHANNEL,
+                     mDeserializedWifiMetrics.softApReturnCode[2].startResult);
+        assertEquals(NUM_SOFTAP_FAILED_NO_CHANNEL,
+                     mDeserializedWifiMetrics.softApReturnCode[2].count);
+        assertEquals(NUM_HAL_CRASHES, mDeserializedWifiMetrics.numHalCrashes);
+        assertEquals(NUM_WIFICOND_CRASHES, mDeserializedWifiMetrics.numWificondCrashes);
+        assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_HAL,
+                mDeserializedWifiMetrics.numWifiOnFailureDueToHal);
+        assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_WIFICOND,
+                mDeserializedWifiMetrics.numWifiOnFailureDueToWificond);
     }
 
     /**
@@ -659,6 +733,362 @@
                 2, mDeserializedWifiMetrics.connectionEvent.length);
     }
 
+    private static final int NUM_REPEATED_DELTAS = 7;
+    private static final int REPEATED_DELTA = 0;
+    private static final int SINGLE_GOOD_DELTA = 1;
+    private static final int SINGLE_TIMEOUT_DELTA = 2;
+    private static final int NUM_REPEATED_BOUND_DELTAS = 2;
+    private static final int MAX_DELTA_LEVEL = 127;
+    private static final int MIN_DELTA_LEVEL = -127;
+    private static final int ARBITRARY_DELTA_LEVEL = 20;
+
+    /**
+     * Sunny day RSSI delta logging scenario.
+     * Logs one rssi delta value multiple times
+     * Logs a different delta value a single time
+     */
+    @Test
+    public void testRssiDeltasSuccessfulLogging() throws Exception {
+        // Generate some repeated deltas
+        for (int i = 0; i < NUM_REPEATED_DELTAS; i++) {
+            generateRssiDelta(MIN_RSSI_LEVEL, REPEATED_DELTA,
+                    WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
+        }
+        // Generate a single delta
+        generateRssiDelta(MIN_RSSI_LEVEL, SINGLE_GOOD_DELTA,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
+        dumpProtoAndDeserialize();
+        assertEquals(2, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        // Check the repeated deltas
+        assertEquals(NUM_REPEATED_DELTAS, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
+        assertEquals(REPEATED_DELTA, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
+        // Check the single delta
+        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[1].count);
+        assertEquals(SINGLE_GOOD_DELTA, mDeserializedWifiMetrics.rssiPollDeltaCount[1].rssi);
+    }
+
+    /**
+     * Tests that Rssi Delta events whose scanResult and Rssi Poll come too far apart, timeout,
+     * and are not logged.
+     */
+    @Test
+    public void testRssiDeltasTimeout() throws Exception {
+        // Create timed out rssi deltas
+        generateRssiDelta(MIN_RSSI_LEVEL, REPEATED_DELTA,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS + 1);
+        generateRssiDelta(MIN_RSSI_LEVEL, SINGLE_TIMEOUT_DELTA,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS + 1);
+        dumpProtoAndDeserialize();
+        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+    }
+
+    /**
+     * Tests the exact inclusive boundaries of RSSI delta logging.
+     */
+    @Test
+    public void testRssiDeltaSuccessfulLoggingExactBounds() throws Exception {
+        generateRssiDelta(MIN_RSSI_LEVEL, MAX_DELTA_LEVEL,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
+        generateRssiDelta(MAX_RSSI_LEVEL, MIN_DELTA_LEVEL,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
+        dumpProtoAndDeserialize();
+        assertEquals(2, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(MIN_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
+        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
+        assertEquals(MAX_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[1].rssi);
+        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[1].count);
+    }
+
+    /**
+     * Tests the exact exclusive boundaries of RSSI delta logging.
+     * This test ensures that too much data is not generated.
+     */
+    @Test
+    public void testRssiDeltaOutOfBounds() throws Exception {
+        generateRssiDelta(MIN_RSSI_LEVEL, MAX_DELTA_LEVEL + 1,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
+        generateRssiDelta(MAX_RSSI_LEVEL, MIN_DELTA_LEVEL - 1,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
+        dumpProtoAndDeserialize();
+        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+    }
+
+    /**
+     * This test ensures no rssi Delta is logged after an unsuccessful ConnectionEvent
+     */
+    @Test
+    public void testUnsuccesfulConnectionEventRssiDeltaIsNotLogged() throws Exception {
+        generateRssiDelta(MIN_RSSI_LEVEL, ARBITRARY_DELTA_LEVEL,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS,
+                false, // successfulConnectionEvent
+                true, // completeConnectionEvent
+                true, // useValidScanResult
+                true // dontDeserializeBeforePoll
+        );
+
+        dumpProtoAndDeserialize();
+        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+    }
+
+    /**
+     * This test ensures rssi Deltas can be logged during a ConnectionEvent
+     */
+    @Test
+    public void testIncompleteConnectionEventRssiDeltaIsLogged() throws Exception {
+        generateRssiDelta(MIN_RSSI_LEVEL, ARBITRARY_DELTA_LEVEL,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS,
+                true, // successfulConnectionEvent
+                false, // completeConnectionEvent
+                true, // useValidScanResult
+                true // dontDeserializeBeforePoll
+        );
+        dumpProtoAndDeserialize();
+        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(ARBITRARY_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
+        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
+    }
+
+    /**
+     * This test ensures that no delta is logged for a null ScanResult Candidate
+     */
+    @Test
+    public void testRssiDeltaNotLoggedForNullCandidateScanResult() throws Exception {
+        generateRssiDelta(MIN_RSSI_LEVEL, ARBITRARY_DELTA_LEVEL,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS,
+                true, // successfulConnectionEvent
+                true, // completeConnectionEvent
+                false, // useValidScanResult
+                true // dontDeserializeBeforePoll
+        );
+        dumpProtoAndDeserialize();
+        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+    }
+
+    /**
+     * This test ensures that Rssi Deltas are not logged over a 'clear()' call (Metrics Serialized)
+     */
+    @Test
+    public void testMetricsSerializedDuringRssiDeltaEventLogsNothing() throws Exception {
+        generateRssiDelta(MIN_RSSI_LEVEL, ARBITRARY_DELTA_LEVEL,
+                WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS,
+                true, // successfulConnectionEvent
+                true, // completeConnectionEvent
+                true, // useValidScanResult
+                false // dontDeserializeBeforePoll
+        );
+        dumpProtoAndDeserialize();
+        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+    }
+
+    private static final int DEAUTH_REASON = 7;
+    private static final int ASSOC_STATUS = 11;
+    private static final int ASSOC_TIMEOUT = 1;
+    private static final int LOCAL_GEN = 1;
+    private static final int AUTH_FAILURE_REASON = WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD;
+    private static final int NUM_TEST_STA_EVENTS = 14;
+    private static final String   sSSID = "\"SomeTestSsid\"";
+    private static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
+    private static final String   sBSSID = "01:02:03:04:05:06";
+
+    private final StateChangeResult mStateDisconnected =
+            new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.DISCONNECTED);
+    private final StateChangeResult mStateCompleted =
+            new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED);
+    // Test bitmasks of supplicant state changes
+    private final int mSupBm1 = WifiMetrics.supplicantStateToBit(mStateDisconnected.state);
+    private final int mSupBm2 = WifiMetrics.supplicantStateToBit(mStateDisconnected.state)
+            | WifiMetrics.supplicantStateToBit(mStateCompleted.state);
+    // An invalid but interesting wifiConfiguration that exercises the StaEvent.ConfigInfo encoding
+    private final WifiConfiguration mTestWifiConfig = createComplexWifiConfig();
+    // <msg.what> <msg.arg1> <msg.arg2>
+    private int[][] mTestStaMessageInts = {
+        {WifiMonitor.ASSOCIATION_REJECTION_EVENT,   ASSOC_TIMEOUT,      ASSOC_STATUS},
+        {WifiMonitor.AUTHENTICATION_FAILURE_EVENT,  0,                  AUTH_FAILURE_REASON},
+        {WifiMonitor.NETWORK_CONNECTION_EVENT,      0,                  0},
+        {WifiMonitor.NETWORK_DISCONNECTION_EVENT,   LOCAL_GEN,          DEAUTH_REASON},
+        {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0,                  0},
+        {WifiStateMachine.CMD_ASSOCIATED_BSSID,     0,                  0},
+        {WifiStateMachine.CMD_TARGET_BSSID,         0,                  0},
+        {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0,                  0},
+        {WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0,                  0}
+    };
+    private Object[] mTestStaMessageObjs = {
+        null,
+        null,
+        null,
+        null,
+        mStateDisconnected,
+        null,
+        null,
+        mStateDisconnected,
+        mStateCompleted
+    };
+    // Values used to generate the StaEvent log calls from WifiStateMachine
+    // <StaEvent.Type>, <StaEvent.FrameworkDisconnectReason>, <1|0>(testWifiConfiguration, null)
+    private int[][] mTestStaLogInts = {
+        {StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL, 0,                          0},
+        {StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST,       0,                          0},
+        {StaEvent.TYPE_CMD_IP_REACHABILITY_LOST,        0,                          0},
+        {StaEvent.TYPE_CMD_START_CONNECT,               0,                          1},
+        {StaEvent.TYPE_CMD_START_ROAM,                  0,                          1},
+        {StaEvent.TYPE_CONNECT_NETWORK,                 0,                          1},
+        {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     0,                          0},
+        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            StaEvent.DISCONNECT_API,    0}
+    };
+    // Values used to generate the StaEvent log calls from WifiMonitor
+    // <type>, <reason>, <status>, <local_gen>,
+    // <auth_fail_reason>, <assoc_timed_out> <supplicantStateChangeBitmask> <1|0>(has ConfigInfo)
+    private int[][] mExpectedValues = {
+        {StaEvent.TYPE_ASSOCIATION_REJECTION_EVENT,     -1,  ASSOC_STATUS,         0,
+            /**/                               0, ASSOC_TIMEOUT,        0, 0},    /**/
+        {StaEvent.TYPE_AUTHENTICATION_FAILURE_EVENT,    -1,            -1,         0,
+            /**/StaEvent.AUTH_FAILURE_WRONG_PSWD,             0,        0, 0},    /**/
+        {StaEvent.TYPE_NETWORK_CONNECTION_EVENT,        -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_NETWORK_DISCONNECTION_EVENT, DEAUTH_REASON,     -1, LOCAL_GEN,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_CMD_ASSOCIATED_BSSID,            -1,            -1,         0,
+            /**/                               0,             0,  mSupBm1, 0},    /**/
+        {StaEvent.TYPE_CMD_TARGET_BSSID,                -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL, -1,            -1,         0,
+            /**/                               0,             0,  mSupBm2, 0},    /**/
+        {StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST,       -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_CMD_IP_REACHABILITY_LOST,        -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_CMD_START_CONNECT,               -1,            -1,         0,
+            /**/                               0,             0,        0, 1},    /**/
+        {StaEvent.TYPE_CMD_START_ROAM,                  -1,            -1,         0,
+            /**/                               0,             0,        0, 1},    /**/
+        {StaEvent.TYPE_CONNECT_NETWORK,                 -1,            -1,         0,
+            /**/                               0,             0,        0, 1},    /**/
+        {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            -1,            -1,         0,
+            /**/                               0,             0,        0, 0}     /**/
+    };
+
+    /**
+     * Generates events from all the rows in mTestStaMessageInts, and then mTestStaLogInts
+     */
+    private void generateStaEvents(WifiMetrics wifiMetrics) {
+        Handler handler = wifiMetrics.getHandler();
+        for (int i = 0; i < mTestStaMessageInts.length; i++) {
+            int[] mia = mTestStaMessageInts[i];
+            handler.sendMessage(
+                    handler.obtainMessage(mia[0], mia[1], mia[2], mTestStaMessageObjs[i]));
+        }
+        mTestLooper.dispatchAll();
+        for (int i = 0; i < mTestStaLogInts.length; i++) {
+            int[] lia = mTestStaLogInts[i];
+            wifiMetrics.logStaEvent(lia[0], lia[1], lia[2] == 1 ? mTestWifiConfig : null);
+        }
+    }
+    private void verifyDeserializedStaEvents(WifiMetricsProto.WifiLog wifiLog) {
+        assertEquals(NUM_TEST_STA_EVENTS, wifiLog.staEventList.length);
+        int j = 0; // De-serialized event index
+        for (int i = 0; i < mTestStaMessageInts.length; i++) {
+            StaEvent event = wifiLog.staEventList[j];
+            int[] mia = mTestStaMessageInts[i];
+            int[] evs = mExpectedValues[j];
+            if (mia[0] != WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT) {
+                assertEquals(evs[0], event.type);
+                assertEquals(evs[1], event.reason);
+                assertEquals(evs[2], event.status);
+                assertEquals(evs[3] == 1 ? true : false, event.localGen);
+                assertEquals(evs[4], event.authFailureReason);
+                assertEquals(evs[5] == 1 ? true : false, event.associationTimedOut);
+                assertEquals(evs[6], event.supplicantStateChangesBitmask);
+                assertConfigInfoEqualsWifiConfig(
+                        evs[7] == 1 ? mTestWifiConfig : null, event.configInfo);
+                j++;
+            }
+        }
+    }
+
+    /**
+     * Generate StaEvents of each type, ensure all the different values are logged correctly,
+     * and that they survive serialization & de-serialization
+     */
+    @Test
+    public void testStaEventsLogSerializeDeserialize() throws Exception {
+        generateStaEvents(mWifiMetrics);
+        dumpProtoAndDeserialize();
+        verifyDeserializedStaEvents(mDeserializedWifiMetrics);
+    }
+
+    /**
+     * Ensure the number of StaEvents does not exceed MAX_STA_EVENTS by generating lots of events
+     * and checking how many are deserialized
+     */
+    @Test
+    public void testStaEventBounding() throws Exception {
+        for (int i = 0; i < (WifiMetrics.MAX_STA_EVENTS + 10); i++) {
+            mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT);
+        }
+        dumpProtoAndDeserialize();
+        assertEquals(WifiMetrics.MAX_STA_EVENTS, mDeserializedWifiMetrics.staEventList.length);
+    }
+
+    /**
+     * Ensure WifiMetrics doesn't cause a null pointer exception when called with null args
+     */
+    @Test
+    public void testDumpNullArg() {
+        mWifiMetrics.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), null);
+    }
+
+    /**
+     * Generate an RSSI delta event by creating a connection event and an RSSI poll within
+     * 'interArrivalTime' milliseconds of each other.
+     * Event will not be logged if interArrivalTime > mWifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS
+     * successfulConnectionEvent, completeConnectionEvent, useValidScanResult and
+     * dontDeserializeBeforePoll
+     * each create an anomalous condition when set to false.
+     */
+    private void generateRssiDelta(int scanRssi, int rssiDelta,
+            long interArrivalTime, boolean successfulConnectionEvent,
+            boolean completeConnectionEvent, boolean useValidScanResult,
+            boolean dontDeserializeBeforePoll) throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 0);
+        ScanResult scanResult = null;
+        if (useValidScanResult) {
+            scanResult = mock(ScanResult.class);
+            scanResult.level = scanRssi;
+        }
+        WifiConfiguration config = mock(WifiConfiguration.class);
+        WifiConfiguration.NetworkSelectionStatus networkSelectionStat =
+                mock(WifiConfiguration.NetworkSelectionStatus.class);
+        when(networkSelectionStat.getCandidate()).thenReturn(scanResult);
+        when(config.getNetworkSelectionStatus()).thenReturn(networkSelectionStat);
+        mWifiMetrics.startConnectionEvent(config, "TestNetwork",
+                WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
+        if (completeConnectionEvent) {
+            if (successfulConnectionEvent) {
+                mWifiMetrics.endConnectionEvent(
+                        WifiMetrics.ConnectionEvent.FAILURE_NONE,
+                        WifiMetricsProto.ConnectionEvent.HLF_NONE);
+            } else {
+                mWifiMetrics.endConnectionEvent(
+                        WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
+                        WifiMetricsProto.ConnectionEvent.HLF_NONE);
+            }
+        }
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(interArrivalTime);
+        if (!dontDeserializeBeforePoll) {
+            dumpProtoAndDeserialize();
+        }
+        mWifiMetrics.incrementRssiPollRssiCount(scanRssi + rssiDelta);
+    }
+    /**
+     * Generate an RSSI delta event, with all extra conditions set to true.
+     */
+    private void generateRssiDelta(int scanRssi, int rssiDelta,
+            long interArrivalTime) throws Exception {
+        generateRssiDelta(scanRssi, rssiDelta, interArrivalTime, true, true, true, true);
+    }
+
     private void assertStringContains(
             String actualString, String expectedSubstring) {
         assertTrue("Expected text not found in: " + actualString,
@@ -673,6 +1103,56 @@
         writer.flush();
         return stream.toString();
     }
+
+    private static final int TEST_ALLOWED_KEY_MANAGEMENT = 83;
+    private static final int TEST_ALLOWED_PROTOCOLS = 22;
+    private static final int TEST_ALLOWED_AUTH_ALGORITHMS = 11;
+    private static final int TEST_ALLOWED_PAIRWISE_CIPHERS = 67;
+    private static final int TEST_ALLOWED_GROUP_CIPHERS = 231;
+    private static final int TEST_CANDIDATE_LEVEL = -80;
+    private static final int TEST_CANDIDATE_FREQ = 2345;
+
+    private WifiConfiguration createComplexWifiConfig() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement = intToBitSet(TEST_ALLOWED_KEY_MANAGEMENT);
+        config.allowedProtocols = intToBitSet(TEST_ALLOWED_PROTOCOLS);
+        config.allowedAuthAlgorithms = intToBitSet(TEST_ALLOWED_AUTH_ALGORITHMS);
+        config.allowedPairwiseCiphers = intToBitSet(TEST_ALLOWED_PAIRWISE_CIPHERS);
+        config.allowedGroupCiphers = intToBitSet(TEST_ALLOWED_GROUP_CIPHERS);
+        config.hiddenSSID = true;
+        config.ephemeral = true;
+        config.getNetworkSelectionStatus().setHasEverConnected(true);
+        ScanResult candidate = new ScanResult();
+        candidate.level = TEST_CANDIDATE_LEVEL;
+        candidate.frequency = TEST_CANDIDATE_FREQ;
+        config.getNetworkSelectionStatus().setCandidate(candidate);
+        return config;
+    }
+
+    private void assertConfigInfoEqualsWifiConfig(WifiConfiguration config,
+            StaEvent.ConfigInfo info) {
+        if (config == null && info == null) return;
+        assertEquals(config.allowedKeyManagement,   intToBitSet(info.allowedKeyManagement));
+        assertEquals(config.allowedProtocols,       intToBitSet(info.allowedProtocols));
+        assertEquals(config.allowedAuthAlgorithms,  intToBitSet(info.allowedAuthAlgorithms));
+        assertEquals(config.allowedPairwiseCiphers, intToBitSet(info.allowedPairwiseCiphers));
+        assertEquals(config.allowedGroupCiphers,    intToBitSet(info.allowedGroupCiphers));
+        assertEquals(config.hiddenSSID, info.hiddenSsid);
+        assertEquals(config.ephemeral, info.isEphemeral);
+        assertEquals(config.getNetworkSelectionStatus().getHasEverConnected(),
+                info.hasEverConnected);
+        assertEquals(config.getNetworkSelectionStatus().getCandidate().level, info.scanRssi);
+        assertEquals(config.getNetworkSelectionStatus().getCandidate().frequency, info.scanFreq);
+    }
+
+    /**
+     * Sets the values of bitSet to match an int mask
+     */
+    private static BitSet intToBitSet(int mask) {
+        BitSet bitSet = new BitSet();
+        for (int bitIndex = 0; mask > 0; mask >>>= 1, bitIndex++) {
+            if ((mask & 1) != 0) bitSet.set(bitIndex);
+        }
+        return bitSet;
+    }
 }
-
-
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMonitorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMonitorTest.java
new file mode 100644
index 0000000..64d7c87
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMonitorTest.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback.WpsConfigError;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantStaIfaceCallback.WpsErrorIndication;
+import android.net.wifi.SupplicantState;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.hotspot2.AnqpEvent;
+import com.android.server.wifi.hotspot2.IconEvent;
+import com.android.server.wifi.util.TelephonyUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiMonitor}.
+ */
+@SmallTest
+public class WifiMonitorTest {
+    private static final String WLAN_IFACE_NAME = "wlan0";
+    private static final String SECOND_WLAN_IFACE_NAME = "wlan1";
+    private static final String[] GSM_AUTH_DATA = { "45adbc", "fead45", "0x3452"};
+    private static final String[] UMTS_AUTH_DATA = { "fead45", "0x3452"};
+    private static final String BSSID = "fe:45:23:12:12:0a";
+    private static final int NETWORK_ID = 5;
+    private static final String SSID = "\"test124\"";
+    private WifiMonitor mWifiMonitor;
+    private TestLooper mLooper;
+    private Handler mHandlerSpy;
+    private Handler mSecondHandlerSpy;
+
+    @Before
+    public void setUp() throws Exception {
+        mWifiMonitor = new WifiMonitor(mock(WifiInjector.class));
+        mLooper = new TestLooper();
+        mHandlerSpy = spy(new Handler(mLooper.getLooper()));
+        mSecondHandlerSpy = spy(new Handler(mLooper.getLooper()));
+        mWifiMonitor.setMonitoring(WLAN_IFACE_NAME, true);
+    }
+
+    /**
+     * Broadcast WPS failure event test.
+     */
+    @Test
+    public void testBroadcastWpsEventFailDueToErrorTkipOnlyProhibhited() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_FAIL_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsFailEvent(
+                WLAN_IFACE_NAME, WpsConfigError.NO_ERROR,
+                WpsErrorIndication.SECURITY_TKIP_ONLY_PROHIBITED);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_FAIL_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiManager.WPS_TKIP_ONLY_PROHIBITED, messageCaptor.getValue().arg1);
+    }
+
+    /**
+     * Broadcast WPS failure event test.
+     */
+    @Test
+    public void testBroadcastWpsEventFailDueToErrorWepProhibhited() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_FAIL_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsFailEvent(
+                WLAN_IFACE_NAME, WpsConfigError.NO_ERROR,
+                WpsErrorIndication.SECURITY_WEP_PROHIBITED);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_FAIL_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiManager.WPS_WEP_PROHIBITED, messageCaptor.getValue().arg1);
+    }
+
+    /**
+     * Broadcast WPS failure event test.
+     */
+    @Test
+    public void testBroadcastWpsEventFailDueToConfigAuthError() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_FAIL_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsFailEvent(
+                WLAN_IFACE_NAME, WpsConfigError.DEV_PASSWORD_AUTH_FAILURE,
+                WpsErrorIndication.NO_ERROR);
+
+        mLooper.dispatchAll();
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_FAIL_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiManager.WPS_AUTH_FAILURE, messageCaptor.getValue().arg1);
+    }
+
+    /**
+     * Broadcast WPS failure event test.
+     */
+    @Test
+    public void testBroadcastWpsEventFailDueToConfigPbcOverlapError() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_FAIL_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsFailEvent(
+                WLAN_IFACE_NAME, WpsConfigError.MULTIPLE_PBC_DETECTED,
+                WpsErrorIndication.NO_ERROR);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_FAIL_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiManager.WPS_OVERLAP_ERROR, messageCaptor.getValue().arg1);
+    }
+
+    /**
+     * Broadcast WPS failure event test.
+     */
+    @Test
+    public void testBroadcastWpsEventFailDueToConfigError() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_FAIL_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsFailEvent(
+                WLAN_IFACE_NAME, WpsConfigError.MSG_TIMEOUT,
+                WpsErrorIndication.NO_ERROR);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_FAIL_EVENT, messageCaptor.getValue().what);
+        assertEquals(WifiManager.ERROR, messageCaptor.getValue().arg1);
+        assertEquals(WpsConfigError.MSG_TIMEOUT, messageCaptor.getValue().arg2);
+    }
+
+    /**
+     * Broadcast WPS success event test.
+     */
+    @Test
+    public void testBroadcastWpsEventSuccess() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_SUCCESS_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsSuccessEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_SUCCESS_EVENT, messageCaptor.getValue().what);
+    }
+
+    /**
+     * Broadcast WPS overlap event test.
+     */
+    @Test
+    public void testBroadcastWpsEventOverlap() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_OVERLAP_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsOverlapEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_OVERLAP_EVENT, messageCaptor.getValue().what);
+    }
+
+    /**
+     * Broadcast WPS timeout event test.
+     */
+    @Test
+    public void testBroadcastWpsEventTimeout() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.WPS_TIMEOUT_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastWpsTimeoutEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.WPS_TIMEOUT_EVENT, messageCaptor.getValue().what);
+    }
+
+    /**
+     * Broadcast ANQP done event test.
+     */
+    @Test
+    public void testBroadcastAnqpDoneEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.ANQP_DONE_EVENT, mHandlerSpy);
+        long bssid = 5;
+        mWifiMonitor.broadcastAnqpDoneEvent(WLAN_IFACE_NAME, new AnqpEvent(bssid, null));
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.ANQP_DONE_EVENT, messageCaptor.getValue().what);
+        assertEquals(bssid, ((AnqpEvent) messageCaptor.getValue().obj).getBssid());
+        assertNull(((AnqpEvent) messageCaptor.getValue().obj).getElements());
+    }
+
+    /**
+     * Broadcast Icon event test.
+     */
+    @Test
+    public void testBroadcastIconDoneEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.RX_HS20_ANQP_ICON_EVENT, mHandlerSpy);
+        long bssid = 5;
+        String fileName = "test";
+        int fileSize = 0;
+        mWifiMonitor.broadcastIconDoneEvent(
+                WLAN_IFACE_NAME, new IconEvent(bssid, fileName, fileSize, null));
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.RX_HS20_ANQP_ICON_EVENT, messageCaptor.getValue().what);
+        assertEquals(bssid, ((IconEvent) messageCaptor.getValue().obj).getBSSID());
+        assertEquals(fileName, ((IconEvent) messageCaptor.getValue().obj).getFileName());
+        assertEquals(fileSize, ((IconEvent) messageCaptor.getValue().obj).getSize());
+        assertNull(((IconEvent) messageCaptor.getValue().obj).getData());
+    }
+
+    /**
+     * Broadcast network Gsm auth request test.
+     */
+    @Test
+    public void testBroadcastNetworkGsmAuthRequestEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_REQUEST_SIM_AUTH, mHandlerSpy);
+        int networkId = NETWORK_ID;
+        String ssid = SSID;
+        String[] data = GSM_AUTH_DATA;
+        mWifiMonitor.broadcastNetworkGsmAuthRequestEvent(WLAN_IFACE_NAME, networkId, ssid, data);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_REQUEST_SIM_AUTH, messageCaptor.getValue().what);
+        TelephonyUtil.SimAuthRequestData authData =
+                (TelephonyUtil.SimAuthRequestData) messageCaptor.getValue().obj;
+        assertEquals(networkId, authData.networkId);
+        assertEquals(ssid, authData.ssid);
+        assertEquals(WifiEnterpriseConfig.Eap.SIM, authData.protocol);
+        assertArrayEquals(data, authData.data);
+    }
+
+    /**
+     * Broadcast network Umts auth request test.
+     */
+    @Test
+    public void testBroadcastNetworkUmtsAuthRequestEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_REQUEST_SIM_AUTH, mHandlerSpy);
+        int networkId = NETWORK_ID;
+        String ssid = SSID;
+        String[] data = UMTS_AUTH_DATA;
+        mWifiMonitor.broadcastNetworkUmtsAuthRequestEvent(WLAN_IFACE_NAME, networkId, ssid, data);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_REQUEST_SIM_AUTH, messageCaptor.getValue().what);
+        TelephonyUtil.SimAuthRequestData authData =
+                (TelephonyUtil.SimAuthRequestData) messageCaptor.getValue().obj;
+        assertEquals(networkId, authData.networkId);
+        assertEquals(ssid, authData.ssid);
+        assertEquals(WifiEnterpriseConfig.Eap.AKA, authData.protocol);
+        assertArrayEquals(data, authData.data);
+    }
+
+    /**
+     * Broadcast pno scan results event test.
+     */
+    @Test
+    public void testBroadcastPnoScanResultsEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.PNO_SCAN_RESULTS_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastPnoScanResultEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.PNO_SCAN_RESULTS_EVENT, messageCaptor.getValue().what);
+    }
+
+    /**
+     * Broadcast Scan results event test.
+     */
+    @Test
+    public void testBroadcastScanResultsEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SCAN_RESULTS_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastScanResultEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SCAN_RESULTS_EVENT, messageCaptor.getValue().what);
+    }
+
+    /**
+     * Broadcast Scan failed event test.
+     */
+    @Test
+    public void testBroadcastScanFailedEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SCAN_FAILED_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastScanFailedEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+
+        assertEquals(WifiMonitor.SCAN_FAILED_EVENT, messageCaptor.getValue().what);
+    }
+
+    /**
+     * Broadcast authentication failure test.
+     */
+    @Test
+    public void testBroadcastAuthenticationFailureEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.AUTHENTICATION_FAILURE_EVENT, mHandlerSpy);
+        int reason = WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD;
+        mWifiMonitor.broadcastAuthenticationFailureEvent(WLAN_IFACE_NAME, reason);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.AUTHENTICATION_FAILURE_EVENT, messageCaptor.getValue().what);
+        assertEquals(reason, messageCaptor.getValue().arg2);
+
+    }
+
+
+    /**
+     * Broadcast association rejection test.
+     */
+    @Test
+    public void testBroadcastAssociationRejectionEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.ASSOCIATION_REJECTION_EVENT, mHandlerSpy);
+        int status = 5;
+        String bssid = BSSID;
+        mWifiMonitor.broadcastAssociationRejectionEvent(WLAN_IFACE_NAME, status, false, bssid);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.ASSOCIATION_REJECTION_EVENT, messageCaptor.getValue().what);
+        assertEquals(0, messageCaptor.getValue().arg1);
+        assertEquals(status, messageCaptor.getValue().arg2);
+        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+    }
+
+    /**
+     * Broadcast associated bssid test.
+     */
+    @Test
+    public void testBroadcastAssociatedBssidEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiStateMachine.CMD_ASSOCIATED_BSSID, mHandlerSpy);
+        String bssid = BSSID;
+        mWifiMonitor.broadcastAssociatedBssidEvent(WLAN_IFACE_NAME, bssid);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiStateMachine.CMD_ASSOCIATED_BSSID, messageCaptor.getValue().what);
+        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+    }
+
+    /**
+     * Broadcast network connection test.
+     */
+    @Test
+    public void testBroadcastNetworkConnectionEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.NETWORK_CONNECTION_EVENT, mHandlerSpy);
+        int networkId = NETWORK_ID;
+        String bssid = BSSID;
+        mWifiMonitor.broadcastNetworkConnectionEvent(WLAN_IFACE_NAME, networkId, bssid);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.NETWORK_CONNECTION_EVENT, messageCaptor.getValue().what);
+        assertEquals(networkId, messageCaptor.getValue().arg1);
+        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+    }
+
+    /**
+     * Broadcast network disconnection test.
+     */
+    @Test
+    public void testBroadcastNetworkDisconnectionEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.NETWORK_DISCONNECTION_EVENT, mHandlerSpy);
+        int local = 1;
+        int reason  = 5;
+        String bssid = BSSID;
+        mWifiMonitor.broadcastNetworkDisconnectionEvent(WLAN_IFACE_NAME, local, reason, bssid);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.NETWORK_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+        assertEquals(local, messageCaptor.getValue().arg1);
+        assertEquals(reason, messageCaptor.getValue().arg2);
+        assertEquals(bssid, (String) messageCaptor.getValue().obj);
+    }
+
+    /**
+     * Broadcast supplicant state change test.
+     */
+    @Test
+    public void testBroadcastSupplicantStateChangeEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, mHandlerSpy);
+        int networkId = NETWORK_ID;
+        WifiSsid wifiSsid = WifiSsid.createFromAsciiEncoded(SSID);
+        String bssid = BSSID;
+        SupplicantState newState = SupplicantState.ASSOCIATED;
+        mWifiMonitor.broadcastSupplicantStateChangeEvent(
+                WLAN_IFACE_NAME, networkId, wifiSsid, bssid, newState);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, messageCaptor.getValue().what);
+        StateChangeResult result = (StateChangeResult) messageCaptor.getValue().obj;
+        assertEquals(networkId, result.networkId);
+        assertEquals(wifiSsid, result.wifiSsid);
+        assertEquals(bssid, result.BSSID);
+        assertEquals(newState, result.state);
+    }
+
+    /**
+     * Broadcast supplicant connection test.
+     */
+    @Test
+    public void testBroadcastSupplicantConnectionEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_CONNECTION_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastSupplicantConnectionEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+    /**
+     * Broadcast supplicant disconnection test.
+     */
+    @Test
+    public void testBroadcastSupplicantDisconnectionEvent() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastSupplicantDisconnectionEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+    /**
+     * Broadcast message to two handlers test.
+     */
+    @Test
+    public void testBroadcastEventToTwoHandlers() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_CONNECTION_EVENT, mHandlerSpy);
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_CONNECTION_EVENT, mSecondHandlerSpy);
+        mWifiMonitor.broadcastSupplicantConnectionEvent(WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
+        verify(mSecondHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+    /**
+     * Broadcast message when iface is null.
+     */
+    @Test
+    public void testBroadcastEventWhenIfaceIsNull() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastSupplicantDisconnectionEvent(null);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+    /**
+     * Broadcast message when iface handler is null.
+     */
+    @Test
+    public void testBroadcastEventWhenIfaceHandlerIsNull() {
+        mWifiMonitor.registerHandler(
+                WLAN_IFACE_NAME, WifiMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
+        mWifiMonitor.broadcastSupplicantDisconnectionEvent(SECOND_WLAN_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+}
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/WifiNativeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
index 7013fe3..2f13baf 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
@@ -20,20 +20,25 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.net.wifi.IApInterface;
+import android.net.wifi.IClientInterface;
+import android.net.wifi.WifiConfiguration;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
-import java.lang.reflect.Constructor;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 /**
@@ -41,16 +46,6 @@
  */
 @SmallTest
 public class WifiNativeTest {
-    private static final int NETWORK_ID = 0;
-    private static final String NETWORK_EXTRAS_VARIABLE = "test";
-    private static final Map<String, String> NETWORK_EXTRAS_VALUES = new HashMap<>();
-    static {
-        NETWORK_EXTRAS_VALUES.put("key1", "value1");
-        NETWORK_EXTRAS_VALUES.put("key2", "value2");
-    }
-    private static final String NETWORK_EXTRAS_SERIALIZED =
-            "\"%7B%22key2%22%3A%22value2%22%2C%22key1%22%3A%22value1%22%7D\"";
-
     private static final long FATE_REPORT_DRIVER_TIMESTAMP_USEC = 12345;
     private static final byte[] FATE_REPORT_FRAME_BYTES = new byte[] {
             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0, 1, 2, 3, 4, 5, 6, 7};
@@ -112,39 +107,55 @@
             new FateMapping(WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER, "driver dropped (other)"),
             new FateMapping((byte) 42, "42")
     };
+    private static final WifiNative.SignalPollResult SIGNAL_POLL_RESULT =
+            new WifiNative.SignalPollResult() {{
+                currentRssi = -60;
+                txBitrate = 12;
+                associationFrequency = 5240;
+            }};
+    private static final WifiNative.TxPacketCounters PACKET_COUNTERS_RESULT =
+            new WifiNative.TxPacketCounters() {{
+                txSucceeded = 2000;
+                txFailed = 120;
+            }};
 
+    private static final Set<Integer> SCAN_FREQ_SET =
+            new HashSet<Integer>() {{
+                add(2410);
+                add(2450);
+                add(5050);
+                add(5200);
+            }};
+    private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
+    private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
+    private static final Set<String> SCAN_HIDDEN_NETWORK_SSID_SET =
+            new HashSet<String>() {{
+                add(TEST_QUOTED_SSID_1);
+                add(TEST_QUOTED_SSID_2);
+            }};
+
+    private static final WifiNative.PnoSettings TEST_PNO_SETTINGS =
+            new WifiNative.PnoSettings() {{
+                isConnected = false;
+                periodInMs = 6000;
+                networkList = new WifiNative.PnoNetwork[2];
+                networkList[0] = new WifiNative.PnoNetwork();
+                networkList[1] = new WifiNative.PnoNetwork();
+                networkList[0].ssid = TEST_QUOTED_SSID_1;
+                networkList[1].ssid = TEST_QUOTED_SSID_2;
+            }};
+
+    @Mock private WifiVendorHal mWifiVendorHal;
+    @Mock private WificondControl mWificondControl;
+    @Mock private SupplicantStaIfaceHal mStaIfaceHal;
     private WifiNative mWifiNative;
 
     @Before
     public void setUp() throws Exception {
-        final Constructor<WifiNative> wifiNativeConstructor =
-                WifiNative.class.getDeclaredConstructor(String.class, Boolean.TYPE);
-        wifiNativeConstructor.setAccessible(true);
-        mWifiNative = spy(wifiNativeConstructor.newInstance("test", true));
-    }
-
-    /**
-     * Verifies that setNetworkExtra() correctly writes a serialized and URL-encoded JSON object.
-     */
-    @Test
-    public void testSetNetworkExtra() {
-        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString())).thenReturn(true);
-        assertTrue(mWifiNative.setNetworkExtra(NETWORK_ID, NETWORK_EXTRAS_VARIABLE,
-                NETWORK_EXTRAS_VALUES));
-        verify(mWifiNative).setNetworkVariable(NETWORK_ID, NETWORK_EXTRAS_VARIABLE,
-                NETWORK_EXTRAS_SERIALIZED);
-    }
-
-    /**
-     * Verifies that getNetworkExtra() correctly reads a serialized and URL-encoded JSON object.
-     */
-    @Test
-    public void testGetNetworkExtra() {
-        when(mWifiNative.getNetworkVariable(NETWORK_ID, NETWORK_EXTRAS_VARIABLE))
-                .thenReturn(NETWORK_EXTRAS_SERIALIZED);
-        final Map<String, String> actualValues =
-                mWifiNative.getNetworkExtra(NETWORK_ID, NETWORK_EXTRAS_VARIABLE);
-        assertEquals(NETWORK_EXTRAS_VALUES, actualValues);
+        MockitoAnnotations.initMocks(this);
+        when(mWifiVendorHal.isVendorHalSupported()).thenReturn(true);
+        when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(true);
+        mWifiNative = new WifiNative("test0", mWifiVendorHal, mStaIfaceHal, mWificondControl);
     }
 
     /**
@@ -181,6 +192,34 @@
         assertArrayEquals(FATE_REPORT_FRAME_BYTES, fateReport.mFrameBytes);
     }
 
+    /**
+     * Verifies the hashCode methods for HiddenNetwork and PnoNetwork classes
+     */
+    @Test
+    public void testHashCode() {
+        WifiNative.HiddenNetwork hiddenNet1 = new WifiNative.HiddenNetwork();
+        hiddenNet1.ssid = new String("sametext");
+
+        WifiNative.HiddenNetwork hiddenNet2 = new WifiNative.HiddenNetwork();
+        hiddenNet2.ssid = new String("sametext");
+
+        assertTrue(hiddenNet1.equals(hiddenNet2));
+        assertEquals(hiddenNet1.hashCode(), hiddenNet2.hashCode());
+
+        WifiNative.PnoNetwork pnoNet1 = new WifiNative.PnoNetwork();
+        pnoNet1.ssid = new String("sametext");
+        pnoNet1.flags = 2;
+        pnoNet1.auth_bit_field = 4;
+
+        WifiNative.PnoNetwork pnoNet2 = new WifiNative.PnoNetwork();
+        pnoNet2.ssid = new String("sametext");
+        pnoNet2.flags = 2;
+        pnoNet2.auth_bit_field = 4;
+
+        assertTrue(pnoNet1.equals(pnoNet2));
+        assertEquals(pnoNet1.hashCode(), pnoNet2.hashCode());
+    }
+
     // Support classes for test{Tx,Rx}FateReportToString.
     private static class FrameTypeMapping {
         byte mTypeNumber;
@@ -439,4 +478,226 @@
     }
 
     // TODO(b/28005116): Add test for the success case of getDriverStateDump().
+
+    /**
+     * Verifies that setupDriverForClientMode() calls underlying WificondControl.
+     */
+    @Test
+    public void testSetupDriverForClientMode() {
+        IClientInterface clientInterface = mock(IClientInterface.class);
+        when(mWificondControl.setupDriverForClientMode()).thenReturn(clientInterface);
+
+        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+        verify(mWifiVendorHal).startVendorHal(eq(true));
+        verify(mWificondControl).setupDriverForClientMode();
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() does not call start vendor HAL when it is not
+     * supported and calls underlying WificondControl setup.
+     */
+    @Test
+    public void testSetupDriverForClientModeWithNoVendorHal() {
+        when(mWifiVendorHal.isVendorHalSupported()).thenReturn(false);
+        IClientInterface clientInterface = mock(IClientInterface.class);
+        when(mWificondControl.setupDriverForClientMode()).thenReturn(clientInterface);
+
+        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+        verify(mWifiVendorHal, never()).startVendorHal(anyBoolean());
+        verify(mWificondControl).setupDriverForClientMode();
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() returns null when underlying WificondControl
+     * call fails.
+     */
+    @Test
+    public void testSetupDriverForClientModeWificondError() {
+        when(mWificondControl.setupDriverForClientMode()).thenReturn(null);
+
+        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
+        assertEquals(null, returnedClientInterface);
+        verify(mWifiVendorHal).startVendorHal(eq(true));
+        verify(mWificondControl).setupDriverForClientMode();
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() returns null when underlying Hal call fails.
+     */
+    @Test
+    public void testSetupDriverForClientModeHalError() {
+        when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(false);
+
+        IClientInterface returnedClientInterface = mWifiNative.setupForClientMode();
+        assertEquals(null, returnedClientInterface);
+        verify(mWifiVendorHal).startVendorHal(eq(true));
+        verify(mWificondControl, never()).setupDriverForClientMode();
+    }
+
+    /**
+     * Verifies that setupDriverForSoftApMode() calls underlying WificondControl.
+     */
+    @Test
+    public void testSetupDriverForSoftApMode() {
+        IApInterface apInterface = mock(IApInterface.class);
+        when(mWificondControl.setupDriverForSoftApMode()).thenReturn(apInterface);
+
+        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
+        assertEquals(apInterface, returnedApInterface);
+        verify(mWifiVendorHal).startVendorHal(eq(false));
+        verify(mWificondControl).setupDriverForSoftApMode();
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() does not call start vendor HAL when it is not
+     * supported and calls underlying WificondControl setup.
+     */
+    @Test
+    public void testSetupDriverForSoftApModeWithNoVendorHal() {
+        when(mWifiVendorHal.isVendorHalSupported()).thenReturn(false);
+        IApInterface apInterface = mock(IApInterface.class);
+        when(mWificondControl.setupDriverForSoftApMode()).thenReturn(apInterface);
+
+        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
+        assertEquals(apInterface, returnedApInterface);
+        verify(mWifiVendorHal, never()).startVendorHal(anyBoolean());
+        verify(mWificondControl).setupDriverForSoftApMode();
+    }
+
+    /**
+     * Verifies that setupDriverForSoftApMode() returns null when underlying WificondControl
+     * call fails.
+     */
+    @Test
+    public void testSetupDriverForSoftApModeWificondError() {
+        when(mWificondControl.setupDriverForSoftApMode()).thenReturn(null);
+        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
+
+        assertEquals(null, returnedApInterface);
+        verify(mWifiVendorHal).startVendorHal(eq(false));
+        verify(mWificondControl).setupDriverForSoftApMode();
+    }
+
+    /**
+     * Verifies that setupDriverForSoftApMode() returns null when underlying Hal call fails.
+     */
+    @Test
+    public void testSetupDriverForSoftApModeHalError() {
+        when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(false);
+
+        IApInterface returnedApInterface = mWifiNative.setupForSoftApMode();
+        assertEquals(null, returnedApInterface);
+        verify(mWifiVendorHal).startVendorHal(eq(false));
+        verify(mWificondControl, never()).setupDriverForSoftApMode();
+    }
+
+    /**
+     * Verifies that enableSupplicant() calls underlying WificondControl.
+     */
+    @Test
+    public void testEnableSupplicant() {
+        when(mWificondControl.enableSupplicant()).thenReturn(true);
+
+        mWifiNative.enableSupplicant();
+        verify(mWificondControl).enableSupplicant();
+    }
+
+    /**
+     * Verifies that disableSupplicant() calls underlying WificondControl.
+     */
+    @Test
+    public void testDisableSupplicant() {
+        when(mWificondControl.disableSupplicant()).thenReturn(true);
+
+        mWifiNative.disableSupplicant();
+        verify(mWificondControl).disableSupplicant();
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls underlying WificondControl.
+     */
+    @Test
+    public void testTearDown() {
+        when(mWificondControl.tearDownInterfaces()).thenReturn(true);
+
+        assertTrue(mWifiNative.tearDown());
+        verify(mWificondControl).tearDownInterfaces();
+        verify(mWifiVendorHal).stopVendorHal();
+    }
+
+    /**
+     * Verifies that signalPoll() calls underlying WificondControl.
+     */
+    @Test
+    public void testSignalPoll() throws Exception {
+        when(mWificondControl.signalPoll()).thenReturn(SIGNAL_POLL_RESULT);
+
+        assertEquals(SIGNAL_POLL_RESULT, mWifiNative.signalPoll());
+        verify(mWificondControl).signalPoll();
+    }
+
+    /**
+     * Verifies that getTxPacketCounters() calls underlying WificondControl.
+     */
+    @Test
+    public void testGetTxPacketCounters() throws Exception {
+        when(mWificondControl.getTxPacketCounters()).thenReturn(PACKET_COUNTERS_RESULT);
+
+        assertEquals(PACKET_COUNTERS_RESULT, mWifiNative.getTxPacketCounters());
+        verify(mWificondControl).getTxPacketCounters();
+    }
+
+    /**
+     * Verifies that scan() calls underlying WificondControl.
+     */
+    @Test
+    public void testScan() throws Exception {
+        mWifiNative.scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET);
+        verify(mWificondControl).scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET);
+    }
+
+    /**
+     * Verifies that startPnoscan() calls underlying WificondControl.
+     */
+    @Test
+    public void testStartPnoScan() throws Exception {
+        mWifiNative.startPnoScan(TEST_PNO_SETTINGS);
+        verify(mWificondControl).startPnoScan(TEST_PNO_SETTINGS);
+    }
+
+    /**
+     * Verifies that stopPnoscan() calls underlying WificondControl.
+     */
+    @Test
+    public void testStopPnoScan() throws Exception {
+        mWifiNative.stopPnoScan();
+        verify(mWificondControl).stopPnoScan();
+    }
+
+    /**
+     * Verifies that connectToNetwork() calls underlying WificondControl and SupplicantStaIfaceHal.
+     */
+    @Test
+    public void testConnectToNetwork() throws Exception {
+        WifiConfiguration config = mock(WifiConfiguration.class);
+        mWifiNative.connectToNetwork(config);
+        // connectToNetwork() should abort ongoing scan before connection.
+        verify(mWificondControl).abortScan();
+        verify(mStaIfaceHal).connectToNetwork(config);
+    }
+
+    /**
+     * Verifies that roamToNetwork() calls underlying WificondControl and SupplicantStaIfaceHal.
+     */
+    @Test
+    public void testRoamToNetwork() throws Exception {
+        WifiConfiguration config = mock(WifiConfiguration.class);
+        mWifiNative.roamToNetwork(config);
+        // roamToNetwork() should abort ongoing scan before connection.
+        verify(mWificondControl).abortScan();
+        verify(mStaIfaceHal).roamToNetwork(config);
+    }
+
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
new file mode 100644
index 0000000..8ad9e07
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
@@ -0,0 +1,660 @@
+/*
+ * 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.WifiConfigurationTestUtil.SECURITY_NONE;
+import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiInfo;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LocalLog;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.server.wifi.WifiNetworkSelectorTestUtil.ScanDetailsAndWifiConfigs;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiNetworkSelector}.
+ */
+@SmallTest
+public class WifiNetworkSelectorTest {
+
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        setupContext();
+        setupResources();
+        setupWifiConfigManager();
+        setupWifiInfo();
+        mLocalLog = new LocalLog(512);
+
+        mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiConfigManager, mClock,
+                mLocalLog);
+        mWifiNetworkSelector.registerNetworkEvaluator(mDummyEvaluator, 1);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime());
+
+        mThresholdMinimumRssi2G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mThresholdMinimumRssi5G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdQualifiedRssi2G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
+        mThresholdQualifiedRssi5G = mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz);
+    }
+
+    /** Cleans up test. */
+    @After
+    public void cleanup() {
+        validateMockitoUsage();
+    }
+
+    /**
+     * All this dummy network evaluator does is to pick the very first network
+     * in the scan results.
+     */
+    public class DummyNetworkEvaluator implements WifiNetworkSelector.NetworkEvaluator {
+        private static final String NAME = "DummyNetworkEvaluator";
+
+        @Override
+        public String getName() {
+            return NAME;
+        }
+
+        @Override
+        public void update(List<ScanDetail> scanDetails) {}
+
+        /**
+         * Always return the first network in the scan results for connection.
+         */
+        @Override
+        public WifiConfiguration evaluateNetworks(List<ScanDetail> scanDetails,
+                    WifiConfiguration currentNetwork, String currentBssid, boolean connected,
+                    boolean untrustedNetworkAllowed,
+                    List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks) {
+            ScanDetail scanDetail = scanDetails.get(0);
+            mWifiConfigManager.setNetworkCandidateScanResult(0, scanDetail.getScanResult(), 100);
+
+            return mWifiConfigManager.getSavedNetworkForScanDetailAndCache(scanDetail);
+        }
+    }
+
+    private WifiNetworkSelector mWifiNetworkSelector = null;
+    private DummyNetworkEvaluator mDummyEvaluator = new DummyNetworkEvaluator();
+    @Mock private WifiConfigManager mWifiConfigManager;
+    @Mock private Context mContext;
+    @Mock private Resources mResource;
+    @Mock private WifiInfo mWifiInfo;
+    @Mock private Clock mClock;
+    private LocalLog mLocalLog;
+    private int mThresholdMinimumRssi2G;
+    private int mThresholdMinimumRssi5G;
+    private int mThresholdQualifiedRssi2G;
+    private int mThresholdQualifiedRssi5G;
+
+    private void setupContext() {
+        when(mContext.getResources()).thenReturn(mResource);
+    }
+
+    private void setupResources() {
+        when(mResource.getBoolean(
+                R.bool.config_wifi_framework_enable_associated_network_selection)).thenReturn(true);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz))
+                .thenReturn(-70);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz))
+                .thenReturn(-73);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz))
+                .thenReturn(-82);
+        when(mResource.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz))
+                .thenReturn(-85);
+    }
+
+    private void setupWifiInfo() {
+        // simulate a disconnected state
+        when(mWifiInfo.is24GHz()).thenReturn(true);
+        when(mWifiInfo.is5GHz()).thenReturn(false);
+        when(mWifiInfo.getRssi()).thenReturn(-70);
+        when(mWifiInfo.getNetworkId()).thenReturn(WifiConfiguration.INVALID_NETWORK_ID);
+        when(mWifiInfo.getBSSID()).thenReturn(null);
+    }
+
+    private void setupWifiConfigManager() {
+        when(mWifiConfigManager.getLastSelectedNetwork())
+                .thenReturn(WifiConfiguration.INVALID_NETWORK_ID);
+    }
+
+    /**
+     * No network selection if scan result is empty.
+     *
+     * WifiStateMachine is in disconnected state.
+     * scanDetails is empty.
+     *
+     * Expected behavior: no network recommended by Network Selector
+     */
+    @Test
+    public void emptyScanResults() {
+        String[] ssids = new String[0];
+        String[] bssids = new String[0];
+        int[] freqs = new int[0];
+        String[] caps = new String[0];
+        int[] levels = new int[0];
+        int[] securities = new int[0];
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+
+    /**
+     * No network selection if the RSSI values in scan result are too low.
+     *
+     * WifiStateMachine is in disconnected state.
+     * scanDetails contains a 2.4GHz and a 5GHz network, but both with RSSI lower than
+     * the threshold
+     *
+     * Expected behavior: no network recommended by Network Selector
+     */
+    @Test
+    public void verifyMinimumRssiThreshold() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G - 1, mThresholdMinimumRssi5G - 1};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+    /**
+     * No network selection if WiFi is connected and it is too short from last
+     * network selection.
+     *
+     * WifiStateMachine is in connected state.
+     * scanDetails contains two valid networks.
+     * Perform a network seletion right after the first one.
+     *
+     * Expected behavior: no network recommended by Network Selector
+     */
+    @Test
+    public void verifyMinimumTimeGapWhenConnected() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        // Make a network selection.
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS - 2000);
+
+        // Do another network selection with WSM in CONNECTED state.
+        candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, true, false, false);
+
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+    /**
+     * Perform network selection if WiFi is disconnected even if it is too short from last
+     * network selection.
+     *
+     * WifiStateMachine is in disconnected state.
+     * scanDetails contains two valid networks.
+     * Perform a network seletion right after the first one.
+     *
+     * Expected behavior: the first network is recommended by Network Selector
+     */
+    @Test
+    public void verifyNoMinimumTimeGapWhenDisconnected() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        // Make a network selection.
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS - 2000);
+
+        // Do another network selection with WSM in DISCONNECTED state.
+        candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * No network selection if the currently connected on is already sufficient.
+     *
+     * WifiStateMachine is connected to a qualified (5G, secure, good RSSI) network.
+     * scanDetails contains a valid network.
+     * Perform a network seletion after the first one.
+     *
+     * Expected behavior: no network recommended by Network Selector
+     */
+    @Test
+    public void noNetworkSelectionWhenCurrentOneIsSufficient() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G + 8};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+
+        // connect to test1
+        mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo, false, true, false);
+        when(mWifiInfo.getNetworkId()).thenReturn(0);
+        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(false);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        levels[0] = mThresholdQualifiedRssi5G + 20;
+        scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        scanDetails = scanDetailsAndConfigs.getScanDetails();
+
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, true, false, false);
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+    /**
+     * New network selection is performed if the currently connected network
+     * band is 2G.
+     *
+     * WifiStateMachine is connected to a 2G network.
+     * scanDetails contains a valid networks.
+     * Perform a network seletion after the first one.
+     *
+     * Expected behavior: the first network is recommended by Network Selector
+     */
+    @Test
+    public void band2GNetworkIsNotSufficient() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {2470};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi2G + 8};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        // connect to test1
+        mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo, false, true, false);
+        when(mWifiInfo.getNetworkId()).thenReturn(0);
+        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        // Do another network selection.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, true, false, false);
+
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+
+    /**
+     * New network selection is performed if the currently connected network
+     * is a open one.
+     *
+     * WifiStateMachine is connected to a open network.
+     * scanDetails contains a valid networks.
+     * Perform a network seletion after the first one.
+     *
+     * Expected behavior: the first network is recommended by Network Selector
+     */
+    @Test
+    public void openNetworkIsNotSufficient() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {5180};
+        String[] caps = {"[ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G + 8};
+        int[] securities = {SECURITY_NONE};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        // connect to test1
+        mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo, false, true, false);
+        when(mWifiInfo.getNetworkId()).thenReturn(0);
+        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(false);
+        when(mWifiInfo.is5GHz()).thenReturn(true);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        // Do another network selection.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, true, false, false);
+
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * New network selection is performed if the currently connected network
+     * has low RSSI value.
+     *
+     * WifiStateMachine is connected to a low RSSI 5GHz network.
+     * scanDetails contains a valid networks.
+     * Perform a network seletion after the first one.
+     *
+     * Expected behavior: the first network is recommended by Network Selector
+     */
+    @Test
+    public void lowRssi5GNetworkIsNotSufficient() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G - 2};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration[] savedConfigs = scanDetailsAndConfigs.getWifiConfigs();
+
+        // connect to test1
+        mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo, false, true, false);
+        when(mWifiInfo.getNetworkId()).thenReturn(0);
+        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(false);
+        when(mWifiInfo.is5GHz()).thenReturn(true);
+        when(mWifiInfo.getRssi()).thenReturn(levels[0]);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        // Do another network selection.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, true, false, false);
+
+        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
+        WifiConfigurationTestUtil.assertConfigurationEqual(savedConfigs[0], candidate);
+        WifiNetworkSelectorTestUtil.verifySelectedScanResult(mWifiConfigManager,
+                chosenScanResult, candidate);
+    }
+
+    /**
+     * Blacklisted BSSID is filtered out for network selection.
+     *
+     * WifiStateMachine is disconnected.
+     * scanDetails contains a network which is blacklisted.
+     *
+     * Expected behavior: no network recommended by Network Selector
+     */
+    @Test
+    public void filterOutBlacklistedBssid() {
+        String[] ssids = {"\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
+        int[] freqs = {5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdQualifiedRssi5G + 8};
+        int[] securities = {SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        blacklist.add(bssids[0]);
+
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+    /**
+     * Wifi network selector doesn't recommend any network if the currently connected one
+     * doesn't show up in the scan results.
+     *
+     * WifiStateMachine is under connected state and 2.4GHz test1 is connected.
+     * The second scan results contains only test2 which now has a stronger RSSI than test1.
+     * Test1 is not in the second scan results.
+     *
+     * Expected behavior: no network recommended by Network Selector
+     */
+    @Test
+    public void noSelectionWhenCurrentNetworkNotInScanResults() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 2457};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + 20, mThresholdMinimumRssi2G + 1};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        // Make a network selection to connect to test1.
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                    freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+
+        when(mWifiInfo.getNetworkId()).thenReturn(0);
+        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
+        when(mWifiInfo.is24GHz()).thenReturn(true);
+        when(mWifiInfo.is5GHz()).thenReturn(false);
+        when(mWifiInfo.getRssi()).thenReturn(levels[0]);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        // Prepare the second scan results which have no test1.
+        String[] ssidsNew = {"\"test2\""};
+        String[] bssidsNew = {"6c:f3:7f:ae:8c:f4"};
+        int[] freqsNew = {2457};
+        String[] capsNew = {"[WPA2-EAP-CCMP][ESS]"};
+        int[] levelsNew = {mThresholdMinimumRssi2G + 40};
+        scanDetails = WifiNetworkSelectorTestUtil.buildScanDetails(ssidsNew, bssidsNew,
+                freqsNew, capsNew, levelsNew, mClock);
+        candidate = mWifiNetworkSelector.selectNetwork(scanDetails, blacklist, mWifiInfo,
+                true, false, false);
+
+        // The second network selection is skipped since current connected network is
+        // missing from the scan results.
+        assertEquals("Expect null configuration", null, candidate);
+    }
+
+    /**
+     * Ensures that settings the user connect choice updates the
+     * NetworkSelectionStatus#mConnectChoice for all other WifiConfigurations in range in the last
+     * round of network selection.
+     *
+     * Expected behavior: WifiConfiguration.NetworkSelectionStatus#mConnectChoice is set to
+     *                    test1's configkey for test2. test3's WifiConfiguration is unchanged.
+     */
+    @Test
+    public void setUserConnectChoice() {
+        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
+        int[] freqs = {2437, 5180, 5181};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1,
+                mThresholdMinimumRssi5G + 1};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+
+        WifiConfiguration selectedWifiConfig = scanDetailsAndConfigs.getWifiConfigs()[0];
+        selectedWifiConfig.getNetworkSelectionStatus()
+                .setCandidate(scanDetailsAndConfigs.getScanDetails().get(0).getScanResult());
+        selectedWifiConfig.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED);
+        selectedWifiConfig.getNetworkSelectionStatus().setConnectChoice("bogusKey");
+
+        WifiConfiguration configInLastNetworkSelection = scanDetailsAndConfigs.getWifiConfigs()[1];
+        configInLastNetworkSelection.getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+
+        WifiConfiguration configNotInLastNetworkSelection =
+                scanDetailsAndConfigs.getWifiConfigs()[2];
+
+        assertTrue(mWifiNetworkSelector.setUserConnectChoice(selectedWifiConfig.networkId));
+
+        verify(mWifiConfigManager).updateNetworkSelectionStatus(selectedWifiConfig.networkId,
+                NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+        verify(mWifiConfigManager).clearNetworkConnectChoice(selectedWifiConfig.networkId);
+        verify(mWifiConfigManager).setNetworkConnectChoice(configInLastNetworkSelection.networkId,
+                selectedWifiConfig.configKey(), mClock.getWallClockMillis());
+        verify(mWifiConfigManager, never()).setNetworkConnectChoice(
+                configNotInLastNetworkSelection.networkId, selectedWifiConfig.configKey(),
+                mClock.getWallClockMillis());
+    }
+
+    /**
+     * If two qualified networks, test1 and test2, are in range when the user selects test2 over
+     * test1, WifiNetworkSelector will override the NetworkSelector's choice to connect to test1
+     * with test2.
+     *
+     * Expected behavior: test2 is the recommended network
+     */
+    @Test
+    public void userConnectChoiceOverridesNetworkEvaluators() {
+        String[] ssids = {"\"test1\"", "\"test2\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] freqs = {2437, 5180};
+        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
+        int[] levels = {mThresholdMinimumRssi2G + 1, mThresholdMinimumRssi5G + 1};
+        int[] securities = {SECURITY_PSK, SECURITY_PSK};
+
+        ScanDetailsAndWifiConfigs scanDetailsAndConfigs =
+                WifiNetworkSelectorTestUtil.setupScanDetailsAndConfigStore(ssids, bssids,
+                        freqs, caps, levels, securities, mWifiConfigManager, mClock);
+        List<ScanDetail> scanDetails = scanDetailsAndConfigs.getScanDetails();
+        HashSet<String> blacklist = new HashSet<String>();
+
+        // DummyEvaluator always selects the first network in the list.
+        WifiConfiguration networkSelectorChoice = scanDetailsAndConfigs.getWifiConfigs()[0];
+        networkSelectorChoice.getNetworkSelectionStatus()
+                .setSeenInLastQualifiedNetworkSelection(true);
+
+        WifiConfiguration userChoice = scanDetailsAndConfigs.getWifiConfigs()[1];
+        userChoice.getNetworkSelectionStatus()
+                .setCandidate(scanDetailsAndConfigs.getScanDetails().get(1).getScanResult());
+
+        // With no user choice set, networkSelectorChoice should be chosen.
+        WifiConfiguration candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(networkSelectorChoice, candidate);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(SystemClock.elapsedRealtime()
+                + WifiNetworkSelector.MINIMUM_NETWORK_SELECTION_INTERVAL_MS + 2000);
+
+        assertTrue(mWifiNetworkSelector.setUserConnectChoice(userChoice.networkId));
+
+        // After user connect choice is set, userChoice should override networkSelectorChoice.
+        candidate = mWifiNetworkSelector.selectNetwork(scanDetails,
+                blacklist, mWifiInfo, false, true, false);
+
+        WifiConfigurationTestUtil.assertConfigurationEqual(userChoice, candidate);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
new file mode 100644
index 0000000..9c78c9b
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTestUtil.java
@@ -0,0 +1,403 @@
+/*
+ * 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.WifiConfigurationTestUtil.generateWifiConfig;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
+import android.net.wifi.WifiNetworkScoreCache;
+import android.net.wifi.WifiSsid;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import com.android.server.wifi.util.ScanResultUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper for WifiNetworkSelector unit tests.
+ */
+@SmallTest
+public class WifiNetworkSelectorTestUtil {
+
+    /**
+     * A class that holds a list of scanDetail and their associated WifiConfiguration.
+     */
+    public static class ScanDetailsAndWifiConfigs {
+        List<ScanDetail> mScanDetails;
+        WifiConfiguration[] mWifiConfigs;
+
+        ScanDetailsAndWifiConfigs(List<ScanDetail> scanDetails, WifiConfiguration[] configs) {
+            mScanDetails = scanDetails;
+            mWifiConfigs = configs;
+        }
+
+        List<ScanDetail> getScanDetails() {
+            return mScanDetails;
+        }
+
+        WifiConfiguration[] getWifiConfigs() {
+            return mWifiConfigs;
+        }
+    }
+
+    /**
+     * Build a list of ScanDetail based on the caller supplied network SSID, BSSID,
+     * frequency, capability and RSSI level information. Create the corresponding
+     * WifiConfiguration for these networks and set up the mocked WifiConfigManager.
+     *
+     * @param ssids an array of SSIDs
+     * @param bssids an array of BSSIDs
+     * @param freqs an array of the network's frequency
+     * @param caps an array of the network's capability
+     * @param levels an array of the network's RSSI levels
+     * @param securities an array of the network's security setting
+     * @param wifiConfigManager the mocked WifiConfigManager
+     * @return the constructed ScanDetail list and WifiConfiguration array
+     */
+    public static ScanDetailsAndWifiConfigs setupScanDetailsAndConfigStore(String[] ssids,
+                String[] bssids, int[] freqs, String[] caps, int[] levels, int[] securities,
+                WifiConfigManager wifiConfigManager, Clock clock) {
+        List<ScanDetail> scanDetails = buildScanDetails(ssids, bssids, freqs, caps, levels, clock);
+        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, securities);
+        prepareConfigStore(wifiConfigManager, savedConfigs);
+        scanResultLinkConfiguration(wifiConfigManager, savedConfigs, scanDetails);
+
+        return new ScanDetailsAndWifiConfigs(scanDetails, savedConfigs);
+    }
+
+    /**
+     * Verify whether the WifiConfiguration chosen by WifiNetworkSelector matches
+     * with the chosen scan result.
+     *
+     * @param chosenScanResult the chosen scan result
+     * @param chosenCandidate  the chosen configuration
+     */
+    public static void verifySelectedScanResult(WifiConfigManager wifiConfigManager,
+            ScanResult chosenScanResult, WifiConfiguration chosenCandidate) {
+        verify(wifiConfigManager, atLeastOnce()).setNetworkCandidateScanResult(
+                eq(chosenCandidate.networkId), eq(chosenScanResult), anyInt());
+    }
+
+
+    /**
+     * Build a list of scanDetails based on the caller supplied network SSID, BSSID,
+     * frequency, capability and RSSI level information.
+     *
+     * @param ssids an array of SSIDs
+     * @param bssids an array of BSSIDs
+     * @param freqs an array of the network's frequency
+     * @param caps an array of the network's capability
+     * @param levels an array of the network's RSSI levels
+     * @return the constructed list of ScanDetail
+     */
+    public static List<ScanDetail> buildScanDetails(String[] ssids, String[] bssids, int[] freqs,
+                                            String[] caps, int[] levels, Clock clock) {
+        List<ScanDetail> scanDetailList = new ArrayList<ScanDetail>();
+
+        long timeStamp = clock.getElapsedSinceBootMillis();
+        for (int index = 0; index < ssids.length; index++) {
+            ScanDetail scanDetail = new ScanDetail(WifiSsid.createFromAsciiEncoded(ssids[index]),
+                    bssids[index], caps[index], levels[index], freqs[index], timeStamp, 0);
+            scanDetailList.add(scanDetail);
+        }
+        return scanDetailList;
+    }
+
+
+    /**
+     * Generate an array of {@link android.net.wifi.WifiConfiguration} based on the caller
+     * supplied network SSID and security information.
+     *
+     * @param ssids an array of SSIDs
+     * @param securities an array of the network's security setting
+     * @return the constructed array of {@link android.net.wifi.WifiConfiguration}
+     */
+    public static WifiConfiguration[] generateWifiConfigurations(String[] ssids,
+                int[] securities) {
+        if (ssids == null || securities == null || ssids.length != securities.length
+                || ssids.length == 0) {
+            return null;
+        }
+
+        Map<String, Integer> netIdMap = new HashMap<>();
+        int netId = 0;
+
+        WifiConfiguration[] configs = new WifiConfiguration[ssids.length];
+        for (int index = 0; index < ssids.length; index++) {
+            String configKey = ssids[index] + Integer.toString(securities[index]);
+            Integer id;
+
+            id = netIdMap.get(configKey);
+            if (id == null) {
+                id = new Integer(netId);
+                netIdMap.put(configKey, id);
+                netId++;
+            }
+
+            configs[index] = generateWifiConfig(id.intValue(), 0, ssids[index], false, true, null,
+                    null, securities[index]);
+        }
+
+        return configs;
+    }
+
+    /**
+     * Add the Configurations to WifiConfigManager (WifiConfigureStore can take them out according
+     * to the networkd ID) and setup the WifiConfigManager mocks for these networks.
+     * This simulates the WifiConfigManager class behaviour.
+     *
+     * @param wifiConfigManager the mocked WifiConfigManager
+     * @param configs input configuration need to be added to WifiConfigureStore
+     */
+    private static void prepareConfigStore(final WifiConfigManager wifiConfigManager,
+                final WifiConfiguration[] configs) {
+        when(wifiConfigManager.getConfiguredNetwork(anyInt()))
+                .then(new AnswerWithArguments() {
+                    public WifiConfiguration answer(int netId) {
+                        for (WifiConfiguration config : configs) {
+                            if (netId == config.networkId) {
+                                return new WifiConfiguration(config);
+                            }
+                        }
+                        return null;
+                    }
+                });
+        when(wifiConfigManager.getConfiguredNetwork(anyString()))
+                .then(new AnswerWithArguments() {
+                    public WifiConfiguration answer(String configKey) {
+                        for (WifiConfiguration config : configs) {
+                            if (TextUtils.equals(config.configKey(), configKey)) {
+                                return new WifiConfiguration(config);
+                            }
+                        }
+                        return null;
+                    }
+                });
+        when(wifiConfigManager.getSavedNetworks())
+                .then(new AnswerWithArguments() {
+                    public List<WifiConfiguration> answer() {
+                        List<WifiConfiguration> savedNetworks = new ArrayList<>();
+                        for (int netId = 0; netId < configs.length; netId++) {
+                            savedNetworks.add(new WifiConfiguration(configs[netId]));
+                        }
+                        return savedNetworks;
+                    }
+                });
+        when(wifiConfigManager.clearNetworkCandidateScanResult(anyInt()))
+                .then(new AnswerWithArguments() {
+                    public boolean answer(int netId) {
+                        if (netId >= 0 && netId < configs.length) {
+                            configs[netId].getNetworkSelectionStatus().setCandidate(null);
+                            configs[netId].getNetworkSelectionStatus()
+                                    .setCandidateScore(Integer.MIN_VALUE);
+                            configs[netId].getNetworkSelectionStatus()
+                                    .setSeenInLastQualifiedNetworkSelection(false);
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+                });
+        when(wifiConfigManager.setNetworkCandidateScanResult(
+                anyInt(), any(ScanResult.class), anyInt()))
+                .then(new AnswerWithArguments() {
+                    public boolean answer(int netId, ScanResult scanResult, int score) {
+                        if (netId >= 0 && netId < configs.length) {
+                            configs[netId].getNetworkSelectionStatus().setCandidate(scanResult);
+                            configs[netId].getNetworkSelectionStatus().setCandidateScore(score);
+                            configs[netId].getNetworkSelectionStatus()
+                                    .setSeenInLastQualifiedNetworkSelection(true);
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+                });
+        when(wifiConfigManager.clearNetworkConnectChoice(anyInt()))
+                .then(new AnswerWithArguments() {
+                    public boolean answer(int netId) {
+                        if (netId >= 0 && netId < configs.length) {
+                            configs[netId].getNetworkSelectionStatus().setConnectChoice(null);
+                            configs[netId].getNetworkSelectionStatus()
+                                    .setConnectChoiceTimestamp(
+                                            NetworkSelectionStatus
+                                                    .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+                });
+        when(wifiConfigManager.setNetworkConnectChoice(anyInt(), anyString(), anyLong()))
+                .then(new AnswerWithArguments() {
+                    public boolean answer(int netId, String configKey, long timestamp) {
+                        if (netId >= 0 && netId < configs.length) {
+                            configs[netId].getNetworkSelectionStatus().setConnectChoice(configKey);
+                            configs[netId].getNetworkSelectionStatus().setConnectChoiceTimestamp(
+                                    timestamp);
+                            return true;
+                        } else {
+                            return false;
+                        }
+                    }
+                });
+    }
+
+
+    /**
+     * Link scan results to the saved configurations.
+     *
+     * The shorter of the 2 input params will be used to loop over so the inputs don't
+     * need to be of equal length. If there are more scan details then configs the remaining scan
+     * details will be associated with a NULL config.
+     *
+     * @param wifiConfigManager the mocked WifiConfigManager
+     * @param configs     saved configurations
+     * @param scanDetails come in scan results
+     */
+    private static void scanResultLinkConfiguration(WifiConfigManager wifiConfigManager,
+                WifiConfiguration[] configs, List<ScanDetail> scanDetails) {
+        if (configs == null || scanDetails == null) {
+            return;
+        }
+
+        if (scanDetails.size() <= configs.length) {
+            for (int i = 0; i < scanDetails.size(); i++) {
+                ScanDetail scanDetail = scanDetails.get(i);
+                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
+                        .thenReturn(configs[i]);
+            }
+        } else {
+            for (int i = 0; i < configs.length; i++) {
+                ScanDetail scanDetail = scanDetails.get(i);
+                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
+                        .thenReturn(configs[i]);
+            }
+
+            // associated the remaining scan details with a NULL config.
+            for (int i = configs.length; i < scanDetails.size(); i++) {
+                when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                        eq(scanDetails.get(i)))).thenReturn(null);
+            }
+        }
+    }
+
+
+    /**
+     * Configure the score cache for externally scored networks
+     *
+     * @param scoreCache   Wifi network score cache to be configured
+     * @param scanDetails  a list of ScanDetail
+     * @param scores       scores of the networks
+     * @param meteredHints hints of if the networks are metered
+     */
+    public static void configureScoreCache(WifiNetworkScoreCache scoreCache,
+            List<ScanDetail> scanDetails, Integer[] scores, boolean[] meteredHints) {
+        List<ScoredNetwork> networks = new ArrayList<>();
+
+        for (int i = 0; i < scanDetails.size(); i++) {
+            ScanDetail scanDetail = scanDetails.get(i);
+            ScanResult scanResult = scanDetail.getScanResult();
+            WifiKey wifiKey = new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID);
+            NetworkKey ntwkKey = new NetworkKey(wifiKey);
+            RssiCurve rssiCurve;
+
+            if (scores != null) { // fixed score
+                byte rssiScore;
+                Integer score = scores[i];
+
+                if (scores[i] == null) {
+                    rssiScore = WifiNetworkScoreCache.INVALID_NETWORK_SCORE;
+                } else {
+                    rssiScore = scores[i].byteValue();
+                }
+                rssiCurve = new RssiCurve(-100, 100, new byte[] {rssiScore});
+            } else {
+                rssiCurve = new RssiCurve(-80, 20, new byte[] {-10, 0, 10, 20, 30, 40});
+            }
+            ScoredNetwork scoredNetwork = new ScoredNetwork(ntwkKey, rssiCurve, meteredHints[i]);
+
+            networks.add(scoredNetwork);
+        }
+
+        scoreCache.updateScores(networks);
+    }
+
+    /**
+     * Setup WifiConfigManager mock for ephemeral networks.
+     *
+     * @param wifiConfigManager WifiConfigManager mock
+     * @param networkId         ID of the ephemeral network
+     * @param scanDetail        scanDetail of the ephemeral network
+     * @param meteredHint       flag to indidate if the network has meteredHint
+     */
+    public static WifiConfiguration setupEphemeralNetwork(WifiConfigManager wifiConfigManager,
+            int networkId, ScanDetail scanDetail, boolean meteredHint) {
+        // Return the correct networkID for ephemeral network addition.
+        when(wifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(networkId));
+        final WifiConfiguration config =
+                ScanResultUtil.createNetworkFromScanResult(scanDetail.getScanResult());
+        config.ephemeral = true;
+        config.networkId = networkId;
+        config.meteredHint = meteredHint;
+
+        when(wifiConfigManager.getSavedNetworkForScanDetailAndCache(eq(scanDetail)))
+                .thenReturn(new WifiConfiguration(config));
+        when(wifiConfigManager.getConfiguredNetwork(eq(networkId)))
+                .then(new AnswerWithArguments() {
+                    public WifiConfiguration answer(int netId) {
+                        return new WifiConfiguration(config);
+                    }
+                });
+        when(wifiConfigManager.setNetworkCandidateScanResult(
+                eq(networkId), any(ScanResult.class), anyInt()))
+                .then(new AnswerWithArguments() {
+                    public boolean answer(int netId, ScanResult scanResult, int score) {
+                        config.getNetworkSelectionStatus().setCandidate(scanResult);
+                        config.getNetworkSelectionStatus().setCandidateScore(score);
+                        config.getNetworkSelectionStatus()
+                                .setSeenInLastQualifiedNetworkSelection(true);
+                        return true;
+                    }
+                });
+        when(wifiConfigManager.updateNetworkSelectionStatus(eq(networkId),
+                eq(WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE)))
+                .then(new AnswerWithArguments() {
+                    public boolean answer(int netId, int status) {
+                        config.getNetworkSelectionStatus().setNetworkSelectionStatus(
+                                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+                        return true;
+                    }
+                });
+        return config;
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
index 41e4e46..f7b3bf6 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
@@ -26,14 +26,16 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.net.NetworkInfo;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -54,9 +56,12 @@
     public static final String TAG = "WifiScanningServiceTest";
 
     @Mock private Context mContext;
-    @Mock private WifiStateMachine mWifiStateMachine;
+    @Mock private Resources mResources;
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private NotificationManager mNotificationManager;
+    @Mock private UserManager mUserManager;
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private WifiScanner mWifiScanner;
     WifiNotificationController mWifiNotificationController;
 
     /**
@@ -69,20 +74,22 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-
-        // Needed for the NotificationEnabledSettingObserver.
-        when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
-
         when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
                 .thenReturn(mNotificationManager);
 
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(1);
 
-        MockLooper mock_looper = new MockLooper();
+        when(mContext.getSystemService(Context.USER_SERVICE))
+                .thenReturn(mUserManager);
+        when(mContext.getResources()).thenReturn(mResources);
+
+        when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
+
+        TestLooper mock_looper = new TestLooper();
         mWifiNotificationController = new WifiNotificationController(
-                mContext, mock_looper.getLooper(), mWifiStateMachine, mFrameworkFacade,
-                mock(Notification.Builder.class));
+                mContext, mock_looper.getLooper(), mFrameworkFacade,
+                mock(Notification.Builder.class), mWifiInjector);
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
@@ -96,7 +103,7 @@
         ScanResult scanResult = new ScanResult();
         scanResult.capabilities = "[ESS]";
         scanResults.add(scanResult);
-        when(mWifiStateMachine.syncGetScanResultsList()).thenReturn(scanResults);
+        when(mWifiScanner.getSingleScanResults()).thenReturn(scanResults);
     }
 
     /** Verifies that a notification is displayed (and retracted) given system events. */
@@ -111,8 +118,7 @@
         TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
         TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
         verify(mNotificationManager, never())
-                .notifyAsUser(any(String.class), anyInt(), any(Notification.class),
-                        any(UserHandle.class));
+                .notifyAsUser(any(), anyInt(), any(), any(UserHandle.class));
 
         // Changing to and from "SCANNING" state should not affect the counter.
         TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
@@ -120,21 +126,36 @@
         TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
                 NetworkInfo.DetailedState.DISCONNECTED);
 
-        // Needed while WifiNotificationController creates its notification.
-        when(mContext.getResources()).thenReturn(mock(Resources.class));
-
         // The third scan result notification will trigger the notification.
         TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
         verify(mNotificationManager)
-                .notifyAsUser(any(String.class), anyInt(), any(Notification.class),
-                        any(UserHandle.class));
+                .notifyAsUser(any(), anyInt(), any(), any(UserHandle.class));
         verify(mNotificationManager, never())
-                .cancelAsUser(any(String.class), anyInt(), any(UserHandle.class));
+                .cancelAsUser(any(), anyInt(), any(UserHandle.class));
 
         // Changing network state should cause the notification to go away.
         TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
                 NetworkInfo.DetailedState.CONNECTED);
         verify(mNotificationManager)
-                .cancelAsUser(any(String.class), anyInt(), any(UserHandle.class));
+                .cancelAsUser(any(), anyInt(), any(UserHandle.class));
+    }
+
+    @Test
+    public void verifyNotificationNotDisplayed_userHasDisallowConfigWifiRestriction() {
+        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
+                .thenReturn(true);
+
+        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext, WifiManager.WIFI_STATE_ENABLED);
+        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
+                NetworkInfo.DetailedState.DISCONNECTED);
+        setOpenAccessPoint();
+
+        // The notification should be displayed after three scan results.
+        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
+        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
+        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
+
+        verify(mNotificationManager, never())
+                .notifyAsUser(any(), anyInt(), any(), any(UserHandle.class));
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
deleted file mode 100644
index c97618d..0000000
--- a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
+++ /dev/null
@@ -1,2685 +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 com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_EAP;
-import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_NONE;
-import static com.android.server.wifi.WifiConfigurationTestUtil.SECURITY_PSK;
-import static com.android.server.wifi.WifiConfigurationTestUtil.generateWifiConfig;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import org.mockito.AdditionalAnswers;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.validateMockitoUsage;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.NetworkScoreManager;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiEnterpriseConfig;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiSsid;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.LocalLog;
-import com.android.server.wifi.NetworkUpdateResult;
-
-import com.android.internal.R;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-import com.android.server.wifi.util.ScanDetailUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Unit tests for {@link com.android.server.wifi.WifiQualifiedNetworkSelector}.
- */
-@SmallTest
-public class WifiQualifiedNetworkSelectorTest {
-
-    @Before
-    public void setUp() throws Exception {
-        mResource = getResource();
-        mScoreManager = getNetworkScoreManager();
-        mScoreCache = getScoreCache();
-        mContext = getContext();
-        mWifiConfigManager = getWifiConfigManager();
-        mWifiInfo = getWifiInfo();
-        mLocalLog = getLocalLog();
-
-        mWifiQualifiedNetworkSelector = new WifiQualifiedNetworkSelector(mWifiConfigManager,
-                mContext, mWifiInfo, mClock);
-        mWifiQualifiedNetworkSelector.enableVerboseLogging(1);
-        mWifiQualifiedNetworkSelector.setUserPreferredBand(1);
-        mWifiQualifiedNetworkSelector.setWifiNetworkScoreCache(mScoreCache);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
-
-        //setup Carrier Networks
-        int eapType = 4;
-
-        WifiConfiguration wifiConfig = new WifiConfiguration();
-        wifiConfig.SSID = "\"TEST1\"";
-        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-        wifiConfig.enterpriseConfig = new WifiEnterpriseConfig();
-        wifiConfig.enterpriseConfig.setEapMethod(eapType);
-        mCarrierConfiguredNetworks.add(wifiConfig);
-
-        WifiConfiguration wifiConfig1 = new WifiConfiguration();
-        wifiConfig1.SSID = "\"TEST2\"";
-        wifiConfig1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-        wifiConfig1.enterpriseConfig = new WifiEnterpriseConfig();
-        wifiConfig1.enterpriseConfig.setEapMethod(eapType);
-        mCarrierConfiguredNetworks.add(wifiConfig1);
-
-        WifiConfiguration wifiConfig2 = new WifiConfiguration();
-        wifiConfig2.SSID = "\"TEST3\"";
-        wifiConfig2.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
-        wifiConfig2.enterpriseConfig = new WifiEnterpriseConfig();
-        wifiConfig2.enterpriseConfig.setEapMethod(eapType);
-        mCarrierConfiguredNetworks.add(wifiConfig2);
-        mWifiQualifiedNetworkSelector.setCarrierConfiguredNetworks(mCarrierConfiguredNetworks);
-    }
-
-    @After
-    public void cleanup() {
-        validateMockitoUsage();
-    }
-
-    private WifiQualifiedNetworkSelector mWifiQualifiedNetworkSelector = null;
-    private WifiConfigManager mWifiConfigManager = null;
-    private Context mContext;
-    private Resources mResource;
-    private NetworkScoreManager mScoreManager;
-    private WifiNetworkScoreCache mScoreCache;
-    private WifiInfo mWifiInfo;
-    private LocalLog mLocalLog;
-    private Clock mClock = mock(Clock.class);
-    private static final String[] DEFAULT_SSIDS = {"\"test1\"", "\"test2\""};
-    private static final String[] DEFAULT_BSSIDS = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-    private static final String TAG = "QNS Unit Test";
-    List<WifiConfiguration> mCarrierConfiguredNetworks = new ArrayList<WifiConfiguration>();
-
-    private List<ScanDetail> getScanDetails(String[] ssids, String[] bssids, int[] frequencies,
-                                            String[] caps, int[] levels) {
-        List<ScanDetail> scanDetailList = new ArrayList<ScanDetail>();
-        long timeStamp = mClock.elapsedRealtime();
-        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);
-            scanDetailList.add(scanDetail);
-        }
-        return scanDetailList;
-    }
-
-    Context getContext() {
-        Context context = mock(Context.class);
-        Resources resource = mock(Resources.class);
-
-        when(context.getResources()).thenReturn(mResource);
-        when(context.getSystemService(Context.NETWORK_SCORE_SERVICE)).thenReturn(mScoreManager);
-        return context;
-    }
-
-    Resources getResource() {
-        Resources resource = mock(Resources.class);
-
-        when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
-        when(resource.getInteger(R.integer.config_wifi_framework_RSSI_SCORE_OFFSET)).thenReturn(85);
-        when(resource.getInteger(R.integer.config_wifi_framework_SAME_BSSID_AWARD)).thenReturn(24);
-        when(resource.getInteger(R.integer.config_wifi_framework_LAST_SELECTION_AWARD))
-                .thenReturn(480);
-        when(resource.getInteger(R.integer.config_wifi_framework_PASSPOINT_SECURITY_AWARD))
-                .thenReturn(40);
-        when(resource.getInteger(R.integer.config_wifi_framework_SECURITY_AWARD)).thenReturn(80);
-        when(resource.getInteger(R.integer.config_wifi_framework_RSSI_SCORE_SLOPE)).thenReturn(4);
-        return resource;
-    }
-
-    NetworkScoreManager getNetworkScoreManager() {
-        NetworkScoreManager networkScoreManager = mock(NetworkScoreManager.class);
-
-        return networkScoreManager;
-    }
-
-    WifiNetworkScoreCache getScoreCache() {
-        return mock(WifiNetworkScoreCache.class);
-    }
-
-    LocalLog getLocalLog() {
-        return new LocalLog(0);
-    }
-
-    WifiInfo getWifiInfo() {
-        WifiInfo wifiInfo = mock(WifiInfo.class);
-
-        //simulate a disconnected state
-        when(wifiInfo.is24GHz()).thenReturn(true);
-        when(wifiInfo.is5GHz()).thenReturn(false);
-        when(wifiInfo.getRssi()).thenReturn(-70);
-        when(wifiInfo.getNetworkId()).thenReturn(WifiConfiguration.INVALID_NETWORK_ID);
-        when(wifiInfo.getBSSID()).thenReturn(null);
-        when(wifiInfo.getNetworkId()).thenReturn(-1);
-        return wifiInfo;
-    }
-
-    WifiConfigManager getWifiConfigManager() {
-        WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
-        wifiConfigManager.mThresholdSaturatedRssi24 = new AtomicInteger(
-                WifiQualifiedNetworkSelector.RSSI_SATURATION_2G_BAND);
-        wifiConfigManager.mBandAward5Ghz = new AtomicInteger(
-                WifiQualifiedNetworkSelector.BAND_AWARD_5GHz);
-        wifiConfigManager.mCurrentNetworkBoost = new AtomicInteger(
-                WifiQualifiedNetworkSelector.SAME_NETWORK_AWARD);
-        wifiConfigManager.mThresholdQualifiedRssi5 = new AtomicInteger(
-                WifiQualifiedNetworkSelector.QUALIFIED_RSSI_5G_BAND);
-        wifiConfigManager.mThresholdMinimumRssi24 = new AtomicInteger(
-                WifiQualifiedNetworkSelector.MINIMUM_2G_ACCEPT_RSSI);
-        wifiConfigManager.mThresholdMinimumRssi5 = new AtomicInteger(
-                WifiQualifiedNetworkSelector.MINIMUM_5G_ACCEPT_RSSI);
-
-        when(wifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        return wifiConfigManager;
-    }
-
-    /**
-     * This API is used to generate multiple simulated saved configurations used for test
-     *
-     * @param ssid     array of SSID of saved configuration
-     * @param security array  of securities of  saved configuration
-     * @return generated new array of configurations based on input
-     */
-    private WifiConfiguration[] generateWifiConfigurations(String[] ssid, int[] security) {
-        if (ssid == null || security == null || ssid.length != security.length
-                || ssid.length == 0) {
-            return null;
-        }
-
-        WifiConfiguration[] configs = new WifiConfiguration[ssid.length];
-        for (int index = 0; index < ssid.length; index++) {
-            configs[index] = generateWifiConfig(index, 0, ssid[index], false, true, null, null,
-                    security[index]);
-        }
-
-        return configs;
-    }
-
-    /**
-     * set configuration to a passpoint configuration
-     *
-     * @param config The configuration need to be set as a passipoint configuration
-     */
-    private void setConfigPasspoint(WifiConfiguration config) {
-        config.FQDN = "android.qns.unitTest";
-        config.providerFriendlyName = "android.qns.unitTest";
-        WifiEnterpriseConfig enterpriseConfig = mock(WifiEnterpriseConfig.class);
-        when(enterpriseConfig.getEapMethod()).thenReturn(WifiEnterpriseConfig.Eap.PEAP);
-
-    }
-
-    /**
-     * add the Configurations to WifiConfigManager (WifiConfigureStore can take them out according
-     * to the networkd ID)
-     *
-     * @param configs input configuration need to be added to WifiConfigureStore
-     */
-    private void prepareConfigStore(final WifiConfiguration[] configs) {
-        when(mWifiConfigManager.getWifiConfiguration(anyInt()))
-                .then(new AnswerWithArguments() {
-                    public WifiConfiguration answer(int netId) {
-                        if (netId >= 0 && netId < configs.length) {
-                            return configs[netId];
-                        } else {
-                            return null;
-                        }
-                    }
-                });
-    }
-
-    /**
-     * Link scan results to the saved configurations.
-     *
-     * The shorter of the 2 input params will be used to loop over so the inputs don't
-     * need to be of equal length. If there are more scan details then configs the remaining scan
-     * details will be associated with a NULL config.
-     *
-     * @param configs     saved configurations
-     * @param scanDetails come in scan results
-     */
-    private void scanResultLinkConfiguration(WifiConfiguration[] configs,
-                                             List<ScanDetail> scanDetails) {
-        if (scanDetails.size() <= configs.length) {
-            for (int i = 0; i < scanDetails.size(); i++) {
-                ScanDetail scanDetail = scanDetails.get(i);
-                List<WifiConfiguration> associateWithScanResult = new ArrayList<>();
-                associateWithScanResult.add(configs[i]);
-                when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(eq(scanDetail),
-                        anyBoolean())).thenReturn(associateWithScanResult);
-            }
-        } else {
-            for (int i = 0; i < configs.length; i++) {
-                ScanDetail scanDetail = scanDetails.get(i);
-                List<WifiConfiguration> associateWithScanResult = new ArrayList<>();
-                associateWithScanResult.add(configs[i]);
-                when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(eq(scanDetail),
-                        anyBoolean())).thenReturn(associateWithScanResult);
-            }
-
-            // associated the remaining scan details with a NULL config.
-            for (int i = configs.length; i < scanDetails.size(); i++) {
-                when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(eq(scanDetails.get(i)),
-                        anyBoolean())).thenReturn(null);
-            }
-        }
-    }
-
-    private void configureScoreCache(List<ScanDetail> scanDetails, Integer[] scores,
-            boolean[] meteredHints) {
-        for (int i = 0; i < scanDetails.size(); i++) {
-            ScanDetail scanDetail = scanDetails.get(i);
-            Integer score = scores[i];
-            ScanResult scanResult = scanDetail.getScanResult();
-            if (score != null) {
-                when(mScoreCache.isScoredNetwork(scanResult)).thenReturn(true);
-                when(mScoreCache.hasScoreCurve(scanResult)).thenReturn(true);
-                when(mScoreCache.getNetworkScore(eq(scanResult), anyBoolean())).thenReturn(score);
-                when(mScoreCache.getNetworkScore(scanResult)).thenReturn(score);
-            } else {
-                when(mScoreCache.isScoredNetwork(scanResult)).thenReturn(false);
-                when(mScoreCache.hasScoreCurve(scanResult)).thenReturn(false);
-                when(mScoreCache.getNetworkScore(eq(scanResult), anyBoolean())).thenReturn(
-                        WifiNetworkScoreCache.INVALID_NETWORK_SCORE);
-                when(mScoreCache.getNetworkScore(scanResult)).thenReturn(
-                        WifiNetworkScoreCache.INVALID_NETWORK_SCORE);
-            }
-            when(mScoreCache.getMeteredHint(scanResult)).thenReturn(meteredHints[i]);
-        }
-    }
-
-    /**
-     * verify whether the chosen configuration matched with the expected chosen scan result
-     *
-     * @param chosenScanResult the expected chosen scan result
-     * @param candidate        the chosen configuration
-     */
-    private void verifySelectedResult(ScanResult chosenScanResult, WifiConfiguration candidate) {
-        ScanResult candidateScan = candidate.getNetworkSelectionStatus().getCandidate();
-        assertEquals("choose the wrong SSID", chosenScanResult.SSID, candidate.SSID);
-        assertEquals("choose the wrong BSSID", chosenScanResult.BSSID, candidateScan.BSSID);
-    }
-
-    // QNS test under disconnected State
-
-    /**
-     * Case #1    choose 2GHz stronger RSSI test
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network
-     * Both network are enabled, encrypted and at 2.4 GHz
-     * test1 is with RSSI -70 test2 is with RSSI -60
-     *
-     * Expected behavior: test2 is chosen
-     */
-    @Test
-    public void chooseNetworkDisconnected2GHighestRssi() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2417};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-70, -60};
-        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(scanDetails.size() - 1).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #2    choose 5GHz Stronger RSSI Test
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network
-     * Both network are enabled, encrypted and at 5 GHz
-     * test1 is with RSSI -70 test2 is with RSSI -60
-     *
-     * Expected behavior: test2 is chosen
-     */
-    @Test
-    public void chooseNetworkDisconnected5GHighestRssi() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5180, 5610};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-70, -60};
-        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(scanDetails.size() - 1).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #3    5GHz over 2GHz bonus Test
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -60
-     * test2 is @ 5Ghz with RSSI -65
-     *
-     * Expected behavior: test2 is chosen due to 5GHz bonus
-     */
-    @Test
-    public void chooseNetworkDisconnect5GOver2GTest() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-60, -65};
-        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(scanDetails.size() - 1).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #4    2GHz over 5GHz dur to 5GHz signal too weak test
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -60
-     * test2 is @ 5Ghz with RSSI -75
-     *
-     * Expected behavior: test1 is chosen due to 5GHz signal is too weak (5GHz bonus can not
-     * compensate)
-     */
-    @Test
-    public void chooseNetworkDisconnect2GOver5GTest() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-60, -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(0).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #5    2GHz signal Saturation test
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -50
-     * test2 is @ 5Ghz with RSSI -65
-     *
-     * Expected behavior: test2 is chosen. Although the RSSI delta here is 15 too, because 2GHz RSSI
-     * saturates at -60, the real RSSI delta is only 5, which is less than 5GHz bonus
-     */
-    @Test
-    public void chooseNetworkDisconnect2GRssiSaturationTest() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-50, -65};
-        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(scanDetails.size() - 1).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #6    Minimum RSSI test
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -86
-     * test2 is @ 5Ghz with RSSI -83
-     *
-     * Expected behavior: no QNS is made because both network are below the minimum threshold, null
-     */
-    @Test
-    public void chooseNetworkMinimumRssiTest() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {WifiQualifiedNetworkSelector.MINIMUM_2G_ACCEPT_RSSI - 1,
-                WifiQualifiedNetworkSelector.MINIMUM_5G_ACCEPT_RSSI - 1};
-        int[] security = {SECURITY_EAP, 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);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        assertEquals("choose the wrong SSID", null, candidate);
-    }
-
-    /**
-     * Case #7    encrypted network over passpoint network
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1 is secured network, test2 are passpoint network
-     * Both network are enabled and at 2.4 GHz. Both have RSSI of -70
-     *
-     * Expected behavior: test1 is chosen since secured network has higher priority than passpoint
-     * network
-     */
-    @Test
-    public void chooseNetworkSecurityOverPassPoint() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
-        int[] levels = {-70, -70};
-        int[] security = {SECURITY_EAP, SECURITY_NONE};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
-        setConfigPasspoint(savedConfigs[1]);
-        prepareConfigStore(savedConfigs);
-
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #8    passpoint network over open network
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1 is passpoint network, test2 is open network
-     * Both network are enabled and at 2.4 GHz. Both have RSSI of -70
-     *
-     * Expected behavior: test1 is chosen since passpoint network has higher priority than open
-     * network
-     */
-    @Test
-    public void chooseNetworkPasspointOverOpen() {
-        String[] ssids = {"\"test1\"", "\"test2\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f8", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-70, -70};
-        int[] security = {SECURITY_NONE, SECURITY_NONE};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
-        setConfigPasspoint(savedConfigs[0]);
-        prepareConfigStore(savedConfigs);
-
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #9    secure network over open network
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * Two networks test1 is secure network, test2 is open network
-     * Both network are enabled and at 2.4 GHz. Both have RSSI of -70
-     *
-     * Expected behavior: test1 is chosen since secured network has higher priority than open
-     * network
-     */
-    @Test
-    public void chooseNetworkSecureOverOpen() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-70, -70};
-        int[] security = {SECURITY_PSK, SECURITY_NONE};
-
-        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();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #10    first time user select a network
-     *
-     * In this test. we simulate following scenario
-     * There are three saved networks: test1, test2 and test3. Now user select the network test3
-     * check test3 has been saved in test1's and test2's ConnectChoice
-     *
-     * Expected behavior: test1's and test2's ConnectChoice should be test3, test3's ConnectChoice
-     * should be null
-     */
-    @Test
-    public void userSelectsNetworkForFirstTime() {
-        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
-        int[] security = {SECURITY_PSK, SECURITY_PSK, SECURITY_NONE};
-
-        final WifiConfiguration[] configs = generateWifiConfigurations(ssids, security);
-        prepareConfigStore(configs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(configs));
-        for (WifiConfiguration network : configs) {
-            WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
-            status.setSeenInLastQualifiedNetworkSelection(true);
-        }
-
-        mWifiQualifiedNetworkSelector.userSelectNetwork(configs.length - 1, true);
-        String key = configs[configs.length - 1].configKey();
-        for (int index = 0; index < configs.length; index++) {
-            WifiConfiguration config = configs[index];
-            WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
-            if (index == configs.length - 1) {
-                assertEquals("User selected network should not have prefernce over it", null,
-                        status.getConnectChoice());
-            } else {
-                assertEquals("Wrong user preference", key, status.getConnectChoice());
-            }
-        }
-    }
-
-    /**
-     * Case #11    choose user selected network
-     *
-     * In this test, we simulate following scenario:
-     * WifiStateMachine is under disconnected state
-     * There are three networks: test1, test2, test3 and test3 is the user preference
-     * All three networks are enabled
-     * test1 is @ 2.4GHz with RSSI -50 PSK
-     * test2 is @ 5Ghz with RSSI -65 PSK
-     * test3 is @ 2.4GHz with RSSI -55 open
-     *
-     * Expected behavior: test3 is chosen since it is user selected network. It overcome all other
-     * priorities
-     */
-    @Test
-    public void chooseUserPreferredNetwork() {
-        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
-        int[] security = {SECURITY_PSK, SECURITY_PSK, SECURITY_NONE};
-
-        final WifiConfiguration[] configs = generateWifiConfigurations(ssids, security);
-        prepareConfigStore(configs);
-        for (WifiConfiguration network : configs) {
-            WifiConfiguration.NetworkSelectionStatus status = network.getNetworkSelectionStatus();
-            status.setSeenInLastQualifiedNetworkSelection(true);
-        }
-
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(configs));
-
-        //set user preference
-        mWifiQualifiedNetworkSelector.userSelectNetwork(ssids.length - 1, true);
-        //Generate mocked recent scan results
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
-        int[] frequencies = {2437, 5180, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]", "NONE"};
-        int[] levels = {-50, -65, -55};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        scanResultLinkConfiguration(configs, scanDetails);
-
-        ScanResult chosenScanResult = scanDetails.get(scanDetails.size() - 1).getScanResult();
-        when(mWifiConfigManager.getWifiConfiguration(configs[2].configKey()))
-                .thenReturn(configs[2]);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #12    enable a blacklisted BSSID
-     *
-     * In this test, we simulate following scenario:
-     * For two Aps, BSSIDA and BSSIDB. Disable BSSIDA, then check whether BSSIDA is disabled and
-     * BSSIDB is enabled. Then enable BSSIDA, check whether both BSSIDs are enabled.
-     */
-    @Test
-    public void enableBssidTest() {
-        String bssidA = "6c:f3:7f:ae:8c:f3";
-        String bssidB = "6c:f3:7f:ae:8c:f4";
-        //check by default these two BSSIDs should be enabled
-        assertEquals("bssidA should be enabled by default",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidA), false);
-        assertEquals("bssidB should be enabled by default",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidB), false);
-
-        //disable bssidA 3 times, check whether A is dsiabled and B is still enabled
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssidA, false);
-        assertEquals("bssidA should be disabled",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidA), false);
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssidA, false);
-        assertEquals("bssidA should be disabled",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidA), false);
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssidA, false);
-        assertEquals("bssidA should be disabled",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidA), true);
-        assertEquals("bssidB should still be enabled",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidB), false);
-
-        //re-enable bssidA, check whether A is dsiabled and B is still enabled
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssidA, true);
-        assertEquals("bssidA should be enabled by default",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidA), false);
-        assertEquals("bssidB should be enabled by default",
-                mWifiQualifiedNetworkSelector.isBssidDisabled(bssidB), false);
-
-        //make sure illegal input will not cause crash
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(null, false);
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(null, true);
-    }
-
-    /**
-     * Case #13    do not choose the BSSID has been disabled
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network and found in scan results
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -65
-     * test2 is @ 5Ghz with RSSI -50
-     * test2's BSSID is disabled
-     *
-     * expected return test1 since test2's BSSID has been disabled
-     */
-    @Test
-    public void networkChooseWithOneBssidDisabled() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-65, -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.enableBssidForQualityNetworkSelection(bssids[1], false);
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], false);
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], false);
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #14    re-choose the disabled BSSID after it is re-enabled
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network and found in scan results
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -65
-     * test2 is @ 5Ghz with RSSI -50
-     * test2's BSSID is disabled
-     *
-     * expected return test2 since test2's BSSID has been enabled again
-     */
-    @Test
-    public void networkChooseWithOneBssidReenaabled() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-65, -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.enableBssidForQualityNetworkSelection(bssids[1], false);
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], false);
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], false);
-        //re-enable it
-        mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], true);
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #15    re-choose the disabled BSSID after its disability has expired
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network and found in scan results
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -65
-     * test2 is @ 5Ghz with RSSI -50
-     * test2's BSSID is disabled
-     *
-     * expected return test2 since test2's BSSID has been enabled again
-     */
-    @Test
-    public void networkChooseWithOneBssidDisableExpire() {
-        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "6c:f3:7f:ae:8c:f5"};
-        int[] frequencies = {2437, 5180, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]",
-                "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-65, -50, -60};
-        int[] security = {SECURITY_PSK, 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();
-
-        for (int index = 0; index < WifiQualifiedNetworkSelector.BSSID_BLACKLIST_THRESHOLD;
-                index++) {
-            mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[1], false);
-            mWifiQualifiedNetworkSelector.enableBssidForQualityNetworkSelection(bssids[2], false);
-        }
-
-        //re-enable it
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime()
-                + WifiQualifiedNetworkSelector.BSSID_BLACKLIST_EXPIRE_TIME);
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-    /**
-     * Case #16    do not choose the SSID has been disabled
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under disconnected state
-     * Two networks test1, test2 are secured network and found in scan results
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -65
-     * test2 is @ 5Ghz with RSSI -50
-     * test2's SSID is disabled
-     *
-     * expected return test1 since test2's SSID has been disabled
-     */
-    @Test
-    public void networkChooseWithOneSsidDisabled() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-65, -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();
-
-        when(mWifiConfigManager.tryEnableQualifiedNetwork(anyInt())).thenReturn(true);
-        savedConfigs[1].getNetworkSelectionStatus().setNetworkSelectionStatus(
-                WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #17    do not make QNS is link is bouncing now
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under disconnected state and currently is under link bouncing
-     * Two networks test1, test2 are secured network and found in scan results
-     * Both network are enabled
-     * test1 is @ 2GHz with RSSI -50
-     * test2 is @ 5Ghz with RSSI -50
-     *
-     * expected return null
-     */
-    @Test
-    public void noQNSWhenLinkBouncingDisconnected() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {WifiQualifiedNetworkSelector.MINIMUM_2G_ACCEPT_RSSI - 1,
-                WifiQualifiedNetworkSelector.MINIMUM_5G_ACCEPT_RSSI - 1};
-        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);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, true, false, true, false);
-
-        assertEquals("choose the wrong network", null, candidate);
-    }
-
-    /**
-     * Case #18    QNS with very short gap
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under disconnected state
-     * If last QNS is made in less than MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL, we
-     * still should make new QNS since it is disconnected now
-     *
-     * expect return test1 because of band bonus
-     */
-    @Test
-    public void networkSelectionInShortGap() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-50, -65};
-        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);
-        //first QNS
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        //immediately second QNS
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    //Unit test for Connected State
-
-    /**
-     * Case #19    no QNS with very short gap when connected
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and test2 is connected
-     * When WifiStateMachine is already in connected state, if last QNS is made in less than
-     * MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL, no new QNS should be made
-     *
-     * expect return NULL
-     */
-    @Test
-    public void noNetworkSelectionDueToShortGap() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-50, -65};
-        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);
-        //first QNS
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        //immediately second QNS
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-        assertEquals("choose the wrong BSSID", null, candidate);
-    }
-
-    /**
-     * Case #20    force QNS with very short gap under connection
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and test2 is connected
-     * When WifiStateMachine is already in connected state, if last QNS is made in less than
-     * MINIMUM_QUALIFIED_NETWORK_SELECTION_INTERVAL, no new QNS should be made. However, if we force
-     * to make new QNS, QNS still will be made
-     *
-     * expect return test2 since it is the current connected one (bonus)
-     */
-    @Test
-    public void forceNetworkSelectionInShortGap() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-50, -65};
-        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);
-        //first QNS
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(false);
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        //immediately second QNS
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(true,
-                false, scanDetails, false, true, false, false);
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #21    no QNS when connected and user do not allow switch when connected
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and test2 is connected
-     * if user does not allow switch network when connected, do not make new QNS when connected
-     *
-     * expect return NULL
-     */
-    @Test
-    public void noNewNetworkSelectionDuetoUserDisableSwitchWhenConnected() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-50, -65};
-        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);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        assertEquals("choose the wrong BSSID", null, candidate);
-        assertEquals("Should receive zero filteredScanDetails", 0,
-                mWifiQualifiedNetworkSelector.getFilteredScanDetails().size());
-    }
-
-    /**
-     * Case #22    no new QNS if current network is qualified already
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and test2 is connected
-     * If current connected network is Qualified already, do not make new QNS
-     * simulated current connected network as:
-     * 5GHz, RSSI = WifiQualifiedNetworkSelector.QUALIFIED_RSSI_5G_BAND, secured
-     *
-     * expected return null
-     */
-    @Test
-    public void noNewQNSCurrentNetworkQualified() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-65, WifiQualifiedNetworkSelector.QUALIFIED_RSSI_5G_BAND};
-        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);
-
-        //first time, connect to test2 due to 5GHz bonus
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        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);
-
-        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);
-        assertEquals("choose the wrong BSSID", null, candidate);
-    }
-
-    /**
-     * Case #23    No new QNS when link bouncing when connected
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and test2 is connected
-     * no new QNS when link is bouncing
-     *
-     * expected return null
-     */
-    @Test
-    public void noNewQNSLinkBouncing() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 5180};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-70, -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);
-
-        //first connect to test2 due to 5GHz bonus
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        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(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, true, true, false, false);
-        assertEquals("choose the wrong BSSID", null, candidate);
-    }
-
-    /**
-     * Case #24    Qualified network need to be on 5GHz
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and connected to test2
-     * if current connected network is not 5GHz, then it is not qualified. We should make new QNS
-     *
-     * expected result: return test1
-     */
-    @Test
-    public void currentNetworkNotQualifiedDueToBandMismatch() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-50, -65};
-        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);
-        when(mWifiInfo.getNetworkId()).thenReturn(0);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
-        when(mWifiInfo.is24GHz()).thenReturn(true);
-        //connect to config2 first
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-
-        when(mClock.elapsedRealtime()).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);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #25    Qualified network need to be secured
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connects to test2
-     * if current connected network is open network, then it is not qualified. We should make new
-     * QNS
-     *
-     * expected result: return test1 since test1 has higher RSSI
-     */
-    @Test
-    public void currentNetworkNotQualifiedDueToOpenNetwork() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5400, 5400};
-        String[] caps = {"[ESS]", "[ESS]"};
-        int[] levels = {-70, -65};
-        int[] security = {SECURITY_NONE, SECURITY_NONE};
-
-        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);
-
-        //first connect to test2 because of RSSI
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        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(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        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);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #26    ephemeral network can not be qualified network
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connected to test2
-     * if current connected network is ephemeral network, then it is not qualified. We should make
-     * new QNS
-     *
-     * expected result: return test1 (since test2 is ephemeral)
-     */
-    @Test
-    public void currentNetworkNotQualifiedDueToEphemeral() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5200, 5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-100, -50};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
-        savedConfigs[1].ephemeral = true;
-        prepareConfigStore(savedConfigs);
-
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        //first connect to test2 since test1's RSSI is negligible
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(false);
-
-        levels[0] = -70;
-        scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
-
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #27    low signal network can not be Qualified network (5GHz)
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connected to test2
-     * if current connected network's rssi is too low, then it is not qualified. We should
-     * make new QNS
-     *
-     * expected result: return test1
-     */
-    @Test
-    public void currentNetworkNotQualifiedDueToLow5GRssi() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5200, 5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-80, WifiQualifiedNetworkSelector.QUALIFIED_RSSI_5G_BAND - 1};
-        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);
-
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.getRssi()).thenReturn(levels[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(false);
-        when(mWifiInfo.is5GHz()).thenReturn(true);
-
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        when(mClock.elapsedRealtime()).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);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #28    low signal network can not be Qualified network (2.4GHz)
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connected to test2
-     * if current connected network's rssi is too low, then it is not qualified. We should
-     * make new QNS
-     *
-     * expected result: return test1
-     */
-    @Test
-    public void currentNetworkNotQualifiedDueToLow2GRssi() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-100, WifiQualifiedNetworkSelector.QUALIFIED_RSSI_24G_BAND - 1};
-        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);
-
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.getRssi()).thenReturn(levels[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(false);
-        when(mWifiInfo.is5GHz()).thenReturn(true);
-
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        levels[0] = -60;
-        scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        when(mClock.elapsedRealtime()).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);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #29    Choose current network due to current network bonus
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connected to test2
-     * To connect to a network which is not linked to current connected network, unless this network
-     * is more than 10 db higher than current network, we should not switch. So although test2 has a
-     * lower signal, we still choose test2
-     *
-     * expected result: return test2
-     */
-    @Test
-    public void currentNetworkStayDueToSameNetworkBonus() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-100, -80};
-        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);
-
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(true);
-
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        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);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #30    choose another network due to current network's signal is too low
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connected to test2
-     * To connect to a network which is not linked to current connected network, if this network
-     * is more than 10 db higher than current network, we should switch
-     *
-     * expected new result: return test1
-     */
-    @Test
-    public void switchNetworkStayDueToCurrentNetworkRssiLow() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-100, -80};
-        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);
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(true);
-
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        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);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #31    Choose current BSSID due to current BSSID bonus
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connected to test2
-     * Linked network will be treated as same network. To connect to a network which is linked to
-     * current connected network, unless this network is more than 6 db higher than current network,
-     * we should not switch AP and stick to current BSSID
-     *
-     * expected result: return test2
-     */
-    @Test
-    public void currentBssidStayDueToSameBSSIDBonus() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-100, -80};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
-        //link two configuration
-        savedConfigs[0].linkedConfigurations = new HashMap<String, Integer>();
-        savedConfigs[1].linkedConfigurations = new HashMap<String, Integer>();
-        savedConfigs[0].linkedConfigurations.put(savedConfigs[1].configKey(), 1);
-        savedConfigs[1].linkedConfigurations.put(savedConfigs[0].configKey(), 1);
-        prepareConfigStore(savedConfigs);
-
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(true);
-
-        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);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #32    Choose another BSSID due to current BSSID's rssi is too low
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and current connected to test2
-     * Linked network will be treated as same network. To connect to a network which is linked to
-     * current connected network, unless this network is more than 6 db higher than current network,
-     * we should not switch AP and stick to current BSSID
-     *
-     * expected result: return test2
-     */
-    @Test
-    public void swithBssidDueToLowRssi() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS][ESS]"};
-        int[] levels = {-100, -80};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
-        //link two configuration
-        savedConfigs[0].linkedConfigurations = new HashMap<String, Integer>();
-        savedConfigs[1].linkedConfigurations = new HashMap<String, Integer>();
-        savedConfigs[0].linkedConfigurations.put(savedConfigs[1].configKey(), 1);
-        savedConfigs[1].linkedConfigurations.put(savedConfigs[0].configKey(), 1);
-        prepareConfigStore(savedConfigs);
-
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiInfo.is24GHz()).thenReturn(true);
-
-        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);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #33  Choose an ephemeral network with a good score because no saved networks
-     *           are available.
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is not connected to any network.
-     * selectQualifiedNetwork() is called with 2 scan results, test1 and test2.
-     * test1 is an enterprise network w/o a score.
-     * test2 is an open network with a good score. Additionally it's a metered network.
-     * isUntrustedConnectionsAllowed is set to true.
-     *
-     * expected result: return test2 with meteredHint set to True.
-     */
-    @Test
-    public void selectQualifiedNetworkChoosesEphemeral() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5200, 5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
-        int[] levels = {-70, -70};
-        Integer[] scores = {null, 120};
-        boolean[] meteredHints = {false, true};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        configureScoreCache(scanDetails, scores, meteredHints);
-
-        // No saved networks.
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(any(ScanDetail.class),
-                anyBoolean())).thenReturn(null);
-
-        WifiConfiguration unTrustedNetworkCandidate = mock(WifiConfiguration.class);
-        // Setup the config as an invalid candidate. This is done to workaround a Mockito issue.
-        // Basically Mockito is unable to mock package-private methods in classes loaded from a
-        // different Jar (like all of the framework code) which results in the actual saveNetwork()
-        // method being invoked in this case. Because the config is invalid it quickly returns.
-        unTrustedNetworkCandidate.SSID = null;
-        unTrustedNetworkCandidate.networkId = WifiConfiguration.INVALID_NETWORK_ID;
-        ScanResult untrustedScanResult = scanDetails.get(1).getScanResult();
-        when(mWifiConfigManager
-                .wifiConfigurationFromScanResult(untrustedScanResult))
-                .thenReturn(unTrustedNetworkCandidate);
-
-        WifiConfiguration.NetworkSelectionStatus selectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(unTrustedNetworkCandidate.getNetworkSelectionStatus()).thenReturn(selectionStatus);
-        when(selectionStatus.getCandidate()).thenReturn(untrustedScanResult);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-        verify(selectionStatus).setCandidate(untrustedScanResult);
-        assertSame(unTrustedNetworkCandidate, candidate);
-        assertEquals(meteredHints[1], candidate.meteredHint);
-    }
-
-    /**
-     * Case #34    Test Filtering of potential candidate scanDetails (Untrusted allowed)
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * test1 is @ 2GHz with RSSI -60
-     * test2 is @ 5Ghz with RSSI -86, (below minimum threshold)
-     * test3 is @ 5Ghz with RSSI -50, however it has no associated saved config
-     * test4 is @ 2Ghz with RSSI -62, no associated config, but is Ephemeral
-     *
-     * Expected behavior: test1 is chosen due to 5GHz signal is too weak (5GHz bonus can not
-     * compensate).
-     * test1 & test4's scanDetails are returned by 'getFilteredScanDetail()'
-     */
-    @Test
-    public void testGetFilteredScanDetailsReturnsOnlyConsideredScanDetails_untrustedAllowed() {
-        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\"", "\"test4\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "de:ad:ba:b1:e5:55",
-                "c0:ff:ee:ee:e3:ee"};
-        int[] frequencies = {2437, 5180, 5180, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]",
-                "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {-60, -86, -50, -62};
-        int[] security = {SECURITY_PSK, SECURITY_PSK, SECURITY_PSK, SECURITY_PSK};
-        boolean[] meteredHints = {false, false, false, true};
-        Integer[] scores = {null, null, null, 120};
-
-        //Create all 4 scanDetails
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-
-        //Setup NetworkScoreCache for detecting ephemeral networks ("test4")
-        configureScoreCache(scanDetails, scores, meteredHints);
-        WifiConfiguration unTrustedNetworkCandidate = mock(WifiConfiguration.class);
-        unTrustedNetworkCandidate.SSID = null;
-        unTrustedNetworkCandidate.networkId = WifiConfiguration.INVALID_NETWORK_ID;
-        ScanResult untrustedScanResult = scanDetails.get(3).getScanResult();
-        when(mWifiConfigManager
-                .wifiConfigurationFromScanResult(untrustedScanResult))
-                .thenReturn(unTrustedNetworkCandidate);
-        WifiConfiguration.NetworkSelectionStatus selectionStatus =
-                        mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(unTrustedNetworkCandidate.getNetworkSelectionStatus()).thenReturn(selectionStatus);
-
-        //Set up associated configs for test1 & test2
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(
-                Arrays.copyOfRange(ssids, 0, 2), Arrays.copyOfRange(security, 0, 2));
-        prepareConfigStore(savedConfigs);
-        List<ScanDetail> savedScanDetails = new ArrayList<ScanDetail>(scanDetails.subList(0, 2));
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-        scanResultLinkConfiguration(savedConfigs, savedScanDetails);
-
-        //Force mock ConfigManager to return null (and not an empty list) for "test3" & "test4"
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(eq(scanDetails.get(2)),
-                anyBoolean())).thenReturn(null);
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(eq(scanDetails.get(3)),
-                anyBoolean())).thenReturn(null);
-
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-
-        verifySelectedResult(chosenScanResult, candidate);
-        //Verify two scanDetails returned in the filteredScanDetails
-        assertEquals(2, mWifiQualifiedNetworkSelector.getFilteredScanDetails().size());
-        assertEquals(mWifiQualifiedNetworkSelector.getFilteredScanDetails().get(0).first.toString(),
-                scanDetails.get(0).toString());
-        assertEquals(mWifiQualifiedNetworkSelector.getFilteredScanDetails().get(1).first.toString(),
-                scanDetails.get(3).toString());
-    }
-
-
-    /**
-     * Case #35    Test Filtering of potential candidate scanDetails (Untrusted disallowed)
-     *
-     * In this test. we simulate following scenario
-     * WifiStateMachine is under disconnected state
-     * test1 is @ 2GHz with RSSI -60
-     * test2 is @ 5Ghz with RSSI -86, (below minimum threshold)
-     * test3 is @ 5Ghz with RSSI -50, however it has no associated saved config
-     * test4 is @ 2Ghz with RSSI -62, no associated config, but is Ephemeral
-     *
-     * Expected behavior: test1 is chosen due to 5GHz signal is too weak (5GHz bonus can not
-     * compensate).
-     * test1 & test4's scanDetails are returned by 'getFilteredScanDetail()'
-     */
-    @Test
-    public void testGetFilteredScanDetailsReturnsOnlyConsideredScanDetails_untrustedDisallowed() {
-        String[] ssids = {"\"test1\"", "\"test2\"", "\"test3\"", "\"test4\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "de:ad:ba:b1:e5:55",
-                "c0:ff:ee:ee:e3:ee"};
-        int[] frequencies = {2437, 5180, 5180, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]",
-                "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {-60, -86, -50, -62};
-        int[] security = {SECURITY_PSK, SECURITY_PSK, SECURITY_PSK, SECURITY_PSK};
-        boolean[] meteredHints = {false, false, false, true};
-        Integer[] scores = {null, null, null, 120};
-
-        //Create all 4 scanDetails
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-
-        //Setup NetworkScoreCache for detecting ephemeral networks ("test4")
-        configureScoreCache(scanDetails, scores, meteredHints);
-        WifiConfiguration unTrustedNetworkCandidate = mock(WifiConfiguration.class);
-        unTrustedNetworkCandidate.SSID = null;
-        unTrustedNetworkCandidate.networkId = WifiConfiguration.INVALID_NETWORK_ID;
-        ScanResult untrustedScanResult = scanDetails.get(3).getScanResult();
-        when(mWifiConfigManager
-                .wifiConfigurationFromScanResult(untrustedScanResult))
-                .thenReturn(unTrustedNetworkCandidate);
-        WifiConfiguration.NetworkSelectionStatus selectionStatus =
-                        mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(unTrustedNetworkCandidate.getNetworkSelectionStatus()).thenReturn(selectionStatus);
-
-        //Set up associated configs for test1 & test2
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(
-                Arrays.copyOfRange(ssids, 0, 2), Arrays.copyOfRange(security, 0, 2));
-        prepareConfigStore(savedConfigs);
-        List<ScanDetail> savedScanDetails = new ArrayList<ScanDetail>(scanDetails.subList(0, 2));
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-        scanResultLinkConfiguration(savedConfigs, savedScanDetails);
-
-        //Force mock ConfigManager to return null (and not an empty list) for "test3" & "test4"
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(eq(scanDetails.get(2)),
-                anyBoolean())).thenReturn(null);
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(eq(scanDetails.get(3)),
-                anyBoolean())).thenReturn(null);
-
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                false /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-
-        verifySelectedResult(chosenScanResult, candidate);
-        //Verify two scanDetails returned in the filteredScanDetails
-        assertEquals(1, mWifiQualifiedNetworkSelector.getFilteredScanDetails().size());
-        assertEquals(mWifiQualifiedNetworkSelector.getFilteredScanDetails().get(0).first.toString(),
-                scanDetails.get(0).toString());
-    }
-
-    /**
-     * Case #36  Ignore an ephemeral network if it was previously deleted.
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is not connected to any network.
-     * selectQualifiedNetwork() is called with 2 scan results, test1 and test2.
-     * test1 is an open network with a low score. Additionally it's a metered network.
-     * test2 is an open network with a good score but was previously deleted.
-     * isUntrustedConnectionsAllowed is set to true.
-     *
-     * expected result: return test1 with meteredHint set to True.
-     */
-    @Test
-    public void selectQualifiedNetworkDoesNotChooseDeletedEphemeral() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5200, 5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
-        int[] levels = {-70, -70};
-        Integer[] scores = {20, 120};
-        boolean[] meteredHints = {true, false};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        configureScoreCache(scanDetails, scores, meteredHints);
-
-        // No saved networks.
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(any(ScanDetail.class),
-                anyBoolean())).thenReturn(null);
-
-        WifiConfiguration unTrustedNetworkCandidate = mock(WifiConfiguration.class);
-        // Setup the config as an invalid candidate. This is done to workaround a Mockito issue.
-        // Basically Mockito is unable to mock package-private methods in classes loaded from a
-        // different Jar (like all of the framework code) which results in the actual saveNetwork()
-        // method being invoked in this case. Because the config is invalid it quickly returns.
-        unTrustedNetworkCandidate.SSID = null;
-        unTrustedNetworkCandidate.networkId = WifiConfiguration.INVALID_NETWORK_ID;
-        ScanResult untrustedScanResult = scanDetails.get(0).getScanResult();
-        when(mWifiConfigManager
-                .wifiConfigurationFromScanResult(untrustedScanResult))
-                .thenReturn(unTrustedNetworkCandidate);
-
-        // The second scan result is for an ephemeral network which was previously deleted
-        when(mWifiConfigManager
-                .wasEphemeralNetworkDeleted(ScanDetailUtil.createQuotedSSID(
-                        scanDetails.get(0).getScanResult().SSID)))
-                .thenReturn(false);
-        when(mWifiConfigManager
-                .wasEphemeralNetworkDeleted(ScanDetailUtil.createQuotedSSID(
-                        scanDetails.get(1).getScanResult().SSID)))
-                .thenReturn(true);
-
-        WifiConfiguration.NetworkSelectionStatus selectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(unTrustedNetworkCandidate.getNetworkSelectionStatus()).thenReturn(selectionStatus);
-        when(selectionStatus.getCandidate()).thenReturn(untrustedScanResult);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-        verify(selectionStatus).setCandidate(untrustedScanResult);
-        assertSame(candidate, unTrustedNetworkCandidate);
-        assertEquals(meteredHints[0], candidate.meteredHint);
-    }
-
-    /**
-     * Case #37  Choose the saved config that doesn't qualify for external scoring.
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is not connected to any network.
-     * selectQualifiedNetwork() is called with 2 scan results, test1 and test2.
-     * test1 is a saved network.
-     * test2 is a saved network with useExternalScores set to true and a very high score.
-     *
-     * expected result: return test1 because saved networks that don't request external scoring
-     *                  have a higher priority.
-     */
-    @Test
-    public void selectQualifiedNetworkPrefersSavedWithoutExternalScores() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5200, 5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-        int[] levels = {-70, -70};
-        Integer[] scores = {null, 120};
-        boolean[] meteredHints = {false, true};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        configureScoreCache(scanDetails, scores, meteredHints);
-
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(DEFAULT_SSIDS, security);
-        savedConfigs[1].useExternalScores = true; // test2 is set to use external scores.
-        prepareConfigStore(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(savedConfigs));
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                false /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-        verifySelectedResult(scanDetails.get(0).getScanResult(), candidate);
-        assertSame(candidate, savedConfigs[0]);
-    }
-
-    /**
-     * Case #38  Choose the saved config that does qualify for external scoring when other saved
-     *           networks are not available.
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is not connected to any network.
-     * selectQualifiedNetwork() is called with 2 scan results, test1 and test2.
-     * test1 is a saved network with useExternalScores set to true and a very high score.
-     * test2 is a saved network but not in range (not included in the scan results).
-     *
-     * expected result: return test1 because there are no better saved networks within range.
-     */
-    @Test
-    public void selectQualifiedNetworkSelectsSavedWithExternalScores() {
-        String[] ssids = {"\"test1\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3"};
-        int[] frequencies = {5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-        int[] levels = {-70};
-        Integer[] scores = {120};
-        boolean[] meteredHints = {false};
-
-        // Scan details only contains 1 ssid, test1.
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        configureScoreCache(scanDetails, scores, meteredHints);
-
-        // The saved config contains 2 ssids, test1 & test2.
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(DEFAULT_SSIDS, security);
-        savedConfigs[0].useExternalScores = true; // test1 is set to use external scores.
-        prepareConfigStore(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(savedConfigs));
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                false /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-        verifySelectedResult(scanDetails.get(0).getScanResult(), candidate);
-        assertSame(candidate, savedConfigs[0]);
-    }
-
-    /**
-     * Case #39  Choose the saved config that does qualify for external scoring over the
-     *           untrusted network with the same score.
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is not connected to any network.
-     * selectQualifiedNetwork() is called with 2 scan results, test1 and test2.
-     * test1 is a saved network with useExternalScores set to true and the same score as test1.
-     * test2 is NOT saved network but in range with a good external score.
-     *
-     * expected result: return test1 because the tie goes to the saved network.
-     */
-    @Test
-    public void selectQualifiedNetworkPrefersSavedWithExternalScoresOverUntrusted() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5200, 5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-        int[] levels = {-70, -70};
-        Integer[] scores = {120, 120};
-        boolean[] meteredHints = {false, true};
-
-        // Both networks are in the scan results.
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        configureScoreCache(scanDetails, scores, meteredHints);
-
-        // Set up the associated configs only for test1
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(
-                Arrays.copyOfRange(ssids, 0, 1), Arrays.copyOfRange(security, 0, 1));
-        savedConfigs[0].useExternalScores = true; // test1 is set to use external scores.
-        prepareConfigStore(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(savedConfigs));
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-        WifiConfiguration unTrustedNetworkCandidate = mock(WifiConfiguration.class);
-        when(mWifiConfigManager
-                .wifiConfigurationFromScanResult(scanDetails.get(1).getScanResult()))
-                .thenReturn(unTrustedNetworkCandidate);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-        verifySelectedResult(scanDetails.get(0).getScanResult(), candidate);
-        assertSame(candidate, savedConfigs[0]);
-    }
-
-    /**
-     * Case #40  Choose the ephemeral config over the saved config that does qualify for external
-     *           scoring because the untrusted network has a higher score.
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is not connected to any network.
-     * selectQualifiedNetwork() is called with 2 scan results, test1 and test2.
-     * test1 is a saved network with useExternalScores set to true and a low score.
-     * test2 is NOT saved network but in range with a good external score.
-     *
-     * expected result: return test2 because it has a better score.
-     */
-    @Test
-    public void selectQualifiedNetworkPrefersUntrustedOverScoredSaved() {
-        String[] ssids = DEFAULT_SSIDS;
-        String[] bssids = DEFAULT_BSSIDS;
-        int[] frequencies = {5200, 5200};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[ESS]"};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-        int[] levels = {-70, -70};
-        Integer[] scores = {10, 120};
-        boolean[] meteredHints = {false, true};
-
-        // Both networks are in the scan results.
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        configureScoreCache(scanDetails, scores, meteredHints);
-
-        // Set up the associated configs only for test1
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(
-                Arrays.copyOfRange(ssids, 0, 1), Arrays.copyOfRange(security, 0, 1));
-        savedConfigs[0].useExternalScores = true; // test1 is set to use external scores.
-        prepareConfigStore(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(savedConfigs));
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-        WifiConfiguration unTrustedNetworkCandidate = mock(WifiConfiguration.class);
-        unTrustedNetworkCandidate.SSID = null;
-        unTrustedNetworkCandidate.networkId = WifiConfiguration.INVALID_NETWORK_ID;
-        ScanResult untrustedScanResult = scanDetails.get(1).getScanResult();
-        when(mWifiConfigManager
-                .wifiConfigurationFromScanResult(untrustedScanResult))
-                .thenReturn(unTrustedNetworkCandidate);
-        WifiConfiguration.NetworkSelectionStatus selectionStatus =
-                mock(WifiConfiguration.NetworkSelectionStatus.class);
-        when(unTrustedNetworkCandidate.getNetworkSelectionStatus()).thenReturn(selectionStatus);
-        when(selectionStatus.getCandidate()).thenReturn(untrustedScanResult);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(
-                false /* forceSelectNetwork */,
-                true /* isUntrustedConnectionsAllowed */,
-                scanDetails,
-                false, /* isLinkDebouncing */
-                false, /* isConnected */
-                true, /* isDisconnected */
-                false /* isSupplicantTransient */);
-        verify(selectionStatus).setCandidate(untrustedScanResult);
-        assertSame(unTrustedNetworkCandidate, candidate);
-    }
-
-    /**
-     * Case #41 Ensure the ExternalScoreEvaluator correctly selects the untrusted network.
-     *
-     * In this test. we simulate following scenario:
-     * The ExternalScoreEvaluator is asked to evaluate 1 untrusted network and 1 saved network.
-     * The untrusted network has the higher score.
-     *
-     * expected result: The untrusted network is determined to be the best network.
-     */
-    @Test
-    public void externalScoreEvaluator_untrustedIsBest() {
-        WifiQualifiedNetworkSelector.ExternalScoreEvaluator evaluator =
-                new WifiQualifiedNetworkSelector.ExternalScoreEvaluator(mLocalLog, true);
-        ScanResult untrustedScanResult = new ScanResult();
-        int untrustedScore = 100;
-        evaluator.evalUntrustedCandidate(untrustedScore, untrustedScanResult);
-
-        ScanResult savedScanResult = new ScanResult();
-        int savedScore = 50;
-        WifiConfiguration savedConfig = new WifiConfiguration();
-        evaluator.evalSavedCandidate(savedScore, savedConfig, savedScanResult);
-        assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
-                .BestCandidateType.UNTRUSTED_NETWORK, evaluator.getBestCandidateType());
-        assertEquals(untrustedScore, evaluator.getHighScore());
-        assertSame(untrustedScanResult, evaluator.getScanResultCandidate());
-    }
-
-    /**
-     * Case #42 Ensure the ExternalScoreEvaluator correctly selects the saved network.
-     *
-     * In this test. we simulate following scenario:
-     * The ExternalScoreEvaluator is asked to evaluate 1 untrusted network and 1 saved network.
-     * The saved network has the higher score.
-     *
-     * expected result: The saved network is determined to be the best network.
-     */
-    @Test
-    public void externalScoreEvaluator_savedIsBest() {
-        WifiQualifiedNetworkSelector.ExternalScoreEvaluator evaluator =
-                new WifiQualifiedNetworkSelector.ExternalScoreEvaluator(mLocalLog, true);
-        ScanResult untrustedScanResult = new ScanResult();
-        int untrustedScore = 50;
-        evaluator.evalUntrustedCandidate(untrustedScore, untrustedScanResult);
-
-        ScanResult savedScanResult = new ScanResult();
-        int savedScore = 100;
-        WifiConfiguration savedConfig = new WifiConfiguration();
-        evaluator.evalSavedCandidate(savedScore, savedConfig, savedScanResult);
-        assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
-                .BestCandidateType.SAVED_NETWORK, evaluator.getBestCandidateType());
-        assertEquals(savedScore, evaluator.getHighScore());
-        assertSame(savedScanResult, evaluator.getScanResultCandidate());
-    }
-
-    /**
-     * Case #43 Ensure the ExternalScoreEvaluator correctly selects the saved network if a
-     *          tie occurs.
-     *
-     * In this test. we simulate following scenario:
-     * The ExternalScoreEvaluator is asked to evaluate 1 untrusted network and 1 saved network.
-     * Both networks have the same score.
-     *
-     * expected result: The saved network is determined to be the best network.
-     */
-    @Test
-    public void externalScoreEvaluator_tieScores() {
-        WifiQualifiedNetworkSelector.ExternalScoreEvaluator evaluator =
-                new WifiQualifiedNetworkSelector.ExternalScoreEvaluator(mLocalLog, true);
-        ScanResult untrustedScanResult = new ScanResult();
-        int untrustedScore = 100;
-        evaluator.evalUntrustedCandidate(untrustedScore, untrustedScanResult);
-
-        ScanResult savedScanResult = new ScanResult();
-        int savedScore = 100;
-        WifiConfiguration savedConfig = new WifiConfiguration();
-        evaluator.evalSavedCandidate(savedScore, savedConfig, savedScanResult);
-        assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
-                .BestCandidateType.SAVED_NETWORK, evaluator.getBestCandidateType());
-        assertEquals(savedScore, evaluator.getHighScore());
-        assertSame(savedScanResult, evaluator.getScanResultCandidate());
-    }
-
-    /**
-     * Case #44 Ensure the ExternalScoreEvaluator correctly selects the saved network out of
-     *          multiple options.
-     *
-     * In this test. we simulate following scenario:
-     * The ExternalScoreEvaluator is asked to evaluate 2 untrusted networks and 2 saved networks.
-     * The high scores are equal and the low scores differ.
-     *
-     * expected result: The saved network is determined to be the best network.
-     */
-    @Test
-    public void externalScoreEvaluator_multipleScores() {
-        WifiQualifiedNetworkSelector.ExternalScoreEvaluator evaluator =
-                new WifiQualifiedNetworkSelector.ExternalScoreEvaluator(mLocalLog, true);
-        ScanResult untrustedScanResult = new ScanResult();
-        int untrustedScore = 100;
-        evaluator.evalUntrustedCandidate(untrustedScore, untrustedScanResult);
-        evaluator.evalUntrustedCandidate(80, new ScanResult());
-
-        ScanResult savedScanResult = new ScanResult();
-        int savedScore = 100;
-        WifiConfiguration savedConfig = new WifiConfiguration();
-        evaluator.evalSavedCandidate(savedScore, savedConfig, savedScanResult);
-        evaluator.evalSavedCandidate(90, new WifiConfiguration(), new ScanResult());
-        assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
-                .BestCandidateType.SAVED_NETWORK, evaluator.getBestCandidateType());
-        assertEquals(savedScore, evaluator.getHighScore());
-        assertSame(savedScanResult, evaluator.getScanResultCandidate());
-    }
-
-    /**
-     * Case #45 Ensure the ExternalScoreEvaluator correctly handles NULL score inputs.
-     *
-     * In this test we simulate following scenario:
-     * The ExternalScoreEvaluator is asked to evaluate both types of candidates with NULL scores.
-     *
-     * expected result: No crashes. The best candidate type is returned as NONE.
-     */
-    @Test
-    public void externalScoreEvaluator_nullScores() {
-        WifiQualifiedNetworkSelector.ExternalScoreEvaluator evaluator =
-                new WifiQualifiedNetworkSelector.ExternalScoreEvaluator(mLocalLog, true);
-        evaluator.evalUntrustedCandidate(null, new ScanResult());
-        assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
-                .BestCandidateType.NONE, evaluator.getBestCandidateType());
-        evaluator.evalSavedCandidate(null, new WifiConfiguration(), new ScanResult());
-        assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
-                .BestCandidateType.NONE, evaluator.getBestCandidateType());
-    }
-
-    /**
-     * Case #46   Choose 2.4GHz BSSID with stronger RSSI value over
-     *            5GHz BSSID with weaker RSSI value
-     *
-     * In this test. we simulate following scenario:
-     * Two APs are found in scan results
-     * BSSID1 is @ 5GHz with RSSI -82
-     * BSSID2 is @ 2Ghz with RSSI -72
-     * These two BSSIDs get exactly the same QNS score
-     *
-     * expect BSSID2 to be chosen as it has stronger RSSI value
-     */
-    @Test
-    public void chooseStrongerRssiOver5GHz() {
-        String[] ssids = {"\"test1\"", "\"test1\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {5220, 2437};
-        String[] caps = {"[ESS]", "[ESS]"};
-        int[] levels = {-82, -72};
-        int[] security = {SECURITY_NONE, SECURITY_NONE};
-
-        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();
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #47   Choose the currently connected BSSID after a firmware initiated roaming.
-     *
-     * In this test. we simulate following scenario:
-     * Two APs are found in scan results
-     * BSSID1 is @ 2.4GHz with RSSI -78
-     * BSSID2 is @ 2.4Ghz with RSSI -77
-     * BSSID2 is chosen because of stronger RSSI. Then firmware initiates
-     * a roaming to BSSID1. QNS now selects BSSID1 because of the bonus for currently
-     * connected network even if BSSID 2 has slightly stronger signal strengh.
-     *
-     * expect BSSID2 to be chosen after firmware roaming
-     */
-    @Test
-    public void chooseCurrentlyConnectedBssid() {
-        String[] ssids = {"\"test1\"", "\"test1\""};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {2437, 2437};
-        String[] caps = {"[ESS]", "[ESS]"};
-        int[] levels = {-78, -77};
-        int[] security = {SECURITY_NONE, SECURITY_NONE};
-
-        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);
-
-        // Choose BSSID2 as it has stronger RSSI
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-        verifySelectedResult(chosenScanResult, candidate);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[1]);
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-
-        // Choose BSSID2 as it has stronger RSSI and it is the currently connected BSSID
-        chosenScanResult = scanDetails.get(1).getScanResult();
-        candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(true,
-                false, scanDetails, false, true, false, false);
-        verifySelectedResult(chosenScanResult, candidate);
-
-        // Pretend firmware roamed the device to BSSID1
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
-
-        // Choose BSSID1 as it is the currently connected BSSID even if BSSID2 has slightly
-        // higher RSSI value.
-        chosenScanResult = scanDetails.get(0).getScanResult();
-        candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(true,
-                false, scanDetails, false, true, false, false);
-        verifySelectedResult(chosenScanResult, candidate);
-    }
-
-    /**
-     * Case #48    no new QNS if current network doesn't show up in the
-     *             scan results.
-     *
-     * In this test. we simulate following scenario:
-     * WifiStateMachine is under connected state and 2.4GHz test1 is connected.
-     * The second scan results contains test2 which is 5GHz but no test1. Skip
-     * QNS to avoid aggressive network switching.
-     *
-     * expected return null
-     */
-    @Test
-    public void noNewQNSCurrentNetworkNotInScanResults() {
-        //Prepare saved network configurations.
-        String[] ssidsConfig = DEFAULT_SSIDS;
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssidsConfig, security);
-        prepareConfigStore(savedConfigs);
-        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
-        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
-
-        //Prepare the first scan results.
-        String[] ssids = {DEFAULT_SSIDS[0]};
-        String[] bssids = {DEFAULT_BSSIDS[0]};
-        int[] frequencies = {2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {-78};
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        //Connect to test1.
-        mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false, false, scanDetails, false,
-                false, true, false);
-
-        when(mWifiInfo.getNetworkId()).thenReturn(1);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
-        when(mWifiInfo.is24GHz()).thenReturn(true);
-        mWifiQualifiedNetworkSelector.setWifiNetworkScoreCache(null);
-        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
-        when(mClock.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime() + 11 * 1000);
-
-        //Prepare the second scan results which doesn't contain test1.
-        ssids[0] = DEFAULT_SSIDS[1];
-        bssids[0] = DEFAULT_BSSIDS[1];
-        frequencies[0] = 5180;
-        caps[0] = "[WPA2-EAP-CCMP][ESS]";
-        levels[0] = WifiQualifiedNetworkSelector.QUALIFIED_RSSI_5G_BAND;
-        scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        scanResultLinkConfiguration(savedConfigs, scanDetails);
-
-        //Skip the second network selection since current connected network is
-        //missing from the scan results.
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-        assertEquals("Expect no network selection", null, candidate);
-    }
-
-    boolean compareCarrierConfigs(WifiConfiguration candidate, WifiConfiguration carrierConfig) {
-        if (!candidate.SSID.equals(carrierConfig.SSID)) {
-            return false;
-        }
-        if (!candidate.ephemeral || carrierConfig.ephemeral) {
-            return false;
-        }
-        if (!candidate.isCarrierNetwork || carrierConfig.isCarrierNetwork) {
-            return false;
-        }
-        if (candidate.enterpriseConfig.getEapMethod() !=
-                carrierConfig.enterpriseConfig.getEapMethod()) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Case #49 Between two 2G Carrier networks, choose the one with stronger RSSI value
-     * if other conditions are the same and the RSSI values are not staturated.
-     */
-    @Test
-    public void chooseStrongerRssi2GCarrierNetwork()  {
-
-        String[] ssids = {"TEST1", "TEST2"};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {-65,-55};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(any(ScanDetail.class),
-                any(Boolean.class))).thenReturn(null);
-        when(mWifiConfigManager.saveNetworkAndSetCandidate(any(WifiConfiguration.class),
-                any(ScanResult.class))).then(AdditionalAnswers.returnsFirstArg());
-        when(mWifiConfigManager.getScanResultCandidate(any(WifiConfiguration.class)))
-                .thenReturn(chosenScanResult);
-        when(mWifiConfigManager.getIsCarrierNetworkEnabledByUser()).thenReturn(true);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-        assertTrue(compareCarrierConfigs(candidate, mCarrierConfiguredNetworks.get(1)));
-    }
-
-
-    /**
-     * Case #50 Choose 5G over 2G.
-     */
-    @Test
-    public void choose5GNetworkOver2GNetwork()  {
-
-        String[] ssids = {"TEST1", "TEST2"};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {2437, 5240};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {-65,-55};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(any(ScanDetail.class),
-                any(Boolean.class))).thenReturn(null);
-        when(mWifiConfigManager.saveNetworkAndSetCandidate(any(WifiConfiguration.class),
-                any(ScanResult.class))).then(AdditionalAnswers.returnsFirstArg());
-        when(mWifiConfigManager.getScanResultCandidate(any(WifiConfiguration.class)))
-                .thenReturn(chosenScanResult);
-        when(mWifiConfigManager.getIsCarrierNetworkEnabledByUser()).thenReturn(true);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-        assertTrue(compareCarrierConfigs(candidate, mCarrierConfiguredNetworks.get(1)));
-    }
-
-    /**
-     * Case #51 Stay on same BSSID & SSID.
-     */
-    @Test
-    public void chooseSameNetwork()  {
-
-        String[] ssids = {"TEST1", "TEST2"};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {2470, 2437};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {-65,-55};
-        int[] security = {SECURITY_PSK, SECURITY_PSK};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
-        prepareConfigStore(savedConfigs);
-
-        ScanResult chosenScanResult = scanDetails.get(0).getScanResult();
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(any(ScanDetail.class),
-                any(Boolean.class))).thenReturn(null);
-        when(mWifiConfigManager.saveNetworkAndSetCandidate(any(WifiConfiguration.class),
-                any(ScanResult.class))).then(AdditionalAnswers.returnsFirstArg());
-        when(mWifiConfigManager.getScanResultCandidate(any(WifiConfiguration.class)))
-                .thenReturn(chosenScanResult);
-        when(mWifiInfo.getNetworkId()).thenReturn(0);
-        when(mWifiInfo.getBSSID()).thenReturn(bssids[0]);
-        when(mWifiConfigManager.getIsCarrierNetworkEnabledByUser()).thenReturn(true);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, true, false, false);
-
-        assertTrue(compareCarrierConfigs(candidate, mCarrierConfiguredNetworks.get(0)));
-    }
-
-    /**
-     * Case #52 Test condition where no Carrier networks are defined.
-     */
-    @Test
-    public void testNoCarrierNetworks()  {
-
-        String[] ssids = {"TEST1", "TEST2"};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {5200, 5240};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        // test2 has slightly stronger RSSI value than test1
-        int[] levels = {-65,-53};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-
-        List<WifiConfiguration> nullCarrierConfiguredNetworks = new ArrayList<WifiConfiguration>();
-        mWifiQualifiedNetworkSelector.setCarrierConfiguredNetworks(nullCarrierConfiguredNetworks);
-        when(mWifiConfigManager.getIsCarrierNetworkEnabledByUser()).thenReturn(true);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                true, scanDetails, false, false, true, false);
-        assertEquals("Expect no network selection", null, candidate);
-    }
-
-    /**
-     * Case #53 Test condition where no Carrier networks are defined.
-     */
-    @Test
-    public void testParseCarrierInfoSuccess()  {
-        String[] wifiArray = new String[3];
-        wifiArray[0] = "V2lmaSBFeHRyYQ==|2|4";
-        wifiArray[1] = "R29vZ2xlLUE=|2|4";
-        wifiArray[2] = "R29vZ2xlLUd1ZXN0|2|4";
-
-        List<WifiConfiguration> configList =
-                mWifiQualifiedNetworkSelector.parseCarrierSuppliedWifiInfo(wifiArray);
-        assertEquals("Expect right number of etnries", configList.size(), 3);
-        assertEquals("Expect right network", configList.get(0).SSID, "\"Wifi Extra\"");
-        assertEquals("Expect right network", configList.get(1).SSID, "\"Google-A\"");
-        assertEquals("Expect right network", configList.get(2).SSID, "\"Google-Guest\"");
-        assertTrue("Expect right key",
-                configList.get(0).allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
-        assertEquals("Expect right EAP method",
-                configList.get(0).enterpriseConfig.getEapMethod(), 4);
-    }
-
-    /**
-     * Case #54 Test condition where string has non-numerics.
-     */
-    @Test
-    public void testParseCarrierInfoBadEntries1()  {
-        String[] wifiArray = new String[3];
-        wifiArray[0] = "V2lmaSBFeHRyYQ==|2|4";
-        wifiArray[1] = "R29vZ2xlLUE=|2|A"; //Invalid entry. Non-numeric.
-        wifiArray[2] = "R29vZ2xlLUd1ZXN0|2|4";
-
-        List<WifiConfiguration> configList =
-                mWifiQualifiedNetworkSelector.parseCarrierSuppliedWifiInfo(wifiArray);
-        assertEquals("Expect right number of etnries", configList.size(), 2);
-        assertEquals("Expect right network", configList.get(0).SSID, "\"Wifi Extra\"");
-        assertEquals("Expect right network", configList.get(1).SSID, "\"Google-Guest\"");
-    }
-
-    /**
-     * Case #55 Test condition where the config does not have the right number of entries.
-     */
-    @Test
-    public void testParseCarrierInfoBadEntries2()  {
-        String[] wifiArray = new String[3];
-        wifiArray[0] = "V2lmaSBFeHRyYQ==|2"; //Invalid number of entries
-        wifiArray[1] = "R29vZ2xlLUE=|2|4";
-        wifiArray[2] = "R29vZ2xlLUd1ZXN0|2|4";
-
-        List<WifiConfiguration> configList =
-                mWifiQualifiedNetworkSelector.parseCarrierSuppliedWifiInfo(wifiArray);
-        assertEquals("Expect right network", configList.get(0).SSID, "\"Google-A\"");
-        assertEquals("Expect right network", configList.get(1).SSID, "\"Google-Guest\"");
-    }
-
-    /**
-     * Case #56 Test invalid base-64.
-     */
-    @Test
-    public void testParseCarrierInfoBadBase64()  {
-        String[] wifiArray = new String[3];
-        wifiArray[0] = "V2lmaSBFeHRyYQ==|2|4";
-        wifiArray[1] = "xyz==|2|4"; //Invalid base64
-        wifiArray[2] = "R29vZ2xlLUd1ZXN0|2|4";
-
-        List<WifiConfiguration> configList =
-                mWifiQualifiedNetworkSelector.parseCarrierSuppliedWifiInfo(wifiArray);
-        assertEquals("Expect right network", configList.get(0).SSID, "\"Wifi Extra\"");
-        assertEquals("Expect right network", configList.get(1).SSID, "\"Google-Guest\"");
-    }
-
-    /**
-     * Case #56 Test invalid eap-method
-     */
-    @Test
-    public void testParseCarrierInfoBadEapMethod()  {
-        String[] wifiArray = new String[3];
-        wifiArray[0] = "V2lmaSBFeHRyYQ==|2|4";
-        wifiArray[1] = "R29vZ2xlLUE=|2|4";
-        wifiArray[2] = "R29vZ2xlLUd1ZXN0|2|11"; //Invalid eap-method
-
-        List<WifiConfiguration> configList =
-                mWifiQualifiedNetworkSelector.parseCarrierSuppliedWifiInfo(wifiArray);
-        assertEquals("Expect right network", configList.get(0).SSID, "\"Wifi Extra\"");
-        assertEquals("Expect right network", configList.get(1).SSID, "\"Google-A\"");
-    }
-
-    /**
-     * Case #56 Test invalid key
-     */
-    @Test
-    public void testParseCarrierInfoBadKey()  {
-        String[] wifiArray = new String[3];
-        wifiArray[0] = "V2lmaSBFeHRyYQ==|2|4";
-        wifiArray[1] = "R29vZ2xlLUE=|9|4";  //Invalid key
-        wifiArray[2] = "R29vZ2xlLUd1ZXN0|2|4";
-
-        List<WifiConfiguration> configList =
-                mWifiQualifiedNetworkSelector.parseCarrierSuppliedWifiInfo(wifiArray);
-        assertEquals("Expect right network", configList.get(0).SSID, "\"Wifi Extra\"");
-        assertEquals("Expect right network", configList.get(2).SSID, "\"Google-Guest\"");
-    }
-
-    /**
-     * Case #57 Test condition where no Carrier networks are defined.
-     */
-    @Test
-    public void testCarrierNotEnabledByUser()  {
-
-        String[] ssids = {"TEST1", "TEST2"};
-        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
-        int[] frequencies = {2437, 5240};
-        String[] caps = {"[WPA2-EAP-CCMP][ESS]", "[WPA2-EAP-CCMP][ESS]"};
-        int[] levels = {-65,-55};
-
-        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
-        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
-        when(mWifiConfigManager.updateSavedNetworkWithNewScanDetail(any(ScanDetail.class),
-                any(Boolean.class))).thenReturn(null);
-        when(mWifiConfigManager.saveNetworkAndSetCandidate(any(WifiConfiguration.class),
-                any(ScanResult.class))).then(AdditionalAnswers.returnsFirstArg());
-        when(mWifiConfigManager.getScanResultCandidate(any(WifiConfiguration.class)))
-                .thenReturn(chosenScanResult);
-        when(mWifiConfigManager.getIsCarrierNetworkEnabledByUser()).thenReturn(false);
-
-        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
-                false, scanDetails, false, false, true, false);
-        assertEquals("Expect no network selection", null, candidate);
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
new file mode 100644
index 0000000..41f14dd
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.NetworkAgent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiScoreReport}.
+ */
+public class WifiScoreReportTest {
+
+    private static final int CELLULAR_THRESHOLD_SCORE = 50;
+
+    WifiConfiguration mWifiConfiguration;
+    WifiScoreReport mWifiScoreReport;
+    ScanDetailCache mScanDetailCache;
+    WifiInfo mWifiInfo;
+    @Mock Context mContext;
+    @Mock NetworkAgent mNetworkAgent;
+    @Mock Resources mResources;
+    @Mock WifiConfigManager mWifiConfigManager;
+    @Mock WifiMetrics mWifiMetrics;
+
+    /**
+     * Sets up resource values for testing
+     *
+     * See frameworks/base/core/res/res/values/config.xml
+     */
+    private void setUpResources(Resources resources) {
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz))
+            .thenReturn(-82);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz))
+            .thenReturn(-70);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz))
+            .thenReturn(-57);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz))
+            .thenReturn(-85);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz))
+            .thenReturn(-73);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz))
+            .thenReturn(-60);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_link_speed_24))
+            .thenReturn(6); // Mbps
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_link_speed_5))
+            .thenReturn(12);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_link_speed_24))
+            .thenReturn(24);
+        when(resources.getInteger(
+                R.integer.config_wifi_framework_wifi_score_good_link_speed_5))
+            .thenReturn(36);
+    }
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        setUpResources(mResources);
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "nooooooooooo";
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        config.hiddenSSID = false;
+        mWifiInfo = new WifiInfo();
+        mWifiInfo.setFrequency(2412);
+        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(config));
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
+        mWifiConfiguration = config;
+        int maxSize = 10;
+        int trimSize = 5;
+        mScanDetailCache = new ScanDetailCache(config, maxSize, trimSize);
+        // TODO: populate the cache, but probably in the test cases, not here.
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(anyInt()))
+                .thenReturn(mScanDetailCache);
+        when(mContext.getResources()).thenReturn(mResources);
+        mWifiScoreReport = new WifiScoreReport(mContext, mWifiConfigManager);
+    }
+
+    /**
+     * Cleans up after test
+     */
+    @After
+    public void tearDown() throws Exception {
+        mResources = null;
+        mWifiScoreReport = null;
+        mWifiConfigManager = null;
+        mWifiMetrics = null;
+    }
+
+    /**
+     * Test for score reporting
+     *
+     * The score should be sent to both the NetworkAgent and the
+     * WifiMetrics
+     */
+    @Test
+    public void calculateAndReportScoreSucceeds() throws Exception {
+        int aggressiveHandover = 0;
+        mWifiInfo.setRssi(-77);
+        mWifiScoreReport.calculateAndReportScore(mWifiInfo,
+                mNetworkAgent, aggressiveHandover, mWifiMetrics);
+        verify(mNetworkAgent).sendNetworkScore(anyInt());
+        verify(mWifiMetrics).incrementWifiScoreCount(anyInt());
+    }
+
+    /**
+     * Test for operation with null NetworkAgent
+     *
+     * Expect to not die, and to calculate the score and report to metrics.
+     */
+    @Test
+    public void networkAgentMayBeNull() throws Exception {
+        mWifiInfo.setRssi(-33);
+        mWifiScoreReport.enableVerboseLogging(true);
+        mWifiScoreReport.calculateAndReportScore(mWifiInfo, null, 0, mWifiMetrics);
+        verify(mWifiMetrics).incrementWifiScoreCount(anyInt());
+    }
+
+    /**
+     * Exercise the rates with low RSSI
+     *
+     * The setup has a low (not bad) RSSI, and data movement (txSuccessRate) above
+     * the threshold.
+     *
+     * Expect a score above threshold.
+     */
+    @Test
+    public void allowLowRssiIfDataIsMoving() throws Exception {
+        mWifiInfo.setRssi(-80);
+        mWifiInfo.setLinkSpeed(6); // Mbps
+        mWifiInfo.txSuccessRate = 5.1; // proportional to pps
+        mWifiInfo.rxSuccessRate = 5.1;
+        for (int i = 0; i < 10; i++) {
+            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, 0, mWifiMetrics);
+        }
+        int score = mWifiInfo.score;
+        assertTrue(score > CELLULAR_THRESHOLD_SCORE);
+    }
+
+    /**
+     * Bad RSSI without data moving should allow handoff
+     *
+     * The setup has a bad RSSI, and the txSuccessRate is below threshold; several
+     * scoring iterations are performed.
+     *
+     * Expect the score to drop below the handoff threshold.
+     */
+    @Test
+    public void giveUpOnBadRssiWhenDataIsNotMoving() throws Exception {
+        mWifiInfo.setRssi(-100);
+        mWifiInfo.setLinkSpeed(6); // Mbps
+        mWifiInfo.setFrequency(5220);
+        mWifiScoreReport.enableVerboseLogging(true);
+        mWifiInfo.txSuccessRate = 0.1;
+        mWifiInfo.rxSuccessRate = 0.1;
+        for (int i = 0; i < 10; i++) {
+            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, 0, mWifiMetrics);
+        }
+        int score = mWifiInfo.score;
+        assertTrue(score < CELLULAR_THRESHOLD_SCORE);
+        verify(mNetworkAgent, atLeast(1)).sendNetworkScore(score);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
new file mode 100644
index 0000000..d5bfb20
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -0,0 +1,1583 @@
+/*
+ * 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 android.net.wifi.WifiManager.HOTSPOT_FAILED;
+import static android.net.wifi.WifiManager.HOTSPOT_STARTED;
+import static android.net.wifi.WifiManager.HOTSPOT_STOPPED;
+import static android.net.wifi.WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR;
+import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
+import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
+import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC;
+import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
+import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHANNEL;
+import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED;
+import static android.net.wifi.WifiManager.SAP_START_FAILURE_GENERAL;
+import static android.net.wifi.WifiManager.SAP_START_FAILURE_NO_CHANNEL;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.provider.Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
+
+import static com.android.server.wifi.LocalOnlyHotspotRequestInfo.HOTSPOT_NO_ERROR;
+import static com.android.server.wifi.WifiController.CMD_SET_AP;
+import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.IpConfiguration;
+import android.net.wifi.ScanSettings;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.os.WorkSource;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.server.wifi.WifiServiceImpl.LocalOnlyRequestorCallback;
+import com.android.server.wifi.util.WifiAsyncChannel;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+
+/**
+ * Unit tests for {@link WifiServiceImpl}.
+ *
+ * Note: this is intended to build up over time and will not immediately cover the entire file.
+ */
+@SmallTest
+public class WifiServiceImplTest {
+
+    private static final String TAG = "WifiServiceImplTest";
+    private static final String SCAN_PACKAGE_NAME = "scanPackage";
+    private static final String WHITE_LIST_SCAN_PACKAGE_NAME = "whiteListScanPackage";
+    private static final int DEFAULT_VERBOSE_LOGGING = 0;
+    private static final long WIFI_BACKGROUND_SCAN_INTERVAL = 10000;
+    private static final String ANDROID_SYSTEM_PACKAGE = "android";
+    private static final String TEST_PACKAGE_NAME = "TestPackage";
+    private static final String SYSUI_PACKAGE_NAME = "com.android.systemui";
+    private static final int TEST_PID = 6789;
+    private static final int TEST_PID2 = 9876;
+    private static final String WIFI_IFACE_NAME = "wlan0";
+
+    private WifiServiceImpl mWifiServiceImpl;
+    private TestLooper mLooper;
+    private PowerManager mPowerManager;
+    private Handler mHandler;
+    private Messenger mAppMessenger;
+    private int mPid;
+    private int mPid2 = Process.myPid();
+
+    final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+    final ArgumentCaptor<IntentFilter> mIntentFilterCaptor =
+            ArgumentCaptor.forClass(IntentFilter.class);
+
+    final ArgumentCaptor<Message> mMessageCaptor = ArgumentCaptor.forClass(Message.class);
+    final ArgumentCaptor<SoftApModeConfiguration> mSoftApModeConfigCaptor =
+            ArgumentCaptor.forClass(SoftApModeConfiguration.class);
+
+    @Mock Context mContext;
+    @Mock WifiInjector mWifiInjector;
+    @Mock Clock mClock;
+    @Mock WifiController mWifiController;
+    @Mock WifiTrafficPoller mWifiTrafficPoller;
+    @Mock WifiStateMachine mWifiStateMachine;
+    @Mock HandlerThread mHandlerThread;
+    @Mock AsyncChannel mAsyncChannel;
+    @Mock Resources mResources;
+    @Mock FrameworkFacade mFrameworkFacade;
+    @Mock WifiLockManager mLockManager;
+    @Mock WifiMulticastLockManager mWifiMulticastLockManager;
+    @Mock WifiLastResortWatchdog mWifiLastResortWatchdog;
+    @Mock WifiBackupRestore mWifiBackupRestore;
+    @Mock WifiMetrics mWifiMetrics;
+    @Mock WifiPermissionsUtil mWifiPermissionsUtil;
+    @Mock WifiSettingsStore mSettingsStore;
+    @Mock ContentResolver mContentResolver;
+    @Mock UserManager mUserManager;
+    @Mock WifiConfiguration mApConfig;
+    @Mock ActivityManager mActivityManager;
+    @Mock AppOpsManager mAppOpsManager;
+    @Mock IBinder mAppBinder;
+    @Mock WifiNotificationController mWifiNotificationController;
+    @Mock LocalOnlyHotspotRequestInfo mRequestInfo;
+    @Mock LocalOnlyHotspotRequestInfo mRequestInfo2;
+
+    @Spy FakeWifiLog mLog;
+
+    private class WifiAsyncChannelTester {
+        private static final String TAG = "WifiAsyncChannelTester";
+        public static final int CHANNEL_STATE_FAILURE = -1;
+        public static final int CHANNEL_STATE_DISCONNECTED = 0;
+        public static final int CHANNEL_STATE_HALF_CONNECTED = 1;
+        public static final int CHANNEL_STATE_FULLY_CONNECTED = 2;
+
+        private int mState = CHANNEL_STATE_DISCONNECTED;
+        private WifiAsyncChannel mChannel;
+        private WifiLog mAsyncTestLog;
+
+        WifiAsyncChannelTester(WifiInjector wifiInjector) {
+            mAsyncTestLog = wifiInjector.makeLog(TAG);
+        }
+
+        public int getChannelState() {
+            return mState;
+        }
+
+        public void connect(final Looper looper, final Messenger messenger,
+                final Handler incomingMessageHandler) {
+            assertEquals("AsyncChannel must be in disconnected state",
+                    CHANNEL_STATE_DISCONNECTED, mState);
+            mChannel = new WifiAsyncChannel(TAG);
+            mChannel.setWifiLog(mLog);
+            Handler handler = new Handler(mLooper.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                        case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                            if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                                mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                                mState = CHANNEL_STATE_HALF_CONNECTED;
+                            } else {
+                                mState = CHANNEL_STATE_FAILURE;
+                            }
+                            break;
+                        case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+                            mState = CHANNEL_STATE_FULLY_CONNECTED;
+                            break;
+                        case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                            mState = CHANNEL_STATE_DISCONNECTED;
+                            break;
+                        default:
+                            incomingMessageHandler.handleMessage(msg);
+                            break;
+                    }
+                }
+            };
+            mChannel.connect(null, handler, messenger);
+        }
+    }
+
+    @Before public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLooper = new TestLooper();
+        mHandler = spy(new Handler(mLooper.getLooper()));
+        mAppMessenger = new Messenger(mHandler);
+
+        when(mRequestInfo.getPid()).thenReturn(mPid);
+        when(mRequestInfo2.getPid()).thenReturn(mPid2);
+        when(mWifiInjector.getUserManager()).thenReturn(mUserManager);
+        when(mWifiInjector.getWifiController()).thenReturn(mWifiController);
+        when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
+        when(mWifiInjector.getWifiStateMachine()).thenReturn(mWifiStateMachine);
+        when(mWifiStateMachine.syncInitialize(any())).thenReturn(true);
+        when(mWifiInjector.getWifiServiceHandlerThread()).thenReturn(mHandlerThread);
+        when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        doNothing().when(mFrameworkFacade).registerContentObserver(eq(mContext), any(),
+                anyBoolean(), any());
+        when(mContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(mActivityManager);
+        when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+        when(mFrameworkFacade.getLongSetting(
+                eq(mContext),
+                eq(Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS),
+                anyLong()))
+                .thenReturn(WIFI_BACKGROUND_SCAN_INTERVAL);
+        when(mFrameworkFacade.getStringSetting(
+                eq(mContext),
+                eq(Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST)))
+                .thenReturn(WHITE_LIST_SCAN_PACKAGE_NAME);
+        IPowerManager powerManagerService = mock(IPowerManager.class);
+        mPowerManager = new PowerManager(mContext, powerManagerService, new Handler());
+        when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        WifiAsyncChannel wifiAsyncChannel = new WifiAsyncChannel("WifiServiceImplTest");
+        wifiAsyncChannel.setWifiLog(mLog);
+        when(mFrameworkFacade.makeWifiAsyncChannel(anyString())).thenReturn(wifiAsyncChannel);
+        when(mWifiInjector.getFrameworkFacade()).thenReturn(mFrameworkFacade);
+        when(mWifiInjector.getWifiLockManager()).thenReturn(mLockManager);
+        when(mWifiInjector.getWifiMulticastLockManager()).thenReturn(mWifiMulticastLockManager);
+        when(mWifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
+        when(mWifiInjector.getWifiBackupRestore()).thenReturn(mWifiBackupRestore);
+        when(mWifiInjector.makeLog(anyString())).thenReturn(mLog);
+        WifiTrafficPoller wifiTrafficPoller = new WifiTrafficPoller(mContext,
+                mLooper.getLooper(), "mockWlan");
+        when(mWifiInjector.getWifiTrafficPoller()).thenReturn(wifiTrafficPoller);
+        when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
+        when(mWifiInjector.getWifiSettingsStore()).thenReturn(mSettingsStore);
+        when(mWifiInjector.getClock()).thenReturn(mClock);
+        when(mWifiInjector.getWifiNotificationController()).thenReturn(mWifiNotificationController);
+        mWifiServiceImpl = new WifiServiceImpl(mContext, mWifiInjector, mAsyncChannel);
+        mWifiServiceImpl.setWifiHandlerLogForTest(mLog);
+    }
+
+    @Test
+    public void testRemoveNetworkUnknown() {
+        assertFalse(mWifiServiceImpl.removeNetwork(-1));
+    }
+
+    @Test
+    public void testAsyncChannelHalfConnected() {
+        WifiAsyncChannelTester channelTester = new WifiAsyncChannelTester(mWifiInjector);
+        Handler handler = mock(Handler.class);
+        TestLooper looper = new TestLooper();
+        channelTester.connect(looper.getLooper(), mWifiServiceImpl.getWifiServiceMessenger(),
+                handler);
+        mLooper.dispatchAll();
+        assertEquals("AsyncChannel must be half connected",
+                WifiAsyncChannelTester.CHANNEL_STATE_HALF_CONNECTED,
+                channelTester.getChannelState());
+    }
+
+    /**
+     * Tests the isValid() check for StaticIpConfigurations, ensuring that configurations with null
+     * ipAddress are rejected, and configurations with ipAddresses are valid.
+     */
+    @Test
+    public void testStaticIpConfigurationValidityCheck() {
+        WifiConfiguration conf = WifiConfigurationTestUtil.createOpenNetwork();
+        IpConfiguration ipConf =
+                WifiConfigurationTestUtil.createStaticIpConfigurationWithStaticProxy();
+        conf.setIpConfiguration(ipConf);
+        // Ensure staticIpConfiguration with IP Address is valid
+        assertTrue(mWifiServiceImpl.isValid(conf));
+        ipConf.staticIpConfiguration.ipAddress = null;
+        // Ensure staticIpConfiguration with null IP Address it is not valid
+        conf.setIpConfiguration(ipConf);
+        assertFalse(mWifiServiceImpl.isValid(conf));
+    }
+
+    /**
+     * Ensure WifiMetrics.dump() is the only dump called when 'dumpsys wifi WifiMetricsProto' is
+     * called. This is required to support simple metrics collection via dumpsys
+     */
+    @Test
+    public void testWifiMetricsDump() {
+        mWifiServiceImpl.dump(new FileDescriptor(), new PrintWriter(new StringWriter()),
+                new String[]{mWifiMetrics.PROTO_DUMP_ARG});
+        verify(mWifiMetrics)
+                .dump(any(FileDescriptor.class), any(PrintWriter.class), any(String[].class));
+        verify(mWifiStateMachine, never())
+                .dump(any(FileDescriptor.class), any(PrintWriter.class), any(String[].class));
+    }
+
+
+    /**
+     * Ensure WifiServiceImpl.dump() doesn't throw an NPE when executed with null args
+     */
+    @Test
+    public void testDumpNullArgs() {
+        mWifiServiceImpl.dump(new FileDescriptor(), new PrintWriter(new StringWriter()), null);
+    }
+
+    /**
+     * Verify that wifi can be enabled by a caller with WIFI_STATE_CHANGE permission when wifi is
+     * off (no hotspot, no airplane mode).
+     */
+    @Test
+    public void testSetWifiEnabledSuccess() throws Exception {
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that the CMD_TOGGLE_WIFI message won't be sent if wifi is already on.
+     */
+    @Test
+    public void testSetWifiEnabledNoToggle() throws Exception {
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(false);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+        verify(mWifiController, never()).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify a SecurityException is thrown if a caller does not have the correct permission to
+     * toggle wifi.
+     */
+    @Test(expected = SecurityException.class)
+    public void testSetWifiEnableWithoutPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.CHANGE_WIFI_STATE),
+                                                eq("WifiService"));
+        mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true);
+        verify(mWifiStateMachine, never()).syncGetWifiApState();
+    }
+
+    /**
+     * Verify that a call from an app with the NETWORK_SETTINGS permission can enable wifi if we
+     * are in softap mode.
+     */
+    @Test
+    public void testSetWifiEnabledFromNetworkSettingsHolderWhenApEnabled() throws Exception {
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS)))
+                        .thenReturn(PackageManager.PERMISSION_GRANTED);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(SYSUI_PACKAGE_NAME, true));
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that a call from an app cannot enable wifi if we are in softap mode.
+     */
+    @Test
+    public void testSetWifiEnabledFromAppFailsWhenApEnabled() throws Exception {
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
+        when(mContext.checkCallingOrSelfPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS)))
+                        .thenReturn(PackageManager.PERMISSION_DENIED);
+        assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+        verify(mSettingsStore, never()).handleWifiToggled(anyBoolean());
+        verify(mWifiController, never()).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that wifi can be disabled by a caller with WIFI_STATE_CHANGE permission when wifi is
+     * on.
+     */
+    @Test
+    public void testSetWifiDisabledSuccess() throws Exception {
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
+        when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that CMD_TOGGLE_WIFI message won't be sent if wifi is already off.
+     */
+    @Test
+    public void testSetWifiDisabledNoToggle() throws Exception {
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
+        when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(false);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
+        verify(mWifiController, never()).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify a SecurityException is thrown if a caller does not have the correct permission to
+     * toggle wifi.
+     */
+    @Test(expected = SecurityException.class)
+    public void testSetWifiDisabledWithoutPermission() throws Exception {
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.CHANGE_WIFI_STATE),
+                                                eq("WifiService"));
+        mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false);
+    }
+
+    /**
+     * Ensure unpermitted callers cannot write the SoftApConfiguration.
+     *
+     * @throws SecurityException
+     */
+    @Test(expected = SecurityException.class)
+    public void testSetWifiApConfigurationNotSavedWithoutPermission() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+        WifiConfiguration apConfig = new WifiConfiguration();
+        mWifiServiceImpl.setWifiApConfiguration(apConfig);
+        verify(mWifiStateMachine, never()).setWifiApConfiguration(eq(apConfig));
+    }
+
+    /**
+     * Ensure softap config is written when the caller has the correct permission.
+     */
+    @Test
+    public void testSetWifiApConfigurationSuccess() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        WifiConfiguration apConfig = new WifiConfiguration();
+        mWifiServiceImpl.setWifiApConfiguration(apConfig);
+        verify(mWifiStateMachine).setWifiApConfiguration(eq(apConfig));
+    }
+
+    /**
+     * Ensure that a null config does not overwrite the saved ap config.
+     */
+    @Test
+    public void testSetWifiApConfigurationNullConfigNotSaved() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        mWifiServiceImpl.setWifiApConfiguration(null);
+        verify(mWifiStateMachine, never()).setWifiApConfiguration(isNull(WifiConfiguration.class));
+    }
+
+    /**
+     * Ensure unpermitted callers are not able to retrieve the softap config.
+     *
+     * @throws SecurityException
+     */
+    @Test(expected = SecurityException.class)
+    public void testGetWifiApConfigurationNotReturnedWithoutPermission() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(false);
+        mWifiServiceImpl.getWifiApConfiguration();
+        verify(mWifiStateMachine, never()).syncGetWifiApConfiguration();
+    }
+
+    /**
+     * Ensure permitted callers are able to retrieve the softap config.
+     */
+    @Test
+    public void testGetWifiApConfigurationSuccess() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        WifiConfiguration apConfig = new WifiConfiguration();
+        when(mWifiStateMachine.syncGetWifiApConfiguration()).thenReturn(apConfig);
+        assertEquals(apConfig, mWifiServiceImpl.getWifiApConfiguration());
+    }
+
+    /**
+     * Make sure we do not start wifi if System services have to be restarted to decrypt the device.
+     */
+    @Test
+    public void testWifiControllerDoesNotStartWhenDeviceTriggerResetMainAtBoot() {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(true);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+        verify(mWifiController, never()).start();
+    }
+
+    /**
+     * Make sure we do start WifiController (wifi disabled) if the device is already decrypted.
+     */
+    @Test
+    public void testWifiControllerStartsWhenDeviceIsDecryptedAtBootWithWifiDisabled() {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+        verify(mWifiController).start();
+        verify(mWifiController, never()).sendMessage(CMD_WIFI_TOGGLED);
+    }
+
+    /**
+     * Make sure we do start WifiController (wifi enabled) if the device is already decrypted.
+     */
+    @Test
+    public void testWifiFullyStartsWhenDeviceIsDecryptedAtBootWithWifiEnabled() {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.handleWifiToggled(true)).thenReturn(true);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
+        when(mWifiStateMachine.syncGetWifiState()).thenReturn(WIFI_STATE_DISABLED);
+        when(mWifiStateMachine.syncGetWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
+        when(mContext.getPackageName()).thenReturn(ANDROID_SYSTEM_PACKAGE);
+        mWifiServiceImpl.checkAndStartWifi();
+        verify(mWifiController).start();
+        verify(mWifiController).sendMessage(CMD_WIFI_TOGGLED);
+    }
+
+    /**
+     * Verify setWifiApEnabled works with the correct permissions and a null config.
+     */
+    @Test
+    public void testSetWifiApEnabledWithProperPermissionsWithNullConfig() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
+                .thenReturn(false);
+        mWifiServiceImpl.setWifiApEnabled(null, true);
+        verify(mWifiController)
+                .sendMessage(eq(CMD_SET_AP), eq(1), eq(0), mSoftApModeConfigCaptor.capture());
+        assertNull(mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
+    }
+
+    /**
+     * Verify setWifiApEnabled works with correct permissions and a valid config.
+     *
+     * TODO: should really validate that ap configs have a set of basic config settings b/37280779
+     */
+    @Test
+    public void testSetWifiApEnabledWithProperPermissionsWithValidConfig() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
+                .thenReturn(false);
+        WifiConfiguration apConfig = new WifiConfiguration();
+        mWifiServiceImpl.setWifiApEnabled(apConfig, true);
+        verify(mWifiController).sendMessage(
+                eq(CMD_SET_AP), eq(1), eq(0), mSoftApModeConfigCaptor.capture());
+        assertEquals(apConfig, mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
+    }
+
+    /**
+     * Verify setWifiApEnabled when disabling softap with correct permissions sends the correct
+     * message to WifiController.
+     */
+    @Test
+    public void testSetWifiApEnabledFalseWithProperPermissionsWithNullConfig() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
+                .thenReturn(false);
+        mWifiServiceImpl.setWifiApEnabled(null, false);
+        verify(mWifiController)
+                .sendMessage(eq(CMD_SET_AP), eq(0), eq(0), mSoftApModeConfigCaptor.capture());
+        assertNull(mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
+    }
+
+    /**
+     * setWifiApEnabled should fail if the provided config is not valid.
+     */
+    @Test
+    public void testSetWifiApEnabledWithProperPermissionInvalidConfigFails() {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
+                .thenReturn(false);
+        // mApConfig is a mock and the values are not set - triggering the invalid config.  Testing
+        // will be improved when we actually do test softap configs in b/37280779
+        mWifiServiceImpl.setWifiApEnabled(mApConfig, true);
+        verify(mWifiController, never())
+                .sendMessage(eq(CMD_SET_AP), eq(1), eq(0), any(SoftApModeConfiguration.class));
+    }
+
+    /**
+     * setWifiApEnabled should throw a security exception when the caller does not have the correct
+     * permissions.
+     */
+    @Test(expected = SecurityException.class)
+    public void testSetWifiApEnabledThrowsSecurityExceptionWithoutConfigOverridePermission()
+            throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.CHANGE_WIFI_STATE),
+                        eq("WifiService"));
+        mWifiServiceImpl.setWifiApEnabled(null, true);
+    }
+
+    /**
+     * setWifiApEnabled should throw a SecurityException when disallow tethering is set for the
+     * user.
+     */
+    @Test(expected = SecurityException.class)
+    public void testSetWifiApEnabledThrowsSecurityExceptionWithDisallowTethering()
+            throws Exception {
+        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
+        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
+                .thenReturn(true);
+        mWifiServiceImpl.setWifiApEnabled(null, true);
+
+    }
+
+    /**
+     * Verify caller with proper permission can call startSoftAp.
+     */
+    @Test
+    public void testStartSoftApWithPermissionsAndNullConfig() {
+        boolean result = mWifiServiceImpl.startSoftAp(null);
+        assertTrue(result);
+        verify(mWifiController)
+                .sendMessage(eq(CMD_SET_AP), eq(1), eq(0), mSoftApModeConfigCaptor.capture());
+        assertNull(mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
+    }
+
+    /**
+     * Verify caller with proper permissions but an invalid config does not start softap.
+     */
+    @Test
+    public void testStartSoftApWithPermissionsAndInvalidConfig() {
+        boolean result = mWifiServiceImpl.startSoftAp(mApConfig);
+        assertFalse(result);
+        verifyZeroInteractions(mWifiController);
+    }
+
+    /**
+     * Verify caller with proper permission and valid config does start softap.
+     */
+    @Test
+    public void testStartSoftApWithPermissionsAndValidConfig() {
+        WifiConfiguration config = new WifiConfiguration();
+        boolean result = mWifiServiceImpl.startSoftAp(config);
+        assertTrue(result);
+        verify(mWifiController)
+                .sendMessage(eq(CMD_SET_AP), eq(1), eq(0), mSoftApModeConfigCaptor.capture());
+        assertEquals(config, mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
+    }
+
+    /**
+     * Verify a SecurityException is thrown when a caller without the correct permission attempts to
+     * start softap.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartSoftApWithoutPermissionThrowsException() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_STACK),
+                                                eq("WifiService"));
+        mWifiServiceImpl.startSoftAp(null);
+    }
+
+    /**
+     * Verify caller with proper permission can call stopSoftAp.
+     */
+    @Test
+    public void testStopSoftApWithPermissions() {
+        boolean result = mWifiServiceImpl.stopSoftAp();
+        assertTrue(result);
+        verify(mWifiController).sendMessage(eq(CMD_SET_AP), eq(0), eq(0));
+    }
+
+    /**
+     * Verify SecurityException is thrown when a caller without the correct permission attempts to
+     * stop softap.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStopSoftApWithoutPermissionThrowsException() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_STACK),
+                                                eq("WifiService"));
+        mWifiServiceImpl.stopSoftAp();
+    }
+
+    /**
+     * Ensure foreground apps can always do wifi scans.
+     */
+    @Test
+    public void testWifiScanStartedForeground() {
+        when(mActivityManager.getPackageImportance(SCAN_PACKAGE_NAME)).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+        mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+        verify(mWifiStateMachine).startScan(
+                anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+    }
+
+    /**
+     * Ensure background apps get throttled when the previous scan is too close.
+     */
+    @Test
+    public void testWifiScanBackgroundThrottled() {
+        when(mActivityManager.getPackageImportance(SCAN_PACKAGE_NAME)).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+        long startMs = 1000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startMs);
+        mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+        verify(mWifiStateMachine).startScan(
+                anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                startMs + WIFI_BACKGROUND_SCAN_INTERVAL - 1000);
+        mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+        verify(mWifiStateMachine, times(1)).startScan(
+                anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+    }
+
+    /**
+     * Ensure background apps can do wifi scan when the throttle interval reached.
+     */
+    @Test
+    public void testWifiScanBackgroundNotThrottled() {
+        when(mActivityManager.getPackageImportance(SCAN_PACKAGE_NAME)).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+        long startMs = 1000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startMs);
+        mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+        verify(mWifiStateMachine).startScan(
+                anyInt(), eq(0), (ScanSettings) eq(null), any(WorkSource.class));
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                startMs + WIFI_BACKGROUND_SCAN_INTERVAL + 1000);
+        mWifiServiceImpl.startScan(null, null, SCAN_PACKAGE_NAME);
+        verify(mWifiStateMachine).startScan(
+                anyInt(), eq(1), (ScanSettings) eq(null), any(WorkSource.class));
+    }
+
+    /**
+     * Ensure background apps can do wifi scan when the throttle interval reached.
+     */
+    @Test
+    public void testWifiScanBackgroundWhiteListed() {
+        when(mActivityManager.getPackageImportance(WHITE_LIST_SCAN_PACKAGE_NAME)).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED);
+        long startMs = 1000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startMs);
+        mWifiServiceImpl.startScan(null, null, WHITE_LIST_SCAN_PACKAGE_NAME);
+        verify(mWifiStateMachine).startScan(
+                anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                startMs + WIFI_BACKGROUND_SCAN_INTERVAL - 1000);
+        mWifiServiceImpl.startScan(null, null, WHITE_LIST_SCAN_PACKAGE_NAME);
+        verify(mWifiStateMachine, times(2)).startScan(
+                anyInt(), anyInt(), (ScanSettings) eq(null), any(WorkSource.class));
+    }
+
+    private void registerLOHSRequestFull() {
+        // allow test to proceed without a permission check failure
+        when(mSettingsStore.getLocationModeSetting(mContext))
+                .thenReturn(LOCATION_MODE_HIGH_ACCURACY);
+        try {
+            when(mFrameworkFacade.isAppForeground(anyInt())).thenReturn(true);
+        } catch (RemoteException e) { }
+        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING))
+                .thenReturn(false);
+        int result = mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder,
+                TEST_PACKAGE_NAME);
+        assertEquals(LocalOnlyHotspotCallback.REQUEST_REGISTERED, result);
+    }
+
+    /**
+     * Verify that the call to startLocalOnlyHotspot returns REQUEST_REGISTERED when successfully
+     * called.
+     */
+    @Test
+    public void testStartLocalOnlyHotspotSingleRegistrationReturnsRequestRegistered() {
+        registerLOHSRequestFull();
+    }
+
+    /**
+     * Verify that a call to startLocalOnlyHotspot throws a SecurityException if the caller does not
+     * have the CHANGE_WIFI_STATE permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartLocalOnlyHotspotThrowsSecurityExceptionWithoutCorrectPermission() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.CHANGE_WIFI_STATE),
+                                                eq("WifiService"));
+        mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Verify that a call to startLocalOnlyHotspot throws a SecurityException if the caller does not
+     * have Location permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartLocalOnlyHotspotThrowsSecurityExceptionWithoutLocationPermission() {
+        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        doThrow(new SecurityException())
+                .when(mWifiPermissionsUtil).enforceLocationPermission(eq(TEST_PACKAGE_NAME),
+                                                                      anyInt());
+        mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Verify that a call to startLocalOnlyHotspot throws a SecurityException if Location mode is
+     * disabled.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartLocalOnlyHotspotThrowsSecurityExceptionWithoutLocationEnabled() {
+        when(mSettingsStore.getLocationModeSetting(mContext)).thenReturn(LOCATION_MODE_OFF);
+        mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Only start LocalOnlyHotspot if the caller is the foreground app at the time of the request.
+     */
+    @Test
+    public void testStartLocalOnlyHotspotFailsIfRequestorNotForegroundApp() throws Exception {
+        when(mSettingsStore.getLocationModeSetting(mContext))
+                .thenReturn(LOCATION_MODE_HIGH_ACCURACY);
+
+        when(mFrameworkFacade.isAppForeground(anyInt())).thenReturn(false);
+        int result = mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder,
+                TEST_PACKAGE_NAME);
+        assertEquals(LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE, result);
+    }
+
+    /**
+     * Do not register the LocalOnlyHotspot request if the caller app cannot be verified as the
+     * foreground app at the time of the request (ie, throws an exception in the check).
+     */
+    @Test
+    public void testStartLocalOnlyHotspotFailsIfForegroundAppCheckThrowsRemoteException()
+            throws Exception {
+        when(mSettingsStore.getLocationModeSetting(mContext))
+                .thenReturn(LOCATION_MODE_HIGH_ACCURACY);
+
+        when(mFrameworkFacade.isAppForeground(anyInt())).thenThrow(new RemoteException());
+        int result = mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder,
+                TEST_PACKAGE_NAME);
+        assertEquals(LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE, result);
+    }
+
+    /**
+     * Only start LocalOnlyHotspot if we are not tethering.
+     */
+    @Test
+    public void testHotspotDoesNotStartWhenAlreadyTethering() throws Exception {
+        when(mSettingsStore.getLocationModeSetting(mContext))
+                            .thenReturn(LOCATION_MODE_HIGH_ACCURACY);
+        when(mFrameworkFacade.isAppForeground(anyInt())).thenReturn(true);
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_TETHERED);
+        mLooper.dispatchAll();
+        int returnCode = mWifiServiceImpl.startLocalOnlyHotspot(
+                mAppMessenger, mAppBinder, TEST_PACKAGE_NAME);
+        assertEquals(ERROR_INCOMPATIBLE_MODE, returnCode);
+    }
+
+    /**
+     * Only start LocalOnlyHotspot if admin setting does not disallow tethering.
+     */
+    @Test
+    public void testHotspotDoesNotStartWhenTetheringDisallowed() throws Exception {
+        when(mSettingsStore.getLocationModeSetting(mContext))
+                .thenReturn(LOCATION_MODE_HIGH_ACCURACY);
+        when(mFrameworkFacade.isAppForeground(anyInt())).thenReturn(true);
+        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING))
+                .thenReturn(true);
+        int returnCode = mWifiServiceImpl.startLocalOnlyHotspot(
+                mAppMessenger, mAppBinder, TEST_PACKAGE_NAME);
+        assertEquals(ERROR_TETHERING_DISALLOWED, returnCode);
+    }
+
+    /**
+     * Verify that callers can only have one registered LOHS request.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testStartLocalOnlyHotspotThrowsExceptionWhenCallerAlreadyRegistered() {
+        registerLOHSRequestFull();
+
+        // now do the second request that will fail
+        mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder, TEST_PACKAGE_NAME);
+    }
+
+    /**
+     * Verify that the call to stopLocalOnlyHotspot does not do anything when there aren't any
+     * registered callers.
+     */
+    @Test
+    public void testStopLocalOnlyHotspotDoesNothingWithoutRegisteredRequests() {
+        // allow test to proceed without a permission check failure
+        mWifiServiceImpl.stopLocalOnlyHotspot();
+        // there is nothing registered, so this shouldn't do anything
+        verify(mWifiController, never()).sendMessage(eq(CMD_SET_AP), anyInt(), anyInt());
+    }
+
+    /**
+     * Verify that the call to stopLocalOnlyHotspot does not do anything when one caller unregisters
+     * but there is still an active request
+     */
+    @Test
+    public void testStopLocalOnlyHotspotDoesNothingWithARemainingRegisteredRequest() {
+        // register a request that will remain after the stopLOHS call
+        mWifiServiceImpl.registerLOHSForTest(mPid, mRequestInfo);
+
+        registerLOHSRequestFull();
+
+        // Since we are calling with the same pid, the second register call will be removed
+        mWifiServiceImpl.stopLocalOnlyHotspot();
+        // there is still a valid registered request - do not tear down LOHS
+        verify(mWifiController, never()).sendMessage(eq(CMD_SET_AP), anyInt(), anyInt());
+    }
+
+    /**
+     * Verify that the call to stopLocalOnlyHotspot sends a message to WifiController to stop
+     * the softAp when there is one registered caller when that caller is removed.
+     */
+    @Test
+    public void testStopLocalOnlyHotspotTriggersSoftApStopWithOneRegisteredRequest() {
+        registerLOHSRequestFull();
+        verify(mWifiController)
+                .sendMessage(eq(CMD_SET_AP), eq(1), eq(0), any(SoftApModeConfiguration.class));
+
+        mWifiServiceImpl.stopLocalOnlyHotspot();
+        // there is was only one request registered, we should tear down softap
+        verify(mWifiController).sendMessage(eq(CMD_SET_AP), eq(0), eq(0));
+    }
+
+    /**
+     * Verify that a call to stopLocalOnlyHotspot throws a SecurityException if the caller does not
+     * have the CHANGE_WIFI_STATE permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStopLocalOnlyHotspotThrowsSecurityExceptionWithoutCorrectPermission() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.CHANGE_WIFI_STATE),
+                                                eq("WifiService"));
+        mWifiServiceImpl.stopLocalOnlyHotspot();
+    }
+
+    /**
+     * Verify that WifiServiceImpl does not send the stop ap message if there were no
+     * pending LOHS requests upon a binder death callback.
+     */
+    @Test
+    public void testServiceImplNotCalledWhenBinderDeathTriggeredNoRequests() {
+        LocalOnlyRequestorCallback binderDeathCallback =
+                mWifiServiceImpl.new LocalOnlyRequestorCallback();
+
+        binderDeathCallback.onLocalOnlyHotspotRequestorDeath(mRequestInfo);
+        verify(mWifiController, never()).sendMessage(eq(CMD_SET_AP), eq(0), eq(0));
+    }
+
+    /**
+     * Verify that WifiServiceImpl does not send the stop ap message if there are remaining
+     * registered LOHS requests upon a binder death callback.  Additionally verify that softap mode
+     * will be stopped if that remaining request is removed (to verify the binder death properly
+     * cleared the requestor that died).
+     */
+    @Test
+    public void testServiceImplNotCalledWhenBinderDeathTriggeredWithRegisteredRequests() {
+        LocalOnlyRequestorCallback binderDeathCallback =
+                mWifiServiceImpl.new LocalOnlyRequestorCallback();
+
+        // registering a request directly from the test will not trigger a message to start
+        // softap mode
+        mWifiServiceImpl.registerLOHSForTest(mPid, mRequestInfo);
+
+        registerLOHSRequestFull();
+
+        binderDeathCallback.onLocalOnlyHotspotRequestorDeath(mRequestInfo);
+        verify(mWifiController, never()).sendMessage(eq(CMD_SET_AP), anyInt(), anyInt());
+
+        reset(mWifiController);
+
+        // now stop as the second request and confirm CMD_SET_AP will be sent to make sure binder
+        // death requestor was removed
+        mWifiServiceImpl.stopLocalOnlyHotspot();
+        verify(mWifiController).sendMessage(eq(CMD_SET_AP), eq(0), eq(0));
+    }
+
+    private class IntentFilterMatcher implements ArgumentMatcher<IntentFilter> {
+        @Override
+        public boolean matches(IntentFilter filter) {
+            return filter.hasAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        }
+    }
+
+    /**
+     * Verify that onFailed is called for registered LOHS callers when a WIFI_AP_STATE_CHANGE
+     * broadcast is received.
+     */
+    @Test
+    public void testRegisteredCallbacksTriggeredOnSoftApFailureGeneric() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        registerLOHSRequestFull();
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_FAILED, WIFI_AP_STATE_DISABLED, SAP_START_FAILURE_GENERAL,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_FAILED, message.what);
+        assertEquals(ERROR_GENERIC, message.arg1);
+    }
+
+    /**
+     * Verify that onFailed is called for registered LOHS callers when a WIFI_AP_STATE_CHANGE
+     * broadcast is received with the SAP_START_FAILURE_NO_CHANNEL error.
+     */
+    @Test
+    public void testRegisteredCallbacksTriggeredOnSoftApFailureNoChannel() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        registerLOHSRequestFull();
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_FAILED, WIFI_AP_STATE_DISABLED, SAP_START_FAILURE_NO_CHANNEL,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_FAILED, message.what);
+        assertEquals(ERROR_NO_CHANNEL, message.arg1);
+    }
+
+    /**
+     * Verify that onStopped is called for registered LOHS callers when a WIFI_AP_STATE_CHANGE
+     * broadcast is received with WIFI_AP_STATE_DISABLING and LOHS was active.
+     */
+    @Test
+    public void testRegisteredCallbacksTriggeredOnSoftApDisabling() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        registerLOHSRequestFull();
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STARTED, message.what);
+        reset(mHandler);
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLING, WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STOPPED, message.what);
+    }
+
+
+    /**
+     * Verify that onStopped is called for registered LOHS callers when a WIFI_AP_STATE_CHANGE
+     * broadcast is received with WIFI_AP_STATE_DISABLED and LOHS was enabled.
+     */
+    @Test
+    public void testRegisteredCallbacksTriggeredOnSoftApDisabled() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        registerLOHSRequestFull();
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STARTED, message.what);
+        reset(mHandler);
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STOPPED, message.what);
+    }
+
+    /**
+     * Verify that no callbacks are called for registered LOHS callers when a WIFI_AP_STATE_CHANGE
+     * broadcast is received and the softap started.
+     */
+    @Test
+    public void testRegisteredCallbacksNotTriggeredOnSoftApStart() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        registerLOHSRequestFull();
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_ENABLED, WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME,
+                IFACE_IP_MODE_LOCAL_ONLY);
+
+        mLooper.dispatchAll();
+        verifyNoMoreInteractions(mHandler);
+    }
+
+    /**
+     * Verify that onStopped is called only once for registered LOHS callers when
+     * WIFI_AP_STATE_CHANGE broadcasts are received with WIFI_AP_STATE_DISABLING and
+     * WIFI_AP_STATE_DISABLED when LOHS was enabled.
+     */
+    @Test
+    public void testRegisteredCallbacksTriggeredOnlyOnceWhenSoftApDisabling() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        registerLOHSRequestFull();
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STARTED, message.what);
+        reset(mHandler);
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLING, WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STOPPED, message.what);
+    }
+
+    /**
+     * Verify that onFailed is called only once for registered LOHS callers when
+     * WIFI_AP_STATE_CHANGE broadcasts are received with WIFI_AP_STATE_FAILED twice.
+     */
+    @Test
+    public void testRegisteredCallbacksTriggeredOnlyOnceWhenSoftApFailsTwice() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        registerLOHSRequestFull();
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_FAILED, WIFI_AP_STATE_FAILED, ERROR_GENERIC,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_FAILED, WIFI_AP_STATE_FAILED, ERROR_GENERIC,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_FAILED, message.what);
+        assertEquals(ERROR_GENERIC, message.arg1);
+    }
+
+    /**
+     * Verify that onFailed is called for all registered LOHS callers when
+     * WIFI_AP_STATE_CHANGE broadcasts are received with WIFI_AP_STATE_FAILED.
+     */
+    @Test
+    public void testAllRegisteredCallbacksTriggeredWhenSoftApFails() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        // make an additional request for this test
+        mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
+
+        registerLOHSRequestFull();
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_FAILED, WIFI_AP_STATE_FAILED, ERROR_GENERIC,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_FAILED, WIFI_AP_STATE_FAILED, ERROR_GENERIC,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        verify(mRequestInfo).sendHotspotFailedMessage(ERROR_GENERIC);
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_FAILED, message.what);
+        assertEquals(ERROR_GENERIC, message.arg1);
+    }
+
+    /**
+     * Verify that onStopped is called for all registered LOHS callers when
+     * WIFI_AP_STATE_CHANGE broadcasts are received with WIFI_AP_STATE_DISABLED when LOHS was
+     * active.
+     */
+    @Test
+    public void testAllRegisteredCallbacksTriggeredWhenSoftApStops() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
+
+        registerLOHSRequestFull();
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+        verify(mRequestInfo).sendHotspotStartedMessage(any());
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STARTED, message.what);
+        reset(mHandler);
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLING, WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        verify(mRequestInfo).sendHotspotStoppedMessage();
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STOPPED, message.what);
+    }
+
+    /**
+     * Verify that onFailed is called for all registered LOHS callers when
+     * WIFI_AP_STATE_CHANGE broadcasts are received with WIFI_AP_STATE_DISABLED when LOHS was
+     * not active.
+     */
+    @Test
+    public void testAllRegisteredCallbacksTriggeredWhenSoftApStopsLOHSNotActive() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
+        mWifiServiceImpl.registerLOHSForTest(TEST_PID2, mRequestInfo2);
+
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLING, WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+        verify(mRequestInfo).sendHotspotFailedMessage(ERROR_GENERIC);
+        verify(mRequestInfo2).sendHotspotFailedMessage(ERROR_GENERIC);
+    }
+
+    /**
+     * Verify that if we do not have registered LOHS requestors and we receive an update that LOHS
+     * is up and ready for use, we tell WifiController to tear it down.  This can happen if softap
+     * mode fails to come up properly and we get an onFailed message for a tethering call and we
+     * had registered callers for LOHS.
+     */
+    @Test
+    public void testLOHSReadyWithoutRegisteredRequestsStopsSoftApMode() {
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+
+        verify(mWifiController).sendMessage(eq(CMD_SET_AP), eq(0), eq(0));
+    }
+
+    /**
+     * Verify that all registered LOHS requestors are notified via a HOTSPOT_STARTED message that
+     * the hotspot is up and ready to use.
+     */
+    @Test
+    public void testRegisteredLocalOnlyHotspotRequestorsGetOnStartedCallbackWhenReady()
+            throws Exception {
+        registerLOHSRequestFull();
+
+        mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+        verify(mRequestInfo).sendHotspotStartedMessage(any(WifiConfiguration.class));
+
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STARTED, message.what);
+        assertNotNull((WifiConfiguration) message.obj);
+    }
+
+    /**
+     * Verify that if a LOHS is already active, a new call to register a request will trigger the
+     * onStarted callback.
+     */
+    @Test
+    public void testRegisterLocalOnlyHotspotRequestAfterAlreadyStartedGetsOnStartedCallback()
+            throws Exception {
+        mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+
+        registerLOHSRequestFull();
+
+        mLooper.dispatchAll();
+
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_STARTED, message.what);
+        // since the first request was registered out of band, the config will be null
+        assertNull((WifiConfiguration) message.obj);
+    }
+
+    /**
+     * Verify that if a LOHS request is active and we receive an update with an ip mode
+     * configuration error, callers are notified via the onFailed callback with the generic
+     * error and are unregistered.
+     */
+    @Test
+    public void testCallOnFailedLocalOnlyHotspotRequestWhenIpConfigFails() throws Exception {
+        registerLOHSRequestFull();
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_CONFIGURATION_ERROR);
+        mLooper.dispatchAll();
+
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_FAILED, message.what);
+        assertEquals(ERROR_GENERIC, message.arg1);
+
+        // sendMessage should only happen once since the requestor should be unregistered
+        reset(mHandler);
+
+        // send HOTSPOT_FAILED message should only happen once since the requestor should be
+        // unregistered
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_CONFIGURATION_ERROR);
+        mLooper.dispatchAll();
+        verify(mHandler, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Verify that if a LOHS request is active and tethering starts, callers are notified on the
+     * incompatible mode and are unregistered.
+     */
+    @Test
+    public void testCallOnFailedLocalOnlyHotspotRequestWhenTetheringStarts() throws Exception {
+        registerLOHSRequestFull();
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_TETHERED);
+        mLooper.dispatchAll();
+
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(HOTSPOT_FAILED, message.what);
+        assertEquals(ERROR_INCOMPATIBLE_MODE, message.arg1);
+
+        // sendMessage should only happen once since the requestor should be unregistered
+        reset(mHandler);
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_TETHERED);
+        mLooper.dispatchAll();
+        verify(mHandler, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Verify that if LOHS is disabled, a new call to register a request will not trigger the
+     * onStopped callback.
+     */
+    @Test
+    public void testRegisterLocalOnlyHotspotRequestWhenStoppedDoesNotGetOnStoppedCallback()
+            throws Exception {
+        registerLOHSRequestFull();
+        mLooper.dispatchAll();
+
+        verify(mHandler, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Verify that if a LOHS was active and then stopped, a new call to register a request will
+     * not trigger the onStarted callback.
+     */
+    @Test
+    public void testRegisterLocalOnlyHotspotRequestAfterStoppedNoOnStartedCallback()
+            throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        mWifiServiceImpl.checkAndStartWifi();
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat(new IntentFilterMatcher()));
+
+        // register a request so we don't drop the LOHS interface ip update
+        mWifiServiceImpl.registerLOHSForTest(TEST_PID, mRequestInfo);
+
+        mWifiServiceImpl.updateInterfaceIpState(WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+
+        registerLOHSRequestFull();
+        mLooper.dispatchAll();
+
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        assertEquals(HOTSPOT_STARTED, mMessageCaptor.getValue().what);
+
+        reset(mHandler);
+
+        // now stop the hotspot
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLING, WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        TestUtil.sendWifiApStateChanged(mBroadcastReceiverCaptor.getValue(), mContext,
+                WIFI_AP_STATE_DISABLED, WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR,
+                WIFI_IFACE_NAME, IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.dispatchAll();
+        verify(mHandler).handleMessage(mMessageCaptor.capture());
+        assertEquals(HOTSPOT_STOPPED, mMessageCaptor.getValue().what);
+
+        reset(mHandler);
+
+        // now register a new caller - they should not get the onStarted callback
+        Messenger messenger2 = new Messenger(mHandler);
+        IBinder binder2 = mock(IBinder.class);
+
+        int result = mWifiServiceImpl.startLocalOnlyHotspot(messenger2, binder2, TEST_PACKAGE_NAME);
+        assertEquals(LocalOnlyHotspotCallback.REQUEST_REGISTERED, result);
+        mLooper.dispatchAll();
+
+        verify(mHandler, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Verify that a call to startWatchLocalOnlyHotspot is only allowed from callers with the
+     * signature only NETWORK_SETTINGS permission.
+     *
+     * This test is expecting the permission check to enforce the permission and throw a
+     * SecurityException for callers without the permission.  This exception should be bubbled up to
+     * the caller of startLocalOnlyHotspot.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartWatchLocalOnlyHotspotNotApprovedCaller() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                                                eq("WifiService"));
+        mWifiServiceImpl.startWatchLocalOnlyHotspot(mAppMessenger, mAppBinder);
+    }
+
+    /**
+     * Verify that the call to startWatchLocalOnlyHotspot throws the UnsupportedOperationException
+     * when called until the implementation is complete.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testStartWatchLocalOnlyHotspotNotSupported() {
+        mWifiServiceImpl.startWatchLocalOnlyHotspot(mAppMessenger, mAppBinder);
+    }
+
+    /**
+     * Verify that a call to stopWatchLocalOnlyHotspot is only allowed from callers with the
+     * signature only NETWORK_SETTINGS permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStopWatchLocalOnlyHotspotNotApprovedCaller() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                                                eq("WifiService"));
+        mWifiServiceImpl.stopWatchLocalOnlyHotspot();
+    }
+
+    /**
+     * Verify that the call to stopWatchLocalOnlyHotspot throws the UnsupportedOperationException
+     * until the implementation is complete.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testStopWatchLocalOnlyHotspotNotSupported() {
+        mWifiServiceImpl.stopWatchLocalOnlyHotspot();
+    }
+
+    /**
+     * Verify that the call to addOrUpdateNetwork for installing Passpoint profile is redirected
+     * to the Passpoint specific API addOrUpdatePasspointConfiguration.
+     */
+    @Test
+    public void testAddPasspointProfileViaAddNetwork() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        when(mResources.getBoolean(com.android.internal.R.bool.config_wifi_hotspot2_enabled))
+                .thenReturn(true);
+
+        when(mWifiStateMachine.syncAddOrUpdatePasspointConfig(any(),
+                any(PasspointConfiguration.class), anyInt())).thenReturn(true);
+        assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config));
+        verify(mWifiStateMachine).syncAddOrUpdatePasspointConfig(any(),
+                any(PasspointConfiguration.class), anyInt());
+        reset(mWifiStateMachine);
+
+        when(mWifiStateMachine.syncAddOrUpdatePasspointConfig(any(),
+                any(PasspointConfiguration.class), anyInt())).thenReturn(false);
+        assertEquals(-1, mWifiServiceImpl.addOrUpdateNetwork(config));
+        verify(mWifiStateMachine).syncAddOrUpdatePasspointConfig(any(),
+                any(PasspointConfiguration.class), anyInt());
+    }
+
+    /**
+     * Verify that a call to {@link WifiServiceImpl#restoreBackupData(byte[])} is only allowed from
+     * callers with the signature only NETWORK_SETTINGS permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testRestoreBackupDataNotApprovedCaller() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        mWifiServiceImpl.restoreBackupData(null);
+        verify(mWifiBackupRestore, never()).retrieveConfigurationsFromBackupData(any(byte[].class));
+    }
+
+    /**
+     * Verify that a call to {@link WifiServiceImpl#restoreSupplicantBackupData(byte[], byte[])} is
+     * only allowed from callers with the signature only NETWORK_SETTINGS permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testRestoreSupplicantBackupDataNotApprovedCaller() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        mWifiServiceImpl.restoreSupplicantBackupData(null, null);
+        verify(mWifiBackupRestore, never()).retrieveConfigurationsFromSupplicantBackupData(
+                any(byte[].class), any(byte[].class));
+    }
+
+    /**
+     * Verify that a call to {@link WifiServiceImpl#retrieveBackupData()} is only allowed from
+     * callers with the signature only NETWORK_SETTINGS permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testRetrieveBackupDataNotApprovedCaller() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        mWifiServiceImpl.retrieveBackupData();
+        verify(mWifiBackupRestore, never()).retrieveBackupDataFromConfigurations(any(List.class));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
new file mode 100644
index 0000000..49c7d18
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
@@ -0,0 +1,392 @@
+/*
+ * 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 static org.mockito.Mockito.*;
+
+import android.net.wifi.IApInterface;
+import android.net.wifi.IWificond;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.INetworkManagementService;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiStateMachinePrime}.
+ */
+@SmallTest
+public class WifiStateMachinePrimeTest {
+    public static final String TAG = "WifiStateMachinePrimeTest";
+
+    private static final String CLIENT_MODE_STATE_STRING = "ClientModeState";
+    private static final String SCAN_ONLY_MODE_STATE_STRING = "ScanOnlyModeState";
+    private static final String SOFT_AP_MODE_STATE_STRING = "SoftAPModeState";
+    private static final String WIFI_DISABLED_STATE_STRING = "WifiDisabledState";
+    private static final String CLIENT_MODE_ACTIVE_STATE_STRING = "ClientModeActiveState";
+    private static final String SCAN_ONLY_MODE_ACTIVE_STATE_STRING = "ScanOnlyModeActiveState";
+    private static final String SOFT_AP_MODE_ACTIVE_STATE_STRING = "SoftAPModeActiveState";
+
+    @Mock WifiInjector mWifiInjector;
+    @Mock WifiApConfigStore mWifiApConfigStore;
+    TestLooper mLooper;
+    @Mock IWificond mWificond;
+    @Mock IApInterface mApInterface;
+    @Mock INetworkManagementService mNMService;
+    @Mock SoftApManager mSoftApManager;
+    SoftApManager.Listener mSoftApListener;
+    WifiStateMachinePrime mWifiStateMachinePrime;
+
+    /**
+     * Set up the test environment.
+     */
+    @Before
+    public void setUp() throws Exception {
+        Log.d(TAG, "Setting up ...");
+
+        MockitoAnnotations.initMocks(this);
+        mLooper = new TestLooper();
+
+        mWifiInjector = mock(WifiInjector.class);
+        mWifiStateMachinePrime = createWifiStateMachinePrime();
+    }
+
+    private WifiStateMachinePrime createWifiStateMachinePrime() {
+        when(mWifiInjector.makeWificond()).thenReturn(null);
+        return new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
+    }
+
+    /**
+     * Clean up after tests - explicitly set tested object to null.
+     */
+    @After
+    public void cleanUp() throws Exception {
+        mWifiStateMachinePrime = null;
+    }
+
+    private void enterSoftApActiveMode() throws Exception {
+        enterSoftApActiveMode(null);
+    }
+
+    /**
+     * Helper method to enter the SoftApActiveMode for WifiStateMachinePrime.
+     *
+     * This method puts the test object into the correct state and verifies steps along the way.
+     */
+    private void enterSoftApActiveMode(WifiConfiguration wifiConfig) throws Exception {
+        String fromState = mWifiStateMachinePrime.getCurrentMode();
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createApInterface()).thenReturn(mApInterface);
+        doAnswer(
+                new Answer<Object>() {
+                    public SoftApManager answer(InvocationOnMock invocation) {
+                        Object[] args = invocation.getArguments();
+                        assertEquals(mNMService, (INetworkManagementService) args[0]);
+                        mSoftApListener = (SoftApManager.Listener) args[1];
+                        assertEquals(mApInterface, (IApInterface) args[2]);
+                        assertEquals(wifiConfig, (WifiConfiguration) args[3]);
+                        return mSoftApManager;
+                    }
+                }).when(mWifiInjector).makeSoftApManager(any(INetworkManagementService.class),
+                                                         any(SoftApManager.Listener.class),
+                                                         any(IApInterface.class),
+                                                         any());
+        mWifiStateMachinePrime.enterSoftAPMode(wifiConfig);
+        mLooper.dispatchAll();
+        Log.e("WifiStateMachinePrimeTest", "check fromState: " + fromState);
+        if (!fromState.equals(WIFI_DISABLED_STATE_STRING)) {
+            verify(mWificond).tearDownInterfaces();
+        }
+        assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+        verify(mSoftApManager).start();
+    }
+
+    /**
+     * Test that when a new instance of WifiStateMachinePrime is created, any existing interfaces in
+     * the retrieved Wificond instance are cleaned up.
+     * Expectations:  When the new WifiStateMachinePrime instance is created a call to
+     * Wificond.tearDownInterfaces() is made.
+     */
+    @Test
+    public void testWificondExistsOnStartup() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        WifiStateMachinePrime testWifiStateMachinePrime =
+                new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
+        verify(mWificond).tearDownInterfaces();
+    }
+
+    /**
+     * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from the
+     * WifiDisabled state.
+     */
+    @Test
+    public void testEnterSoftApModeFromDisabled() throws Exception {
+        enterSoftApActiveMode();
+    }
+
+    /**
+     * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from another state.
+     * Expectations: When going from one state to another, any interfaces that are still up are torn
+     * down.
+     */
+    @Test
+    public void testEnterSoftApModeFromDifferentState() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        mWifiStateMachinePrime.enterClientMode();
+        mLooper.dispatchAll();
+        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+        enterSoftApActiveMode();
+    }
+
+    /**
+     * Test that we can disable wifi fully from the SoftApModeActiveState.
+     */
+    @Test
+    public void testDisableWifiFromSoftApModeActiveState() throws Exception {
+        enterSoftApActiveMode();
+
+        mWifiStateMachinePrime.disableWifi();
+        mLooper.dispatchAll();
+        verify(mSoftApManager).stop();
+        verify(mWificond).tearDownInterfaces();
+        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+    }
+
+    /**
+     * Test that we can disable wifi fully from the SoftApModeState.
+     */
+    @Test
+    public void testDisableWifiFromSoftApModeState() throws Exception {
+        // Use a failure getting wificond to stay in the SoftAPModeState
+        when(mWifiInjector.makeWificond()).thenReturn(null);
+        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+
+        mWifiStateMachinePrime.disableWifi();
+        mLooper.dispatchAll();
+        // mWificond will be null due to this test, no call to tearDownInterfaces here.
+        assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+    }
+
+    /**
+     * Test that we can switch from SoftApActiveMode to another mode.
+     * Expectation: When switching out of SoftApModeActiveState we stop the SoftApManager and tear
+     * down existing interfaces.
+     */
+    @Test
+    public void testSwitchModeWhenSoftApActiveMode() throws Exception {
+        enterSoftApActiveMode();
+
+        mWifiStateMachinePrime.enterClientMode();
+        mLooper.dispatchAll();
+        verify(mSoftApManager).stop();
+        verify(mWificond).tearDownInterfaces();
+        assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+    }
+
+    /**
+     * Test that we do not attempt to enter SoftApModeActiveState when we cannot get a reference to
+     * wificond.
+     * Expectations: After a failed attempt to get wificond from WifiInjector, we should remain in
+     * the SoftApModeState.
+     */
+    @Test
+    public void testWificondNullWhenSwitchingToApMode() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(null);
+        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+    }
+
+    /**
+     * Test that we do not attempt to enter SoftApModeActiveState when we cannot get an ApInterface
+     * from wificond.
+     * Expectations: After a failed attempt to get an ApInterface from WifiInjector, we should
+     * remain in the SoftApModeState.
+     */
+    @Test
+    public void testAPInterfaceFailedWhenSwitchingToApMode() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createApInterface()).thenReturn(null);
+        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+    }
+
+    /**
+     * Test that we do can enter the SoftApModeActiveState if we are already in the SoftApModeState.
+     * Expectations: We should exit the current SoftApModeState and re-enter before successfully
+     * entering the SoftApModeActiveState.
+     */
+    @Test
+    public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createApInterface()).thenReturn(null);
+        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+
+        enterSoftApActiveMode();
+    }
+
+    /**
+     * Test that we return to the SoftApModeState after a failure is reported when in the
+     * SoftApModeActiveState.
+     * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager.
+     */
+    @Test
+    public void testSoftApFailureWhenActive() throws Exception {
+        enterSoftApActiveMode();
+        // now inject failure through the SoftApManager.Listener
+        mSoftApListener.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+        verify(mSoftApManager).stop();
+    }
+
+    /**
+     * Test that we return to the SoftApModeState after the SoftApManager is stopped in the
+     * SoftApModeActiveState.
+     * Expectations: We should exit the SoftApModeActiveState and stop the SoftApManager.
+     */
+    @Test
+    public void testSoftApDisabledWhenActive() throws Exception {
+        enterSoftApActiveMode();
+        // now inject failure through the SoftApManager.Listener
+        mSoftApListener.onStateChanged(WifiManager.WIFI_AP_STATE_FAILED, 0);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+        verify(mSoftApManager).stop();
+    }
+
+    /**
+     * Test that a config passed in to the call to enterSoftApMode is used to create the new
+     * SoftApManager.
+     * Expectations: We should create a SoftApManager in WifiInjector with the config passed in to
+     * WifiStateMachinePrime to switch to SoftApMode.
+     */
+    @Test
+    public void testConfigIsPassedToWifiInjector() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "ThisIsAConfig";
+        enterSoftApActiveMode(config);
+    }
+
+    /**
+     * Test that when enterSoftAPMode is called with a null config, we pass a null config to
+     * WifiInjector.makeSoftApManager.
+     *
+     * Passing a null config to SoftApManager indicates that the default config should be used.
+     *
+     * Expectations: WifiInjector should be called with a null config.
+     */
+    @Test
+    public void testNullConfigIsPassedToWifiInjector() throws Exception {
+        enterSoftApActiveMode(null);
+    }
+
+    /**
+     * Test that the proper config is used if a prior attempt fails without using the config.
+     * Expectations: A call to start softap with a null config fails, but a second call has a set
+     * config - this second call should use the correct config.
+     */
+    @Test
+    public void testNullConfigFailsSecondCallWithConfigSuccessful() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createApInterface()).thenReturn(null);
+        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "ThisIsAConfig";
+        enterSoftApActiveMode(config);
+    }
+
+    /**
+     * Test that a failed call to start softap with a valid config has the config saved for future
+     * calls to enable softap.
+     *
+     * Expectations: A call to start SoftAPMode with a config should write out the config if we
+     * did not create a SoftApManager.
+     */
+    @Test
+    public void testValidConfigIsSavedOnFailureToStart() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(null);
+        when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "ThisIsAConfig";
+        mWifiStateMachinePrime.enterSoftAPMode(config);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+        verify(mWifiApConfigStore).setApConfiguration(eq(config));
+    }
+
+    /**
+     * Thest that two calls to switch to SoftAPMode in succession ends up with the correct config.
+     *
+     * Expectation: we should end up in SoftAPMode state configured with the second config.
+     */
+    @Test
+    public void testStartSoftApModeTwiceWithTwoConfigs() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createApInterface()).thenReturn(mApInterface);
+        when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
+        WifiConfiguration config1 = new WifiConfiguration();
+        config1.SSID = "ThisIsAConfig";
+        WifiConfiguration config2 = new WifiConfiguration();
+        config2.SSID = "ThisIsASecondConfig";
+
+        when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
+                                             any(SoftApManager.Listener.class),
+                                             any(IApInterface.class),
+                                             eq(config1)))
+                .thenReturn(mSoftApManager);
+        when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
+                                             any(SoftApManager.Listener.class),
+                                             any(IApInterface.class),
+                                             eq(config2)))
+                .thenReturn(mSoftApManager);
+
+
+        mWifiStateMachinePrime.enterSoftAPMode(config1);
+        mWifiStateMachinePrime.enterSoftAPMode(config2);
+        mLooper.dispatchAll();
+        assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
+    }
+
+    /**
+     * Test that we safely disable wifi if it is already disabled.
+     * Expectations: We should not interact with wificond since we should have already cleaned up
+     * everything.
+     */
+    @Test
+    public void disableWifiWhenAlreadyOff() throws Exception {
+        verifyNoMoreInteractions(mWificond);
+        mWifiStateMachinePrime.disableWifi();
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index f3fe0cd..2a30b67 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -16,15 +16,29 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+
+import static com.android.server.wifi.LocalOnlyHotspotRequestInfo.HOTSPOT_NO_ERROR;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.*;
 
 import android.app.ActivityManager;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.app.test.TestAlarmManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
@@ -33,12 +47,19 @@
 import android.net.LinkProperties;
 import android.net.dhcp.DhcpClient;
 import android.net.ip.IpManager;
+import android.net.wifi.IApInterface;
+import android.net.wifi.IClientInterface;
+import android.net.wifi.IWificond;
 import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.HomeSp;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -53,31 +74,33 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
+import android.os.RemoteException;
 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;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Base64;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.R;
 import com.android.internal.app.IBatteryStats;
 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.hotspot2.PasspointManager;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -88,11 +111,11 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiStateMachine}.
@@ -107,6 +130,8 @@
             (ActivityManager.isLowRamDeviceStatic()
                     ? WifiStateMachine.NUM_LOG_RECS_VERBOSE_LOW_MEMORY
                     : WifiStateMachine.NUM_LOG_RECS_VERBOSE);
+    private static final int WPS_SUPPLICANT_NETWORK_ID = 5;
+    private static final int WPS_FRAMEWORK_NETWORK_ID = 10;
     private static final String DEFAULT_TEST_SSID = "\"GoogleGuest\"";
 
     private long mBinderToken;
@@ -157,9 +182,6 @@
     private FrameworkFacade getFrameworkFacade() throws Exception {
         FrameworkFacade facade = mock(FrameworkFacade.class);
 
-        when(facade.makeWifiScanner(any(Context.class), any(Looper.class)))
-                .thenReturn(mWifiScanner);
-        when(facade.makeBaseLogger()).thenReturn(mock(BaseWifiLogger.class));
         when(facade.getService(Context.NETWORKMANAGEMENT_SERVICE)).thenReturn(
                 mockWithInterfaces(IBinder.class, INetworkManagementService.class));
 
@@ -169,20 +191,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));
@@ -199,20 +217,6 @@
                     }
                 });
 
-        when(facade.checkUidPermission(eq(android.Manifest.permission.OVERRIDE_WIFI_CONFIG),
-                anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
-
-        when(facade.makeWifiConfigManager(any(Context.class), any(WifiNative.class),
-                any(FrameworkFacade.class), any(Clock.class),
-                any(UserManager.class), any(KeyStore.class))).then(new AnswerWithArguments() {
-            public WifiConfigManager answer(Context context, WifiNative wifiNative,
-                    FrameworkFacade frameworkFacade, Clock clock,
-                    UserManager userManager, KeyStore keyStore){
-                mWifiConfigManager = new WifiConfigManager(context, wifiNative, frameworkFacade,
-                        clock, userManager, keyStore);
-                return mWifiConfigManager;
-            }
-        });
         return facade;
     }
 
@@ -239,7 +243,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());
 
@@ -311,19 +315,22 @@
     static final String   sHexSSID = sWifiSsid.getHexString().replace("0x", "").replace("22", "");
     static final String   sBSSID = "01:02:03:04:05:06";
     static final int      sFreq = 2437;
+    static final String   WIFI_IFACE_NAME = "mockWlan";
 
     WifiStateMachine mWsm;
     HandlerThread mWsmThread;
     HandlerThread mP2pThread;
     HandlerThread mSyncThread;
     AsyncChannel  mWsmAsyncChannel;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
     TestIpManager mTestIpManager;
-    MockLooper mLooper;
-    WifiConfigManager mWifiConfigManager;
+    TestLooper mLooper;
+    Context mContext;
 
-    @Mock WifiNative mWifiNative;
+    final ArgumentCaptor<SoftApManager.Listener> mSoftApManagerListenerCaptor =
+                    ArgumentCaptor.forClass(SoftApManager.Listener.class);
+
     @Mock WifiScanner mWifiScanner;
     @Mock SupplicantStateTracker mSupplicantStateTracker;
     @Mock WifiMetrics mWifiMetrics;
@@ -335,6 +342,18 @@
     @Mock WifiLastResortWatchdog mWifiLastResortWatchdog;
     @Mock PropertyService mPropertyService;
     @Mock BuildProperties mBuildProperties;
+    @Mock IWificond mWificond;
+    @Mock IApInterface mApInterface;
+    @Mock IClientInterface mClientInterface;
+    @Mock IBinder mApInterfaceBinder;
+    @Mock IBinder mClientInterfaceBinder;
+    @Mock WifiConfigManager mWifiConfigManager;
+    @Mock WifiNative mWifiNative;
+    @Mock WifiConnectivityManager mWifiConnectivityManager;
+    @Mock SoftApManager mSoftApManager;
+    @Mock WifiStateTracker mWifiStateTracker;
+    @Mock PasspointManager mPasspointManager;
+    @Mock SelfRecovery mSelfRecovery;
 
     public WifiStateMachineTest() throws Exception {
     }
@@ -344,33 +363,59 @@
         Log.d(TAG, "Setting up ...");
 
         // Ensure looper exists
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         MockitoAnnotations.initMocks(this);
 
         /** uncomment this to enable logs from WifiStateMachines */
         // enableDebugLogs();
 
-        TestUtil.installWlanWifiNative(mWifiNative);
         mWifiMonitor = new MockWifiMonitor();
         when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
-        when(mWifiInjector.getClock()).thenReturn(mock(Clock.class));
+        when(mWifiInjector.getClock()).thenReturn(new Clock());
         when(mWifiInjector.getWifiLastResortWatchdog()).thenReturn(mWifiLastResortWatchdog);
         when(mWifiInjector.getPropertyService()).thenReturn(mPropertyService);
         when(mWifiInjector.getBuildProperties()).thenReturn(mBuildProperties);
         when(mWifiInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
+        when(mWifiInjector.getWifiBackupRestore()).thenReturn(mock(WifiBackupRestore.class));
+        when(mWifiInjector.makeWifiDiagnostics(anyObject())).thenReturn(
+                mock(BaseWifiDiagnostics.class));
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWifiInjector.getWifiConfigManager()).thenReturn(mWifiConfigManager);
+        when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
+        when(mWifiInjector.makeWifiConnectivityManager(any(WifiInfo.class), anyBoolean()))
+                .thenReturn(mWifiConnectivityManager);
+        when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
+                mSoftApManagerListenerCaptor.capture(), any(IApInterface.class),
+                any(WifiConfiguration.class)))
+                .thenReturn(mSoftApManager);
+        when(mWifiInjector.getPasspointManager()).thenReturn(mPasspointManager);
+        when(mWifiInjector.getWifiStateTracker()).thenReturn(mWifiStateTracker);
+        when(mWifiInjector.getWifiMonitor()).thenReturn(mWifiMonitor);
+        when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
+        when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
+
+        when(mWifiNative.setupForClientMode()).thenReturn(mClientInterface);
+        when(mWifiNative.setupForSoftApMode()).thenReturn(mApInterface);
+        when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
+        when(mWifiNative.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
+        when(mWifiNative.enableSupplicant()).thenReturn(true);
+        when(mWifiNative.disableSupplicant()).thenReturn(true);
+        when(mWifiNative.getFrameworkNetworkId(anyInt())).thenReturn(0);
+
+
         FrameworkFacade factory = getFrameworkFacade();
-        Context context = getContext();
+        mContext = getContext();
 
         Resources resources = getMockResources();
-        when(context.getResources()).thenReturn(resources);
+        when(mContext.getResources()).thenReturn(resources);
 
-        when(factory.getIntegerSetting(context,
+        when(factory.getIntegerSetting(mContext,
                 Settings.Global.WIFI_FREQUENCY_BAND,
                 WifiManager.WIFI_FREQUENCY_BAND_AUTO)).thenReturn(
                 WifiManager.WIFI_FREQUENCY_BAND_AUTO);
 
-        when(factory.makeApConfigStore(eq(context), eq(mBackupManagerProxy)))
+        when(factory.makeApConfigStore(eq(mContext), eq(mBackupManagerProxy)))
                 .thenReturn(mApConfigStore);
 
         when(factory.makeSupplicantStateTracker(
@@ -383,8 +428,11 @@
                 new UserInfo(UserHandle.USER_SYSTEM, "owner", 0),
                 new UserInfo(11, "managed profile", 0)));
 
-        mWsm = new WifiStateMachine(context, factory, mLooper.getLooper(),
-            mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode);
+        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
+        when(mClientInterface.asBinder()).thenReturn(mClientInterfaceBinder);
+
+        mWsm = new WifiStateMachine(mContext, factory, mLooper.getLooper(),
+            mUserManager, mWifiInjector, mBackupManagerProxy, mCountryCode, mWifiNative);
         mWsmThread = getWsmHandlerThread(mWsm);
 
         final AsyncChannel channel = new AsyncChannel();
@@ -406,7 +454,7 @@
             }
         };
 
-        channel.connect(context, handler, mWsm.getMessenger());
+        channel.connect(mContext, handler, mWsm.getMessenger());
         mLooper.dispatchAll();
         /* Now channel is supposed to be connected */
 
@@ -438,79 +486,217 @@
     }
 
     @Test
-    public void loadComponents() throws Exception {
-        when(mWifiNative.loadDriver()).thenReturn(true);
-        when(mWifiNative.startHal()).thenReturn(true);
-        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
-        mWsm.setSupplicantRunning(true);
-        mLooper.dispatchAll();
+    public void loadComponentsInStaMode() throws Exception {
+        startSupplicantAndDispatchMessages();
 
-        assertEquals("SupplicantStartingState", getCurrentState().getName());
-
-        when(mWifiNative.setBand(anyInt())).thenReturn(true);
-        when(mWifiNative.setDeviceName(anyString())).thenReturn(true);
-        when(mWifiNative.setManufacturer(anyString())).thenReturn(true);
-        when(mWifiNative.setModelName(anyString())).thenReturn(true);
-        when(mWifiNative.setModelNumber(anyString())).thenReturn(true);
-        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
-        when(mWifiNative.setConfigMethods(anyString())).thenReturn(true);
-        when(mWifiNative.setDeviceType(anyString())).thenReturn(true);
-        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
-        when(mWifiNative.setScanningMacOui(any(byte[].class))).thenReturn(true);
-
-        mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
-        mLooper.dispatchAll();
+        verify(mContext).sendStickyBroadcastAsUser(
+                (Intent) argThat(new WifiEnablingStateIntentMatcher()), eq(UserHandle.ALL));
 
         assertEquals("DisconnectedState", getCurrentState().getName());
     }
 
+    private void checkApStateChangedBroadcast(Intent intent, int expectedCurrentState,
+            int expectedPrevState, int expectedErrorCode, String expectedIfaceName,
+            int expectedMode) {
+        int currentState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
+        int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
+        int errorCode = intent.getIntExtra(EXTRA_WIFI_AP_FAILURE_REASON, HOTSPOT_NO_ERROR);
+        String ifaceName = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
+        int mode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+        assertEquals(expectedCurrentState, currentState);
+        assertEquals(expectedPrevState, prevState);
+        assertEquals(expectedErrorCode, errorCode);
+        assertEquals(expectedIfaceName, ifaceName);
+        assertEquals(expectedMode, mode);
+    }
+
+    private void loadComponentsInApMode(int mode) throws Exception {
+        SoftApModeConfiguration config = new SoftApModeConfiguration(mode, new WifiConfiguration());
+        mWsm.setHostApRunning(config, true);
+        mLooper.dispatchAll();
+
+        assertEquals("SoftApState", getCurrentState().getName());
+
+        verify(mSoftApManager).start();
+
+        // reset expectations for mContext due to previously sent AP broadcast
+        reset(mContext);
+
+        // get the SoftApManager.Listener and trigger some updates
+        SoftApManager.Listener listener = mSoftApManagerListenerCaptor.getValue();
+        listener.onStateChanged(WIFI_AP_STATE_ENABLING, 0);
+        listener.onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+        listener.onStateChanged(WIFI_AP_STATE_DISABLING, 0);
+        // note, this will trigger a mode change when TestLooper is dispatched
+        listener.onStateChanged(WIFI_AP_STATE_DISABLED, 0);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(4))
+                .sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL));
+
+        List<Intent> capturedIntents = intentCaptor.getAllValues();
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_ENABLING,
+                WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
+        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_ENABLED,
+                WIFI_AP_STATE_ENABLING, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
+        checkApStateChangedBroadcast(capturedIntents.get(2), WIFI_AP_STATE_DISABLING,
+                WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
+        checkApStateChangedBroadcast(capturedIntents.get(3), WIFI_AP_STATE_DISABLED,
+                WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
+    }
+
     @Test
-    public void loadComponentsFailure() throws Exception {
-        when(mWifiNative.loadDriver()).thenReturn(false);
-        when(mWifiNative.startHal()).thenReturn(false);
-        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(false);
+    public void loadComponentsInApModeForTethering() throws Exception {
+        loadComponentsInApMode(WifiManager.IFACE_IP_MODE_TETHERED);
+    }
 
+    @Test
+    public void loadComponentsInApModeForLOHS() throws Exception {
+        loadComponentsInApMode(WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+    }
+
+    @Test
+    public void shouldRequireSupplicantStartupToLeaveInitialState() throws Exception {
+        when(mWifiNative.enableSupplicant()).thenReturn(false);
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+        // we should not be sending a wifi enabling update
+        verify(mContext, never()).sendStickyBroadcastAsUser(
+                (Intent) argThat(new WifiEnablingStateIntentMatcher()), any());
+    }
+
+    @Test
+    public void shouldRequireWificondToLeaveInitialState() throws Exception {
+        // We start out with valid default values, break them going backwards so that
+        // we test all the bailout cases.
+
+        // ClientInterface dies after creation.
+        doThrow(new RemoteException()).when(mClientInterfaceBinder).linkToDeath(any(), anyInt());
         mWsm.setSupplicantRunning(true);
         mLooper.dispatchAll();
         assertEquals("InitialState", getCurrentState().getName());
 
-        when(mWifiNative.loadDriver()).thenReturn(true);
+        // Failed to even create the client interface.
+        when(mWificond.createClientInterface()).thenReturn(null);
         mWsm.setSupplicantRunning(true);
         mLooper.dispatchAll();
         assertEquals("InitialState", getCurrentState().getName());
 
-        when(mWifiNative.startHal()).thenReturn(true);
+        // Failed to get wificond proxy.
+        when(mWifiInjector.makeWificond()).thenReturn(null);
         mWsm.setSupplicantRunning(true);
         mLooper.dispatchAll();
         assertEquals("InitialState", getCurrentState().getName());
     }
 
-    /**
-     * Test to check that mode changes from WifiController will be properly handled in the
-     * InitialState by WifiStateMachine.
-     */
     @Test
-    public void checkOperationalModeInInitialState() throws Exception {
-        when(mWifiNative.loadDriver()).thenReturn(true);
-        when(mWifiNative.startHal()).thenReturn(true);
-        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
+    public void loadComponentsFailure() throws Exception {
+        when(mWifiNative.enableSupplicant()).thenReturn(false);
 
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+    }
+
+    @Test
+    public void checkInitialStateStickyWhenDisabledMode() throws Exception {
         mLooper.dispatchAll();
         assertEquals("InitialState", getCurrentState().getName());
         assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
 
+        mWsm.setOperationalMode(WifiStateMachine.DISABLED_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.DISABLED_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("InitialState", getCurrentState().getName());
+    }
+
+    @Test
+    public void shouldStartSupplicantWhenConnectModeRequested() throws Exception {
+        // The first time we start out in InitialState, we sit around here.
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+
+        // But if someone tells us to enter connect mode, we start up supplicant
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals("SupplicantStartingState", getCurrentState().getName());
+    }
+
+    /**
+     *  Test that mode changes accurately reflect the value for isWifiEnabled.
+     */
+    @Test
+    public void checkIsWifiEnabledForModeChanges() throws Exception {
+        // Check initial state
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+        assertEquals(WifiManager.WIFI_STATE_DISABLED, mWsm.syncGetWifiState());
+
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+        startSupplicantAndDispatchMessages();
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("ScanModeState", getCurrentState().getName());
+        assertEquals(WifiManager.WIFI_STATE_DISABLED, mWsm.syncGetWifiState());
+        verify(mContext, never()).sendStickyBroadcastAsUser(
+                (Intent) argThat(new WifiEnablingStateIntentMatcher()), any());
+
+
+        // switch to connect mode and verify wifi is reported as enabled
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals(WifiManager.WIFI_STATE_ENABLED, mWsm.syncGetWifiState());
+        verify(mContext).sendStickyBroadcastAsUser(
+                (Intent) argThat(new WifiEnablingStateIntentMatcher()), eq(UserHandle.ALL));
+
+        // reset the expectations on mContext since we did get an expected broadcast, but we should
+        // not on the next transition
+        reset(mContext);
+
+        // now go back to scan mode with "wifi disabled" to verify the reported wifi state.
         mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
         mLooper.dispatchAll();
         assertEquals(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE,
-                mWsm.getOperationalModeForTest());
+                     mWsm.getOperationalModeForTest());
+        assertEquals("ScanModeState", getCurrentState().getName());
+        assertEquals(WifiManager.WIFI_STATE_DISABLED, mWsm.syncGetWifiState());
+        verify(mContext, never()).sendStickyBroadcastAsUser(
+                (Intent) argThat(new WifiEnablingStateIntentMatcher()), any());
 
-        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+        // now go to AP mode
+        mWsm.setSupplicantRunning(false);
+        mWsm.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
+        mWsm.sendMessage(WifiMonitor.SUP_DISCONNECTION_EVENT);
+        SoftApModeConfiguration config = new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, new WifiConfiguration());
+        mWsm.setHostApRunning(config, true);
         mLooper.dispatchAll();
-        assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("SoftApState", getCurrentState().getName());
+        assertEquals(WifiManager.WIFI_STATE_DISABLED, mWsm.syncGetWifiState());
+        verify(mContext, never()).sendStickyBroadcastAsUser(
+                (Intent) argThat(new WifiEnablingStateIntentMatcher()), any());
+    }
 
-        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        mLooper.dispatchAll();
-        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+    private class WifiEnablingStateIntentMatcher implements ArgumentMatcher<Intent> {
+        @Override
+        public boolean matches(Intent intent) {
+            if (WifiManager.WIFI_STATE_CHANGED_ACTION != intent.getAction()) {
+                // not the correct type
+                return false;
+            }
+            return WifiManager.WIFI_STATE_ENABLING
+                    == intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                                          WifiManager.WIFI_STATE_DISABLED);
+        }
     }
 
     /**
@@ -519,10 +705,6 @@
      */
     @Test
     public void checkStartInCorrectStateAfterChangingInitialState() throws Exception {
-        when(mWifiNative.loadDriver()).thenReturn(true);
-        when(mWifiNative.startHal()).thenReturn(true);
-        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
-
         // Check initial state
         mLooper.dispatchAll();
         assertEquals("InitialState", getCurrentState().getName());
@@ -534,10 +716,74 @@
         assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
 
         // Start supplicant so we move to the next state
+        startSupplicantAndDispatchMessages();
+
+        assertEquals("ScanModeState", getCurrentState().getName());
+        verify(mContext, never()).sendStickyBroadcastAsUser(
+                (Intent) argThat(new WifiEnablingStateIntentMatcher()), any());
+    }
+
+    /**
+     * Verifies that configs can be removed when in client mode.
+     */
+    @Test
+    public void canRemoveNetworkConfigInClientMode() throws Exception {
+        boolean result;
+        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
+        initializeAndAddNetworkAndVerifySuccess();
+        mLooper.startAutoDispatch();
+        result = mWsm.syncRemoveNetwork(mWsmAsyncChannel, 0);
+        mLooper.stopAutoDispatch();
+        assertTrue(result);
+    }
+
+    /**
+     * Verifies that configs can be removed when not in client mode.
+     */
+    @Test
+    public void canRemoveNetworkConfigWhenWifiDisabled() {
+        boolean result;
+        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
+        mLooper.startAutoDispatch();
+        result = mWsm.syncRemoveNetwork(mWsmAsyncChannel, 0);
+        mLooper.stopAutoDispatch();
+
+        assertTrue(result);
+        verify(mWifiConfigManager).removeNetwork(anyInt(), anyInt());
+    }
+
+    /**
+     * Verifies that configs can be forgotten when in client mode.
+     */
+    @Test
+    public void canForgetNetworkConfigInClientMode() throws Exception {
+        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
+        initializeAndAddNetworkAndVerifySuccess();
+        mWsm.sendMessage(WifiManager.FORGET_NETWORK, 0, MANAGED_PROFILE_UID);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).removeNetwork(anyInt(), anyInt());
+    }
+
+    /**
+     * Verifies that configs can be removed when not in client mode.
+     */
+    @Test
+    public void canForgetNetworkConfigWhenWifiDisabled() throws Exception {
+        when(mWifiConfigManager.removeNetwork(eq(0), anyInt())).thenReturn(true);
+        mWsm.sendMessage(WifiManager.FORGET_NETWORK, 0, MANAGED_PROFILE_UID);
+        mLooper.dispatchAll();
+        verify(mWifiConfigManager).removeNetwork(anyInt(), anyInt());
+    }
+
+    /**
+     * Helper method to move through SupplicantStarting and SupplicantStarted states.
+     */
+    private void startSupplicantAndDispatchMessages() throws Exception {
         mWsm.setSupplicantRunning(true);
         mLooper.dispatchAll();
+
         assertEquals("SupplicantStartingState", getCurrentState().getName());
-        when(mWifiNative.setBand(anyInt())).thenReturn(true);
+
         when(mWifiNative.setDeviceName(anyString())).thenReturn(true);
         when(mWifiNative.setManufacturer(anyString())).thenReturn(true);
         when(mWifiNative.setModelName(anyString())).thenReturn(true);
@@ -550,77 +796,24 @@
 
         mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
         mLooper.dispatchAll();
-
-        assertEquals("ScanModeState", getCurrentState().getName());
-    }
-
-    private void addNetworkAndVerifySuccess() throws Exception {
-        addNetworkAndVerifySuccess(false);
     }
 
     private void addNetworkAndVerifySuccess(boolean isHidden) throws Exception {
-        loadComponents();
-
-        final HashMap<String, String> nameToValue = new HashMap<String, String>();
-
-        when(mWifiNative.addNetwork()).thenReturn(0);
-        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString()))
-                .then(new AnswerWithArguments() {
-                    public boolean answer(int netId, String name, String value) {
-                        if (netId != 0) {
-                            Log.d(TAG, "Can't set var " + name + " for " + netId);
-                            return false;
-                        }
-
-                        Log.d(TAG, "Setting var " + name + " to " + value + " for " + netId);
-                        nameToValue.put(name, value);
-                        return true;
-                    }
-                });
-
-        when(mWifiNative.setNetworkExtra(anyInt(), anyString(), (Map<String, String>) anyObject()))
-                .then(new AnswerWithArguments() {
-                    public boolean answer(int netId, String name, Map<String, String> values) {
-                        if (netId != 0) {
-                            Log.d(TAG, "Can't set extra " + name + " for " + netId);
-                            return false;
-                        }
-
-                        Log.d(TAG, "Setting extra for " + netId);
-                        return true;
-                    }
-                });
-
-        when(mWifiNative.getNetworkVariable(anyInt(), anyString()))
-                .then(new AnswerWithArguments() {
-                    public String answer(int netId, String name) throws Throwable {
-                        if (netId != 0) {
-                            Log.d(TAG, "Can't find var " + name + " for " + netId);
-                            return null;
-                        }
-                        String value = nameToValue.get(name);
-                        if (value != null) {
-                            Log.d(TAG, "Returning var " + name + " to " + value + " for " + netId);
-                        } else {
-                            Log.d(TAG, "Can't find var " + name + " for " + netId);
-                        }
-                        return value;
-                    }
-                });
-
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = sSSID;
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
         config.hiddenSSID = isHidden;
+
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(0));
+        when(mWifiConfigManager.getSavedNetworks()).thenReturn(Arrays.asList(config));
+        when(mWifiConfigManager.getConfiguredNetwork(0)).thenReturn(config);
+
         mLooper.startAutoDispatch();
         mWsm.syncAddOrUpdateNetwork(mWsmAsyncChannel, config);
         mLooper.stopAutoDispatch();
 
-        verify(mWifiNative).addNetwork();
-        verify(mWifiNative).setNetworkVariable(0, "ssid", sHexSSID);
-        if (isHidden) {
-            verify(mWifiNative).setNetworkVariable(0, "scan_ssid", Integer.toString(1));
-        }
+        verify(mWifiConfigManager).addOrUpdateNetwork(eq(config), anyInt());
 
         mLooper.startAutoDispatch();
         List<WifiConfiguration> configs = mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel);
@@ -632,175 +825,13 @@
         assertTrue(config2.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE));
     }
 
-    private void addNetworkAndVerifyFailure() throws Exception {
-        loadComponents();
-
-        final WifiConfiguration config = new WifiConfiguration();
-        config.SSID = sSSID;
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-
-        mLooper.startAutoDispatch();
-        mWsm.syncAddOrUpdateNetwork(mWsmAsyncChannel, config);
-        mLooper.stopAutoDispatch();
-
-        verify(mWifiNative, never()).addNetwork();
-        verify(mWifiNative, never()).setNetworkVariable(anyInt(), anyString(), anyString());
-
-        mLooper.startAutoDispatch();
-        assertTrue(mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel).isEmpty());
-        mLooper.stopAutoDispatch();
+    private void initializeAndAddNetworkAndVerifySuccess() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess(false);
     }
 
-    /**
-     * Verifies that the current foreground user is allowed to add a network.
-     */
-    @Test
-    public void addNetworkAsCurrentUser() throws Exception {
-        addNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a managed profile of the current foreground user is allowed to add a network.
-     */
-    @Test
-    public void addNetworkAsCurrentUsersManagedProfile() throws Exception {
-        BinderUtil.setUid(MANAGED_PROFILE_UID);
-        addNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a background user is not allowed to add a network.
-     */
-    @Test
-    public void addNetworkAsOtherUser() throws Exception {
-        BinderUtil.setUid(OTHER_USER_UID);
-        addNetworkAndVerifyFailure();
-    }
-
-    private void removeNetworkAndVerifySuccess() throws Exception {
-        when(mWifiNative.removeNetwork(0)).thenReturn(true);
-        mLooper.startAutoDispatch();
-        assertTrue(mWsm.syncRemoveNetwork(mWsmAsyncChannel, 0));
-        mLooper.stopAutoDispatch();
-
-        mLooper.startAutoDispatch();
-        assertTrue(mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel).isEmpty());
-        mLooper.stopAutoDispatch();
-    }
-
-    private void removeNetworkAndVerifyFailure() throws Exception {
-        mLooper.startAutoDispatch();
-        assertFalse(mWsm.syncRemoveNetwork(mWsmAsyncChannel, 0));
-        mLooper.stopAutoDispatch();
-
-        mLooper.startAutoDispatch();
-        assertEquals(1, mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel).size());
-        mLooper.stopAutoDispatch();
-        verify(mWifiNative, never()).removeNetwork(anyInt());
-    }
-
-    /**
-     * Verifies that the current foreground user is allowed to remove a network.
-     */
-    @Test
-    public void removeNetworkAsCurrentUser() throws Exception {
-        addNetworkAndVerifySuccess();
-        removeNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a managed profile of the current foreground user is allowed to remove a
-     * network.
-     */
-    @Test
-    public void removeNetworkAsCurrentUsersManagedProfile() throws Exception {
-        addNetworkAndVerifySuccess();
-        BinderUtil.setUid(MANAGED_PROFILE_UID);
-        removeNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a background user is not allowed to remove a network.
-     */
-    @Test
-    public void removeNetworkAsOtherUser() throws Exception {
-        addNetworkAndVerifySuccess();
-        BinderUtil.setUid(OTHER_USER_UID);
-        removeNetworkAndVerifyFailure();
-    }
-
-    private void enableNetworkAndVerifySuccess() throws Exception {
-        when(mWifiNative.selectNetwork(0)).thenReturn(true);
-
-        mLooper.startAutoDispatch();
-        assertTrue(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
-        mLooper.stopAutoDispatch();
-
-        verify(mWifiNative).selectNetwork(0);
-    }
-
-    private void enableNetworkAndVerifyFailure() throws Exception {
-        mLooper.startAutoDispatch();
-        assertFalse(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
-        mLooper.stopAutoDispatch();
-
-        verify(mWifiNative, never()).selectNetwork(anyInt());
-    }
-
-    /**
-     * Verifies that the current foreground user is allowed to enable a network.
-     */
-    @Test
-    public void enableNetworkAsCurrentUser() throws Exception {
-        addNetworkAndVerifySuccess();
-        enableNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a managed profile of the current foreground user is allowed to enable a
-     * network.
-     */
-    @Test
-    public void enableNetworkAsCurrentUsersManagedProfile() throws Exception {
-        addNetworkAndVerifySuccess();
-        BinderUtil.setUid(MANAGED_PROFILE_UID);
-        enableNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a background user is not allowed to enable a network.
-     */
-    @Test
-    public void enableNetworkAsOtherUser() throws Exception {
-        addNetworkAndVerifySuccess();
-        BinderUtil.setUid(OTHER_USER_UID);
-        enableNetworkAndVerifyFailure();
-    }
-
-    private void forgetNetworkAndVerifySuccess() throws Exception {
-        when(mWifiNative.removeNetwork(0)).thenReturn(true);
-        mLooper.startAutoDispatch();
-        final Message result =
-                mWsmAsyncChannel.sendMessageSynchronously(WifiManager.FORGET_NETWORK, 0);
-        mLooper.stopAutoDispatch();
-        assertEquals(WifiManager.FORGET_NETWORK_SUCCEEDED, result.what);
-        result.recycle();
-        mLooper.startAutoDispatch();
-        assertTrue(mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel).isEmpty());
-        mLooper.stopAutoDispatch();
-    }
-
-    private void forgetNetworkAndVerifyFailure() throws Exception {
-        mLooper.startAutoDispatch();
-        final Message result =
-                mWsmAsyncChannel.sendMessageSynchronously(WifiManager.FORGET_NETWORK, 0);
-        mLooper.stopAutoDispatch();
-        assertEquals(WifiManager.FORGET_NETWORK_FAILED, result.what);
-        result.recycle();
-        mLooper.startAutoDispatch();
-        assertEquals(1, mWsm.syncGetConfiguredNetworks(-1, mWsmAsyncChannel).size());
-        mLooper.stopAutoDispatch();
-        verify(mWifiNative, never()).removeNetwork(anyInt());
+    private void initializeAndAddNetworkAndVerifySuccess(boolean isHidden) throws Exception {
+        loadComponentsInStaMode();
+        addNetworkAndVerifySuccess(isHidden);
     }
 
     /**
@@ -821,37 +852,7 @@
         return null;
     }
 
-    /**
-     * Verifies that the current foreground user is allowed to forget a network.
-     */
-    @Test
-    public void forgetNetworkAsCurrentUser() throws Exception {
-        addNetworkAndVerifySuccess();
-        forgetNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a managed profile of the current foreground user is allowed to forget a
-     * network.
-     */
-    @Test
-    public void forgetNetworkAsCurrentUsersManagedProfile() throws Exception {
-        addNetworkAndVerifySuccess();
-        BinderUtil.setUid(MANAGED_PROFILE_UID);
-        forgetNetworkAndVerifySuccess();
-    }
-
-    /**
-     * Verifies that a background user is not allowed to forget a network.
-     */
-    @Test
-    public void forgetNetworkAsOtherUser() throws Exception {
-        addNetworkAndVerifySuccess();
-        BinderUtil.setUid(OTHER_USER_UID);
-        forgetNetworkAndVerifyFailure();
-    }
-
-    private void verifyScan(int band, int reportEvents, Set<Integer> configuredNetworkIds) {
+    private void verifyScan(int band, int reportEvents, Set<String> hiddenNetworkSSIDSet) {
         ArgumentCaptor<WifiScanner.ScanSettings> scanSettingsCaptor =
                 ArgumentCaptor.forClass(WifiScanner.ScanSettings.class);
         ArgumentCaptor<WifiScanner.ScanListener> scanListenerCaptor =
@@ -862,16 +863,16 @@
         assertEquals("band", band, actualSettings.band);
         assertEquals("reportEvents", reportEvents, actualSettings.reportEvents);
 
-        if (configuredNetworkIds == null) {
-            configuredNetworkIds = new HashSet<>();
+        if (hiddenNetworkSSIDSet == null) {
+            hiddenNetworkSSIDSet = new HashSet<>();
         }
-        Set<Integer> actualConfiguredNetworkIds = new HashSet<>();
-        if (actualSettings.hiddenNetworkIds != null) {
-            for (int i = 0; i < actualSettings.hiddenNetworkIds.length; ++i) {
-                actualConfiguredNetworkIds.add(actualSettings.hiddenNetworkIds[i]);
+        Set<String> actualHiddenNetworkSSIDSet = new HashSet<>();
+        if (actualSettings.hiddenNetworks != null) {
+            for (int i = 0; i < actualSettings.hiddenNetworks.length; ++i) {
+                actualHiddenNetworkSSIDSet.add(actualSettings.hiddenNetworks[i].ssid);
             }
         }
-        assertEquals("configured networks", configuredNetworkIds, actualConfiguredNetworkIds);
+        assertEquals("hidden networks", hiddenNetworkSSIDSet, actualHiddenNetworkSSIDSet);
 
         when(mWifiNative.getScanResults()).thenReturn(getMockScanResults());
         mWsm.sendMessage(WifiMonitor.SCAN_RESULTS_EVENT);
@@ -884,7 +885,7 @@
 
     @Test
     public void scan() throws Exception {
-        addNetworkAndVerifySuccess();
+        initializeAndAddNetworkAndVerifySuccess();
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mWsm.startScan(-1, 0, null, null);
@@ -897,7 +898,13 @@
 
     @Test
     public void scanWithHiddenNetwork() throws Exception {
-        addNetworkAndVerifySuccess(true);
+        initializeAndAddNetworkAndVerifySuccess(true);
+
+        Set<String> hiddenNetworkSet = new HashSet<>();
+        hiddenNetworkSet.add(sSSID);
+        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList = new ArrayList<>();
+        hiddenNetworkList.add(new WifiScanner.ScanSettings.HiddenNetwork(sSSID));
+        when(mWifiConfigManager.retrieveHiddenNetworkList()).thenReturn(hiddenNetworkList);
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mWsm.startScan(-1, 0, null, null);
@@ -906,21 +913,25 @@
         verifyScan(WifiScanner.WIFI_BAND_BOTH_WITH_DFS,
                 WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
                 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT,
-                mWifiConfigManager.getHiddenConfiguredNetworkIds());
+                hiddenNetworkSet);
     }
 
     @Test
     public void connect() throws Exception {
-        addNetworkAndVerifySuccess();
+        initializeAndAddNetworkAndVerifySuccess();
+        when(mWifiConfigManager.enableNetwork(eq(0), eq(true), anyInt())).thenReturn(true);
+        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(0), anyInt())).thenReturn(true);
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mLooper.dispatchAll();
+        verify(mWifiNative).removeAllNetworks();
 
         mLooper.startAutoDispatch();
-        mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
+        assertTrue(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
         mLooper.stopAutoDispatch();
 
-        verify(mWifiNative).selectNetwork(0);
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
+        verify(mWifiConnectivityManager).setUserConnectChoice(eq(0));
 
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
         mLooper.dispatchAll();
@@ -944,8 +955,101 @@
     }
 
     @Test
+    public void connectWithNoEnablePermission() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        when(mWifiConfigManager.enableNetwork(eq(0), eq(true), anyInt())).thenReturn(false);
+        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(0), anyInt())).thenReturn(false);
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        verify(mWifiNative).removeAllNetworks();
+
+        mLooper.startAutoDispatch();
+        assertTrue(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
+        verify(mWifiConnectivityManager, never()).setUserConnectChoice(eq(0));
+
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mLooper.dispatchAll();
+
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+
+        assertEquals("ObtainingIpState", getCurrentState().getName());
+
+        DhcpResults dhcpResults = new DhcpResults();
+        dhcpResults.setGateway("1.2.3.4");
+        dhcpResults.setIpAddress("192.168.1.100", 0);
+        dhcpResults.addDns("8.8.8.8");
+        dhcpResults.setLeaseDuration(3600);
+
+        mTestIpManager.injectDhcpSuccess(dhcpResults);
+        mLooper.dispatchAll();
+
+        assertEquals("ConnectedState", getCurrentState().getName());
+    }
+
+    @Test
+    public void enableWithInvalidNetworkId() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+        when(mWifiConfigManager.getConfiguredNetwork(eq(0))).thenReturn(null);
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        verify(mWifiNative).removeAllNetworks();
+
+        mLooper.startAutoDispatch();
+        assertFalse(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiConfigManager, never()).enableNetwork(eq(0), eq(true), anyInt());
+        verify(mWifiConfigManager, never()).checkAndUpdateLastConnectUid(eq(0), anyInt());
+    }
+
+    /**
+     * If caller tries to connect to a network that is already connected, the connection request
+     * should succeed.
+     *
+     * Test: Create and connect to a network, then try to reconnect to the same network. Verify
+     * that connection request returns with CONNECT_NETWORK_SUCCEEDED.
+     */
+    @Test
+    public void reconnectToConnectedNetwork() throws Exception {
+        initializeAndAddNetworkAndVerifySuccess();
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        verify(mWifiNative).removeAllNetworks();
+
+        mLooper.startAutoDispatch();
+        mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
+
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mLooper.dispatchAll();
+
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+
+        assertEquals("ObtainingIpState", getCurrentState().getName());
+
+        // try to reconnect
+        mLooper.startAutoDispatch();
+        Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.CONNECT_NETWORK, 0);
+        mLooper.stopAutoDispatch();
+
+        assertEquals(WifiManager.CONNECT_NETWORK_SUCCEEDED, reply.what);
+    }
+
+    @Test
     public void testDhcpFailure() throws Exception {
-        addNetworkAndVerifySuccess();
+        initializeAndAddNetworkAndVerifySuccess();
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mLooper.dispatchAll();
@@ -954,7 +1058,7 @@
         mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
         mLooper.stopAutoDispatch();
 
-        verify(mWifiNative).selectNetwork(0);
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
 
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
         mLooper.dispatchAll();
@@ -973,7 +1077,7 @@
 
     @Test
     public void testBadNetworkEvent() throws Exception {
-        addNetworkAndVerifySuccess();
+        initializeAndAddNetworkAndVerifySuccess();
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mLooper.dispatchAll();
@@ -982,7 +1086,7 @@
         mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true);
         mLooper.stopAutoDispatch();
 
-        verify(mWifiNative).selectNetwork(0);
+        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
 
         mWsm.sendMessage(WifiMonitor.NETWORK_DISCONNECTION_EVENT, 0, 0, sBSSID);
         mLooper.dispatchAll();
@@ -1019,21 +1123,6 @@
     }
 
     /**
-     * WifiConfigurations default to HasEverConnected to false,  creating and adding a config should
-     * not update this value to true.
-     *
-     * Test: Successfully add a network. Check the config and verify
-     * WifiConfiguration.getHasEverConnected() is false.
-     */
-    @Test
-    public void addNetworkDoesNotSetHasEverConnectedTrue() throws Exception {
-        addNetworkAndVerifySuccess();
-
-        WifiConfiguration checkConfig = getWifiConfigurationForNetwork(DEFAULT_TEST_SSID);
-        assertFalse(checkConfig.getNetworkSelectionStatus().getHasEverConnected());
-    }
-
-    /**
      * Successfully connecting to a network will set WifiConfiguration's value of HasEverConnected
      * to true.
      *
@@ -1043,9 +1132,7 @@
     @Test
     public void setHasEverConnectedTrueOnConnect() throws Exception {
         connect();
-
-        WifiConfiguration checkConfig = getWifiConfigurationForNetwork(DEFAULT_TEST_SSID);
-        assertTrue(checkConfig.getNetworkSelectionStatus().getHasEverConnected());
+        verify(mWifiConfigManager, atLeastOnce()).updateNetworkAfterConnect(0);
     }
 
     /**
@@ -1057,91 +1144,17 @@
     @Test
     public void connectionFailureDoesNotSetHasEverConnectedTrue() throws Exception {
         testDhcpFailure();
-
-        WifiConfiguration checkConfig = getWifiConfigurationForNetwork(DEFAULT_TEST_SSID);
-        assertFalse(checkConfig.getNetworkSelectionStatus().getHasEverConnected());
-    }
-
-    @Test
-    public void handleUserSwitch() throws Exception {
-        assertEquals(UserHandle.USER_SYSTEM, mWifiConfigManager.getCurrentUserId());
-
-        mWsm.handleUserSwitch(10);
-        mLooper.dispatchAll();
-
-        assertEquals(10, mWifiConfigManager.getCurrentUserId());
+        verify(mWifiConfigManager, never()).updateNetworkAfterConnect(0);
     }
 
     @Test
     public void iconQueryTest() throws Exception {
-        /* enable wi-fi */
-        addNetworkAndVerifySuccess();
-
-        long bssid = 0x1234567800FFL;
-        String filename = "iconFileName.png";
-        String command = "REQ_HS20_ICON " + Utils.macToString(bssid) + " " + filename;
-
-        when(mWifiNative.doCustomSupplicantCommand(command)).thenReturn("OK");
-
-        mLooper.startAutoDispatch();
-        boolean result = mWsm.syncQueryPasspointIcon(mWsmAsyncChannel, bssid, filename);
-        mLooper.stopAutoDispatch();
-
-        verify(mWifiNative).doCustomSupplicantCommand(command);
-        assertEquals(true, result);
+        // TODO(b/31065385): Passpoint config management.
     }
 
-    private String createSimChallengeRequest(byte[] challengeValue) {
-        // Produce a base64 encoded length byte + data.
-        byte[] challengeLengthAndValue = new byte[challengeValue.length + 1];
-        challengeLengthAndValue[0] = (byte) challengeValue.length;
-        for (int i = 0; i < challengeValue.length; ++i) {
-            challengeLengthAndValue[i + 1] = challengeValue[i];
-        }
-        return Base64.encodeToString(challengeLengthAndValue, android.util.Base64.NO_WRAP);
-    }
-
-    private String createSimAuthResponse(byte[] sresValue, byte[] kcValue) {
-        // Produce a base64 encoded sres length byte + sres + kc length byte + kc.
-        int overallLength = sresValue.length + kcValue.length + 2;
-        byte[] result = new byte[sresValue.length + kcValue.length + 2];
-        int idx = 0;
-        result[idx++] = (byte) sresValue.length;
-        for (int i = 0; i < sresValue.length; ++i) {
-            result[idx++] = sresValue[i];
-        }
-        result[idx++] = (byte) kcValue.length;
-        for (int i = 0; i < kcValue.length; ++i) {
-            result[idx++] = kcValue[i];
-        }
-        return Base64.encodeToString(result, Base64.NO_WRAP);
-    }
-
-    /** Verifies function getGsmSimAuthResponse method. */
     @Test
-    public void getGsmSimAuthResponseTest() throws Exception {
-        TelephonyManager tm = mock(TelephonyManager.class);
-        final String[] invalidRequests = { null, "", "XXXX" };
-        assertEquals("", mWsm.getGsmSimAuthResponse(invalidRequests, tm));
-
-        final String[] failedRequests = { "5E5F" };
-        when(tm.getIccAuthentication(anyInt(), anyInt(),
-                eq(createSimChallengeRequest(new byte[] { 0x5e, 0x5f })))).thenReturn(null);
-        assertEquals(null, mWsm.getGsmSimAuthResponse(failedRequests, tm));
-
-        when(tm.getIccAuthentication(2, tm.AUTHTYPE_EAP_SIM,
-                createSimChallengeRequest(new byte[] { 0x1a, 0x2b })))
-                .thenReturn(null);
-        when(tm.getIccAuthentication(1, tm.AUTHTYPE_EAP_SIM,
-                createSimChallengeRequest(new byte[] { 0x1a, 0x2b })))
-                .thenReturn(createSimAuthResponse(new byte[] { 0x1D, 0x2C },
-                       new byte[] { 0x3B, 0x4A }));
-        when(tm.getIccAuthentication(1, tm.AUTHTYPE_EAP_SIM,
-                createSimChallengeRequest(new byte[] { 0x01, 0x23 })))
-                .thenReturn(createSimAuthResponse(new byte[] { 0x33, 0x22 },
-                        new byte[] { 0x11, 0x00 }));
-        assertEquals(":3b4a:1d2c:1100:3322", mWsm.getGsmSimAuthResponse(
-                new String[] { "1A2B", "0123" }, tm));
+    public void verboseLogRecSizeIsGreaterThanNormalSize() {
+        assertTrue(LOG_REC_LIMIT_IN_VERBOSE_MODE > WifiStateMachine.NUM_LOG_RECS_NORMAL);
     }
 
     /**
@@ -1149,47 +1162,66 @@
      */
     @Test
     public void normalLogRecSizeIsUsedByDefault() {
-        for (int i = 0; i < WifiStateMachine.NUM_LOG_RECS_NORMAL * 2; i++) {
-            mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
-        }
-        mLooper.dispatchAll();
-        assertEquals(WifiStateMachine.NUM_LOG_RECS_NORMAL, mWsm.getLogRecSize());
+        assertEquals(WifiStateMachine.NUM_LOG_RECS_NORMAL, mWsm.getLogRecMaxSize());
     }
 
     /**
      * Verifies that, in verbose mode, we allow a larger number of log records.
      */
     @Test
-    public void enablingVerboseLoggingIncreasesLogRecSize() {
-        assertTrue(LOG_REC_LIMIT_IN_VERBOSE_MODE > WifiStateMachine.NUM_LOG_RECS_NORMAL);
+    public void enablingVerboseLoggingUpdatesLogRecSize() {
         mWsm.enableVerboseLogging(1);
-        for (int i = 0; i < LOG_REC_LIMIT_IN_VERBOSE_MODE * 2; i++) {
-            mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
-        }
-        mLooper.dispatchAll();
-        assertEquals(LOG_REC_LIMIT_IN_VERBOSE_MODE, mWsm.getLogRecSize());
+        assertEquals(LOG_REC_LIMIT_IN_VERBOSE_MODE, mWsm.getLogRecMaxSize());
     }
 
-    /**
-     * Verifies that moving from verbose mode to normal mode resets the buffer, and limits new
-     * records to a small number of entries.
-     */
     @Test
-    public void disablingVerboseLoggingClearsRecordsAndDecreasesLogRecSize() {
-        mWsm.enableVerboseLogging(1);
-        for (int i = 0; i < LOG_REC_LIMIT_IN_VERBOSE_MODE; i++) {
-            mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
-        }
+    public void disablingVerboseLoggingClearsRecords() {
+        mWsm.sendMessage(WifiStateMachine.CMD_DISCONNECT);
         mLooper.dispatchAll();
-        assertEquals(LOG_REC_LIMIT_IN_VERBOSE_MODE, mWsm.getLogRecSize());
+        assertTrue(mWsm.getLogRecSize() >= 1);
 
         mWsm.enableVerboseLogging(0);
         assertEquals(0, mWsm.getLogRecSize());
-        for (int i = 0; i < LOG_REC_LIMIT_IN_VERBOSE_MODE; i++) {
-            mWsm.sendMessage(WifiStateMachine.CMD_BOOT_COMPLETED);
-        }
+    }
+
+    @Test
+    public void disablingVerboseLoggingUpdatesLogRecSize() {
+        mWsm.enableVerboseLogging(1);
+        mWsm.enableVerboseLogging(0);
+        assertEquals(WifiStateMachine.NUM_LOG_RECS_NORMAL, mWsm.getLogRecMaxSize());
+    }
+
+    @Test
+    public void logRecsIncludeDisconnectCommand() {
+        // There's nothing special about the DISCONNECT command. It's just representative of
+        // "normal" commands.
+        mWsm.sendMessage(WifiStateMachine.CMD_DISCONNECT);
         mLooper.dispatchAll();
-        assertEquals(WifiStateMachine.NUM_LOG_RECS_NORMAL, mWsm.getLogRecSize());
+        assertEquals(1, mWsm.copyLogRecs()
+                .stream()
+                .filter(logRec -> logRec.getWhat() == WifiStateMachine.CMD_DISCONNECT)
+                .count());
+    }
+
+    @Test
+    public void logRecsExcludeRssiPollCommandByDefault() {
+        mWsm.sendMessage(WifiStateMachine.CMD_RSSI_POLL);
+        mLooper.dispatchAll();
+        assertEquals(0, mWsm.copyLogRecs()
+                .stream()
+                .filter(logRec -> logRec.getWhat() == WifiStateMachine.CMD_RSSI_POLL)
+                .count());
+    }
+
+    @Test
+    public void logRecsIncludeRssiPollCommandWhenVerboseLoggingIsEnabled() {
+        mWsm.enableVerboseLogging(1);
+        mWsm.sendMessage(WifiStateMachine.CMD_RSSI_POLL);
+        mLooper.dispatchAll();
+        assertEquals(1, mWsm.copyLogRecs()
+                .stream()
+                .filter(logRec -> logRec.getWhat() == WifiStateMachine.CMD_RSSI_POLL)
+                .count());
     }
 
     /** Verifies that enabling verbose logging sets the hal log property in eng builds. */
@@ -1240,17 +1272,17 @@
     /** Verifies that syncGetSupportedFeatures() masks out capabilities based on system flags. */
     @Test
     public void syncGetSupportedFeatures() {
-        final int featureNan = WifiManager.WIFI_FEATURE_NAN;
+        final int featureAware = WifiManager.WIFI_FEATURE_AWARE;
         final int featureInfra = WifiManager.WIFI_FEATURE_INFRA;
         final int featureD2dRtt = WifiManager.WIFI_FEATURE_D2D_RTT;
         final int featureD2apRtt = WifiManager.WIFI_FEATURE_D2AP_RTT;
 
         assertEquals(0, testGetSupportedFeaturesCase(0, false));
         assertEquals(0, testGetSupportedFeaturesCase(0, true));
-        assertEquals(featureNan | featureInfra,
-                testGetSupportedFeaturesCase(featureNan | featureInfra, false));
-        assertEquals(featureNan | featureInfra,
-                testGetSupportedFeaturesCase(featureNan | featureInfra, true));
+        assertEquals(featureAware | featureInfra,
+                testGetSupportedFeaturesCase(featureAware | featureInfra, false));
+        assertEquals(featureAware | featureInfra,
+                testGetSupportedFeaturesCase(featureAware | featureInfra, true));
         assertEquals(featureInfra | featureD2dRtt,
                 testGetSupportedFeaturesCase(featureInfra | featureD2dRtt, false));
         assertEquals(featureInfra,
@@ -1264,4 +1296,422 @@
         assertEquals(featureInfra,
                 testGetSupportedFeaturesCase(featureInfra | featureD2dRtt | featureD2apRtt, true));
     }
+
+    /**
+     * Verify that syncAddOrUpdatePasspointConfig will redirect calls to {@link PasspointManager}
+     * and returning the result that's returned from {@link PasspointManager}.
+     */
+    @Test
+    public void syncAddOrUpdatePasspointConfig() throws Exception {
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test.com");
+        config.setHomeSp(homeSp);
+
+        when(mPasspointManager.addOrUpdateProvider(config, MANAGED_PROFILE_UID)).thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertTrue(mWsm.syncAddOrUpdatePasspointConfig(
+                mWsmAsyncChannel, config, MANAGED_PROFILE_UID));
+        mLooper.stopAutoDispatch();
+        reset(mPasspointManager);
+
+        when(mPasspointManager.addOrUpdateProvider(config, MANAGED_PROFILE_UID)).thenReturn(false);
+        mLooper.startAutoDispatch();
+        assertFalse(mWsm.syncAddOrUpdatePasspointConfig(
+                mWsmAsyncChannel, config, MANAGED_PROFILE_UID));
+        mLooper.stopAutoDispatch();
+    }
+
+    /**
+     * Verify that syncAddOrUpdatePasspointConfig will redirect calls to {@link PasspointManager}
+     * and returning the result that's returned from {@link PasspointManager} when in client mode.
+     */
+    @Test
+    public void syncAddOrUpdatePasspointConfigInClientMode() throws Exception {
+        loadComponentsInStaMode();
+        syncAddOrUpdatePasspointConfig();
+    }
+
+    /**
+     * Verify that syncRemovePasspointConfig will redirect calls to {@link PasspointManager}
+     * and returning the result that's returned from {@link PasspointManager}.
+     */
+    @Test
+    public void syncRemovePasspointConfig() throws Exception {
+        String fqdn = "test.com";
+        when(mPasspointManager.removeProvider(fqdn)).thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertTrue(mWsm.syncRemovePasspointConfig(mWsmAsyncChannel, fqdn));
+        mLooper.stopAutoDispatch();
+        reset(mPasspointManager);
+
+        when(mPasspointManager.removeProvider(fqdn)).thenReturn(false);
+        mLooper.startAutoDispatch();
+        assertFalse(mWsm.syncRemovePasspointConfig(mWsmAsyncChannel, fqdn));
+        mLooper.stopAutoDispatch();
+    }
+
+    /**
+     * Verify that syncRemovePasspointConfig will redirect calls to {@link PasspointManager}
+     * and returning the result that's returned from {@link PasspointManager} when in client mode.
+     */
+    @Test
+    public void syncRemovePasspointConfigInClientMode() throws Exception {
+        loadComponentsInStaMode();
+        syncRemovePasspointConfig();
+    }
+
+    /**
+     * Verify that syncGetPasspointConfigs will redirect calls to {@link PasspointManager}
+     * and returning the result that's returned from {@link PasspointManager}.
+     */
+    @Test
+    public void syncGetPasspointConfigs() throws Exception {
+        // Setup expected configs.
+        List<PasspointConfiguration> expectedConfigs = new ArrayList<>();
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test.com");
+        config.setHomeSp(homeSp);
+        expectedConfigs.add(config);
+
+        when(mPasspointManager.getProviderConfigs()).thenReturn(expectedConfigs);
+        mLooper.startAutoDispatch();
+        assertEquals(expectedConfigs, mWsm.syncGetPasspointConfigs(mWsmAsyncChannel));
+        mLooper.stopAutoDispatch();
+        reset(mPasspointManager);
+
+        when(mPasspointManager.getProviderConfigs())
+                .thenReturn(new ArrayList<PasspointConfiguration>());
+        mLooper.startAutoDispatch();
+        assertTrue(mWsm.syncGetPasspointConfigs(mWsmAsyncChannel).isEmpty());
+        mLooper.stopAutoDispatch();
+    }
+
+    /**
+     * Verify that syncGetMatchingWifiConfig will redirect calls to {@link PasspointManager}
+     * with expected {@link WifiConfiguration} being returned when in client mode.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void syncGetMatchingWifiConfigInClientMode() throws Exception {
+        loadComponentsInStaMode();
+
+        when(mPasspointManager.getMatchingWifiConfig(any(ScanResult.class))).thenReturn(null);
+        mLooper.startAutoDispatch();
+        assertNull(mWsm.syncGetMatchingWifiConfig(new ScanResult(), mWsmAsyncChannel));
+        mLooper.stopAutoDispatch();
+        reset(mPasspointManager);
+
+        WifiConfiguration expectedConfig = new WifiConfiguration();
+        expectedConfig.SSID = "TestSSID";
+        when(mPasspointManager.getMatchingWifiConfig(any(ScanResult.class)))
+                .thenReturn(expectedConfig);
+        mLooper.startAutoDispatch();
+        WifiConfiguration actualConfig = mWsm.syncGetMatchingWifiConfig(new ScanResult(),
+                mWsmAsyncChannel);
+        mLooper.stopAutoDispatch();
+        assertEquals(expectedConfig.SSID, actualConfig.SSID);
+    }
+
+    /**
+     * Verify that syncGetMatchingWifiConfig will be a no-op and return {@code null} when not in
+     * client mode.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void syncGetMatchingWifiConfigInNonClientMode() throws Exception {
+        mLooper.startAutoDispatch();
+        assertNull(mWsm.syncGetMatchingWifiConfig(new ScanResult(), mWsmAsyncChannel));
+        mLooper.stopAutoDispatch();
+        verify(mPasspointManager, never()).getMatchingWifiConfig(any(ScanResult.class));
+    }
+
+    /**
+     * Verify successful Wps PBC network connection.
+     */
+    @Test
+    public void wpsPbcConnectSuccess() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        when(mWifiNative.startWpsPbc(eq(sBSSID))).thenReturn(true);
+        WpsInfo wpsInfo = new WpsInfo();
+        wpsInfo.setup = WpsInfo.PBC;
+        wpsInfo.BSSID = sBSSID;
+
+        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
+        mLooper.dispatchAll();
+        verify(mWifiNative).startWpsPbc(eq(sBSSID));
+
+        assertEquals("WpsRunningState", getCurrentState().getName());
+
+        setupMocksForWpsNetworkMigration();
+
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        verifyMocksForWpsNetworkMigration();
+    }
+
+    /**
+     * Verify failure in starting Wps PBC network connection.
+     */
+    @Test
+    public void wpsPbcConnectFailure() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        when(mWifiNative.startWpsPbc(eq(sBSSID))).thenReturn(false);
+        WpsInfo wpsInfo = new WpsInfo();
+        wpsInfo.setup = WpsInfo.PBC;
+        wpsInfo.BSSID = sBSSID;
+
+        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
+        mLooper.dispatchAll();
+        verify(mWifiNative).startWpsPbc(eq(sBSSID));
+
+        assertFalse("WpsRunningState".equals(getCurrentState().getName()));
+    }
+
+    /**
+     * Verify successful Wps Pin Display network connection.
+     */
+    @Test
+    public void wpsPinDisplayConnectSuccess() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        when(mWifiNative.startWpsPinDisplay(eq(sBSSID))).thenReturn("34545434");
+        WpsInfo wpsInfo = new WpsInfo();
+        wpsInfo.setup = WpsInfo.DISPLAY;
+        wpsInfo.BSSID = sBSSID;
+
+        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
+        mLooper.dispatchAll();
+        verify(mWifiNative).startWpsPinDisplay(eq(sBSSID));
+
+        assertEquals("WpsRunningState", getCurrentState().getName());
+
+        setupMocksForWpsNetworkMigration();
+
+        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        verifyMocksForWpsNetworkMigration();
+    }
+
+    /**
+     * Verify failure in Wps Pin Display network connection.
+     */
+    @Test
+    public void wpsPinDisplayConnectFailure() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        when(mWifiNative.startWpsPinDisplay(eq(sBSSID))).thenReturn(null);
+        WpsInfo wpsInfo = new WpsInfo();
+        wpsInfo.setup = WpsInfo.DISPLAY;
+        wpsInfo.BSSID = sBSSID;
+
+        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
+        mLooper.dispatchAll();
+        verify(mWifiNative).startWpsPinDisplay(eq(sBSSID));
+
+        assertFalse("WpsRunningState".equals(getCurrentState().getName()));
+    }
+
+    @Test
+    public void handleVendorHalDeath() throws Exception {
+        ArgumentCaptor<WifiNative.VendorHalDeathEventHandler> deathHandlerCapturer =
+                ArgumentCaptor.forClass(WifiNative.VendorHalDeathEventHandler.class);
+        when(mWifiNative.initializeVendorHal(deathHandlerCapturer.capture())).thenReturn(true);
+
+        // Trigger initialize to capture the death handler registration.
+        mLooper.startAutoDispatch();
+        assertTrue(mWsm.syncInitialize(mWsmAsyncChannel));
+        mLooper.stopAutoDispatch();
+
+        verify(mWifiNative).initializeVendorHal(any(WifiNative.VendorHalDeathEventHandler.class));
+        WifiNative.VendorHalDeathEventHandler deathHandler = deathHandlerCapturer.getValue();
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        // Now trigger the death notification.
+        deathHandler.onDeath();
+        mLooper.dispatchAll();
+
+        verify(mWifiMetrics).incrementNumHalCrashes();
+        verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_HAL_CRASH));
+    }
+
+    @Test
+    public void handleWificondDeath() throws Exception {
+        ArgumentCaptor<StateMachineDeathRecipient> deathHandlerCapturer =
+                ArgumentCaptor.forClass(StateMachineDeathRecipient.class);
+
+        // Trigger initialize to capture the death handler registration.
+        loadComponentsInStaMode();
+
+        verify(mClientInterfaceBinder).linkToDeath(deathHandlerCapturer.capture(), anyInt());
+        StateMachineDeathRecipient deathHandler = deathHandlerCapturer.getValue();
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+
+        // Now trigger the death notification.
+        deathHandler.binderDied();
+        mLooper.dispatchAll();
+
+        verify(mWifiMetrics).incrementNumWificondCrashes();
+        verify(mSelfRecovery).trigger(eq(SelfRecovery.REASON_WIFICOND_CRASH));
+    }
+
+    private void setupMocksForWpsNetworkMigration() {
+        // Now trigger the network connection event for adding the WPS network.
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer(Map<String, WifiConfiguration> configs,
+                                  SparseArray<Map<String, String>> networkExtras) throws Exception {
+                WifiConfiguration config = new WifiConfiguration();
+                config.networkId = WPS_SUPPLICANT_NETWORK_ID;
+                config.SSID = DEFAULT_TEST_SSID;
+                configs.put("dummy", config);
+                return true;
+            }
+        }).when(mWifiNative).migrateNetworksFromSupplicant(any(Map.class), any(SparseArray.class));
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(WPS_FRAMEWORK_NETWORK_ID));
+        when(mWifiConfigManager.enableNetwork(eq(WPS_FRAMEWORK_NETWORK_ID), anyBoolean(), anyInt()))
+                .thenReturn(true);
+    }
+
+    private void verifyMocksForWpsNetworkMigration() {
+        // Network Ids should be reset so that it is treated as addition.
+        ArgumentCaptor<WifiConfiguration> wifiConfigCaptor =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mWifiConfigManager).addOrUpdateNetwork(wifiConfigCaptor.capture(), anyInt());
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, wifiConfigCaptor.getValue().networkId);
+        assertEquals(DEFAULT_TEST_SSID, wifiConfigCaptor.getValue().SSID);
+        verify(mWifiConfigManager).enableNetwork(eq(WPS_FRAMEWORK_NETWORK_ID), anyBoolean(),
+                anyInt());
+    }
+
+    /**
+     * Verifies that WifiInfo is cleared upon exiting and entering WifiInfo, and that it is not
+     * updated by SUPPLICAN_STATE_CHANGE_EVENTs in ScanModeState.
+     * This protects WifiStateMachine from  getting into a bad state where WifiInfo says wifi is
+     * already Connected or Connecting, (when it is in-fact Disconnected), so
+     * WifiConnectivityManager does not attempt any new Connections, freezing wifi.
+     */
+    @Test
+    public void testWifiInfoCleanedUpEnteringExitingConnectModeState() throws Exception {
+        InOrder inOrder = inOrder(mWifiConnectivityManager);
+        Log.i(TAG, mWsm.getCurrentState().getName());
+        String initialBSSID = "aa:bb:cc:dd:ee:ff";
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
+        wifiInfo.setBSSID(initialBSSID);
+
+        // Set WSM to CONNECT_MODE and verify state, and wifi enabled in ConnectivityManager
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        startSupplicantAndDispatchMessages();
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals(WifiManager.WIFI_STATE_ENABLED, mWsm.syncGetWifiState());
+        inOrder.verify(mWifiConnectivityManager).setWifiEnabled(eq(true));
+        assertNull(wifiInfo.getBSSID());
+
+        // Send a SUPPLICANT_STATE_CHANGE_EVENT, verify WifiInfo is updated
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
+
+        // Set WSM to SCAN_ONLY_MODE, verify state and wifi disabled in ConnectivityManager, and
+        // WifiInfo is reset() and state set to DISCONNECTED
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("ScanModeState", getCurrentState().getName());
+        assertEquals(WifiManager.WIFI_STATE_DISABLED, mWsm.syncGetWifiState());
+        inOrder.verify(mWifiConnectivityManager).setWifiEnabled(eq(false));
+        assertNull(wifiInfo.getBSSID());
+        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
+
+        // Send a SUPPLICANT_STATE_CHANGE_EVENT, verify WifiInfo is not updated
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
+        mLooper.dispatchAll();
+        assertNull(wifiInfo.getBSSID());
+        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
+
+        // Set the bssid to something, so we can verify it is cleared (just in case)
+        wifiInfo.setBSSID(initialBSSID);
+
+        // Set WSM to CONNECT_MODE and verify state, and wifi enabled in ConnectivityManager,
+        // and WifiInfo has been reset
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals(WifiManager.WIFI_STATE_ENABLED, mWsm.syncGetWifiState());
+        inOrder.verify(mWifiConnectivityManager).setWifiEnabled(eq(true));
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
+        assertNull(wifiInfo.getBSSID());
+    }
+
+    /**
+     * Adds the network without putting WifiStateMachine into ConnectMode.
+     */
+    @Test
+    public void addNetworkInInitialState() throws Exception {
+        // We should not be in initial state now.
+        assertTrue("InitialState".equals(getCurrentState().getName()));
+        addNetworkAndVerifySuccess(false);
+        verify(mWifiConnectivityManager, never()).setUserConnectChoice(eq(0));
+    }
+
+    /**
+     * Test START_WPS with a null wpsInfo object fails gracefully (No NPE)
+     */
+    @Test
+    public void testStartWps_nullWpsInfo() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        mLooper.startAutoDispatch();
+        Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.START_WPS, 0, 0,
+                null);
+        mLooper.stopAutoDispatch();
+        assertEquals(WifiManager.WPS_FAILED, reply.what);
+    }
+
+    /**
+     * Test that DISABLE_NETWORK returns failure to public API when WifiConfigManager returns
+     * failure.
+     */
+    @Test
+    public void testSyncDisableNetwork_failure() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+        assertEquals("DisconnectedState", getCurrentState().getName());
+        when(mWifiConfigManager.disableNetwork(anyInt(), anyInt())).thenReturn(false);
+
+        mLooper.startAutoDispatch();
+        boolean succeeded = mWsm.syncDisableNetwork(mWsmAsyncChannel, 0);
+        mLooper.stopAutoDispatch();
+        assertFalse(succeeded);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateTrackerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateTrackerTest.java
new file mode 100644
index 0000000..b6c8b8d
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateTrackerTest.java
@@ -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.
+ */
+
+package com.android.server.wifi;
+
+import com.android.internal.app.IBatteryStats;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+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 com.android.server.wifi.WifiStateTracker}.
+ */
+@SmallTest
+public class WifiStateTrackerTest {
+
+    private static final String TAG = "WifiStateTrackerTest";
+    @Mock IBatteryStats mBatteryStats;
+    private WifiStateTracker mWifiStateTracker;
+
+    /**
+     * Setup test.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mWifiStateTracker = new WifiStateTracker(mBatteryStats);
+    }
+
+    /**
+     * Ensure BatteryStats's noteWifiState() is called when the method
+     * updateState() is invoked on WifiStateTracker for relevant states.
+     */
+    @Test
+    public void testBatteryStatsUpdated() throws Exception {
+        int[] relevantStates = new int[] { WifiStateTracker.SCAN_MODE,
+                WifiStateTracker.CONNECTED, WifiStateTracker.DISCONNECTED,
+                WifiStateTracker.SOFT_AP};
+        for (int i = 0; i < relevantStates.length; i++) {
+            mWifiStateTracker.updateState(relevantStates[i]);
+        }
+        verify(mBatteryStats, times(relevantStates.length)).noteWifiState(anyInt(), any());
+    }
+
+    /**
+     * Ensure BatteryStats's noteWifiState() is not called when the method
+     * updateState() is invoked on WifiStateTracker for irrelevant states.
+     */
+    @Test
+    public void testBatteryStatsNotUpdated() throws Exception {
+        int[] irrelevantStates = new int[] { WifiStateTracker.SCAN_MODE - 1,
+                WifiStateTracker.SOFT_AP + 1};
+        for (int i = 0; i < irrelevantStates.length; i++) {
+            mWifiStateTracker.updateState(irrelevantStates[i]);
+        }
+        verify(mBatteryStats, times(0)).noteWifiState(anyInt(), any());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
new file mode 100644
index 0000000..34ddf23
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
@@ -0,0 +1,1862 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.hardware.wifi.V1_0.IWifiApIface;
+import android.hardware.wifi.V1_0.IWifiChip;
+import android.hardware.wifi.V1_0.IWifiChipEventCallback;
+import android.hardware.wifi.V1_0.IWifiIface;
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
+import android.hardware.wifi.V1_0.IWifiStaIface;
+import android.hardware.wifi.V1_0.IWifiStaIfaceEventCallback;
+import android.hardware.wifi.V1_0.RttCapabilities;
+import android.hardware.wifi.V1_0.RttConfig;
+import android.hardware.wifi.V1_0.StaApfPacketFilterCapabilities;
+import android.hardware.wifi.V1_0.StaBackgroundScanCapabilities;
+import android.hardware.wifi.V1_0.StaBackgroundScanParameters;
+import android.hardware.wifi.V1_0.StaLinkLayerIfacePacketStats;
+import android.hardware.wifi.V1_0.StaLinkLayerRadioStats;
+import android.hardware.wifi.V1_0.StaLinkLayerStats;
+import android.hardware.wifi.V1_0.StaScanData;
+import android.hardware.wifi.V1_0.StaScanDataFlagMask;
+import android.hardware.wifi.V1_0.StaScanResult;
+import android.hardware.wifi.V1_0.WifiDebugHostWakeReasonStats;
+import android.hardware.wifi.V1_0.WifiDebugPacketFateFrameType;
+import android.hardware.wifi.V1_0.WifiDebugRingBufferFlags;
+import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
+import android.hardware.wifi.V1_0.WifiDebugRingBufferVerboseLevel;
+import android.hardware.wifi.V1_0.WifiDebugRxPacketFate;
+import android.hardware.wifi.V1_0.WifiDebugRxPacketFateReport;
+import android.hardware.wifi.V1_0.WifiDebugTxPacketFate;
+import android.hardware.wifi.V1_0.WifiDebugTxPacketFateReport;
+import android.hardware.wifi.V1_0.WifiInformationElement;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.apf.ApfCapabilities;
+import android.net.wifi.RttManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiLinkLayerStats;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.WifiWakeReasonAndCounts;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.util.Pair;
+
+import com.android.server.connectivity.KeepalivePacketData;
+import com.android.server.wifi.util.NativeUtil;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiVendorHal}.
+ */
+public class WifiVendorHalTest {
+
+    WifiVendorHal mWifiVendorHal;
+    private WifiStatus mWifiStatusSuccess;
+    private WifiStatus mWifiStatusFailure;
+    WifiLog mWifiLog;
+    @Mock
+    private HalDeviceManager mHalDeviceManager;
+    @Mock
+    private TestLooper mLooper;
+    @Mock
+    private WifiVendorHal.HalDeviceManagerStatusListener mHalDeviceManagerStatusCallbacks;
+    @Mock
+    private IWifiApIface mIWifiApIface;
+    @Mock
+    private IWifiChip mIWifiChip;
+    @Mock
+    private IWifiStaIface mIWifiStaIface;
+    @Mock
+    private IWifiRttController mIWifiRttController;
+    private IWifiStaIfaceEventCallback mIWifiStaIfaceEventCallback;
+    private IWifiChipEventCallback mIWifiChipEventCallback;
+    @Mock
+    private WifiNative.VendorHalDeathEventHandler mVendorHalDeathHandler;
+
+    /**
+     * Identity function to supply a type to its argument, which is a lambda
+     */
+    static Answer<WifiStatus> answerWifiStatus(Answer<WifiStatus> statusLambda) {
+        return (statusLambda);
+    }
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mWifiLog = new FakeWifiLog();
+        mLooper = new TestLooper();
+        mWifiStatusSuccess = new WifiStatus();
+        mWifiStatusSuccess.code = WifiStatusCode.SUCCESS;
+        mWifiStatusFailure = new WifiStatus();
+        mWifiStatusFailure.code = WifiStatusCode.ERROR_UNKNOWN;
+        mWifiStatusFailure.description = "I don't even know what a Mock Turtle is.";
+        when(mIWifiStaIface.enableLinkLayerStatsCollection(false)).thenReturn(mWifiStatusSuccess);
+
+        // Setup the HalDeviceManager mock's start/stop behaviour. This can be overridden in
+        // individual tests, if needed.
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer() {
+                when(mHalDeviceManager.isReady()).thenReturn(true);
+                when(mHalDeviceManager.isStarted()).thenReturn(true);
+                mHalDeviceManagerStatusCallbacks.onStatusChanged();
+                return true;
+            }
+        }).when(mHalDeviceManager).start();
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer() {
+                when(mHalDeviceManager.isReady()).thenReturn(true);
+                when(mHalDeviceManager.isStarted()).thenReturn(false);
+                mHalDeviceManagerStatusCallbacks.onStatusChanged();
+            }
+        }).when(mHalDeviceManager).stop();
+        when(mHalDeviceManager.createStaIface(eq(null), eq(null)))
+                .thenReturn(mIWifiStaIface);
+        when(mHalDeviceManager.createApIface(eq(null), eq(null)))
+                .thenReturn(mIWifiApIface);
+        when(mHalDeviceManager.getChip(any(IWifiIface.class)))
+                .thenReturn(mIWifiChip);
+        when(mHalDeviceManager.createRttController(any(IWifiIface.class)))
+                .thenReturn(mIWifiRttController);
+        when(mIWifiChip.registerEventCallback(any(IWifiChipEventCallback.class)))
+                .thenReturn(mWifiStatusSuccess);
+        mIWifiStaIfaceEventCallback = null;
+        when(mIWifiStaIface.registerEventCallback(any(IWifiStaIfaceEventCallback.class)))
+                .thenAnswer(answerWifiStatus((invocation) -> {
+                    Object[] args = invocation.getArguments();
+                    mIWifiStaIfaceEventCallback = (IWifiStaIfaceEventCallback) args[0];
+                    return (mWifiStatusSuccess);
+                }));
+        mIWifiChipEventCallback = null;
+        when(mIWifiChip.registerEventCallback(any(IWifiChipEventCallback.class)))
+                .thenAnswer(answerWifiStatus((invocation) -> {
+                    Object[] args = invocation.getArguments();
+                    mIWifiChipEventCallback = (IWifiChipEventCallback) args[0];
+                    return (mWifiStatusSuccess);
+                }));
+
+        when(mIWifiRttController.registerEventCallback(any(IWifiRttControllerEventCallback.class)))
+                .thenReturn(mWifiStatusSuccess);
+
+        // Create the vendor HAL object under test.
+        mWifiVendorHal = new WifiVendorHal(mHalDeviceManager, mLooper.getLooper());
+
+        // Initialize the vendor HAL to capture the registered callback.
+        mWifiVendorHal.initialize(mVendorHalDeathHandler);
+        ArgumentCaptor<WifiVendorHal.HalDeviceManagerStatusListener> hdmCallbackCaptor =
+                ArgumentCaptor.forClass(WifiVendorHal.HalDeviceManagerStatusListener.class);
+        verify(mHalDeviceManager).registerStatusListener(hdmCallbackCaptor.capture(), any());
+        mHalDeviceManagerStatusCallbacks = hdmCallbackCaptor.getValue();
+
+    }
+
+    /**
+     * Tests the successful starting of HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalSuccessInStaMode() throws  Exception {
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager).getChip(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).isReady();
+        verify(mHalDeviceManager).isStarted();
+        verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
+        verify(mIWifiChip).registerEventCallback(any(IWifiChipEventCallback.class));
+
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+    }
+
+    /**
+     * Tests the successful starting of HAL in AP mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalSuccessInApMode() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHal(false));
+        assertTrue(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createApIface(eq(null), eq(null));
+        verify(mHalDeviceManager).getChip(eq(mIWifiApIface));
+        verify(mHalDeviceManager).isReady();
+        verify(mHalDeviceManager).isStarted();
+
+        verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+    }
+
+    /**
+     * Tests the failure to start HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalFailureInStaMode() throws Exception {
+        // No callbacks are invoked in this case since the start itself failed. So, override
+        // default AnswerWithArguments that we setup.
+        doAnswer(new AnswerWithArguments() {
+            public boolean answer() throws Exception {
+                return false;
+            }
+        }).when(mHalDeviceManager).start();
+        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+
+        verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+        verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+        verify(mIWifiStaIface, never())
+                .registerEventCallback(any(IWifiStaIfaceEventCallback.class));
+    }
+
+    /**
+     * Tests the failure to start HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalFailureInIfaceCreationInStaMode() throws Exception {
+        when(mHalDeviceManager.createStaIface(eq(null), eq(null))).thenReturn(null);
+        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager).stop();
+
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+        verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+        verify(mIWifiStaIface, never())
+                .registerEventCallback(any(IWifiStaIfaceEventCallback.class));
+    }
+
+    /**
+     * Tests the failure to start HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalFailureInRttControllerCreationInStaMode() throws Exception {
+        when(mHalDeviceManager.createRttController(any(IWifiIface.class))).thenReturn(null);
+        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).stop();
+        verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
+
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+        verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
+    }
+
+    /**
+     * Tests the failure to start HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalFailureInChipGetInStaMode() throws Exception {
+        when(mHalDeviceManager.getChip(any(IWifiIface.class))).thenReturn(null);
+        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).getChip(any(IWifiIface.class));
+        verify(mHalDeviceManager).stop();
+        verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
+
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+    }
+
+    /**
+     * Tests the failure to start HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalFailureInStaIfaceCallbackRegistration() throws Exception {
+        when(mIWifiStaIface.registerEventCallback(any(IWifiStaIfaceEventCallback.class)))
+                .thenReturn(mWifiStatusFailure);
+        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager).stop();
+        verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
+
+        verify(mHalDeviceManager, never()).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+    }
+
+    /**
+     * Tests the failure to start HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalFailureInChipCallbackRegistration() throws Exception {
+        when(mIWifiChip.registerEventCallback(any(IWifiChipEventCallback.class)))
+                .thenReturn(mWifiStatusFailure);
+        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).getChip(any(IWifiIface.class));
+        verify(mHalDeviceManager).stop();
+        verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
+        verify(mIWifiChip).registerEventCallback(any(IWifiChipEventCallback.class));
+
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+    }
+
+    /**
+     * Tests the failure to start HAL in STA mode using
+     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     */
+    @Test
+    public void testStartHalFailureInApMode() throws Exception {
+        when(mHalDeviceManager.createApIface(eq(null), eq(null))).thenReturn(null);
+        assertFalse(mWifiVendorHal.startVendorHal(false));
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).createApIface(eq(null), eq(null));
+        verify(mHalDeviceManager).stop();
+
+        verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+    }
+
+    /**
+     * Tests the stopping of HAL in STA mode using
+     * {@link WifiVendorHal#stopVendorHal()}.
+     */
+    @Test
+    public void testStopHalInStaMode() {
+        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.isHalStarted());
+
+        mWifiVendorHal.stopVendorHal();
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).stop();
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager).getChip(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager, times(2)).isReady();
+        verify(mHalDeviceManager, times(2)).isStarted();
+
+        verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
+    }
+
+    /**
+     * Tests the stopping of HAL in AP mode using
+     * {@link WifiVendorHal#stopVendorHal()}.
+     */
+    @Test
+    public void testStopHalInApMode() {
+        assertTrue(mWifiVendorHal.startVendorHal(false));
+        assertTrue(mWifiVendorHal.isHalStarted());
+
+        mWifiVendorHal.stopVendorHal();
+        assertFalse(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        verify(mHalDeviceManager).stop();
+        verify(mHalDeviceManager).createApIface(eq(null), eq(null));
+        verify(mHalDeviceManager).getChip(eq(mIWifiApIface));
+        verify(mHalDeviceManager, times(2)).isReady();
+        verify(mHalDeviceManager, times(2)).isStarted();
+
+        verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
+        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+    }
+
+    /**
+     * Test that enter logs when verbose logging is enabled
+     */
+    @Test
+    public void testEnterLogging() {
+        mWifiVendorHal.mLog = spy(mWifiLog);
+        mWifiVendorHal.enableVerboseLogging(true);
+        mWifiVendorHal.installPacketFilter(new byte[0]);
+        verify(mWifiVendorHal.mLog).trace(eq("% filter length %"));
+    }
+
+    /**
+     * Test that enter does not log when verbose logging is not enabled
+     */
+    @Test
+    public void testEnterSilenceWhenNotEnabled() {
+        mWifiVendorHal.mLog = spy(mWifiLog);
+        mWifiVendorHal.installPacketFilter(new byte[0]);
+        mWifiVendorHal.enableVerboseLogging(true);
+        mWifiVendorHal.enableVerboseLogging(false);
+        mWifiVendorHal.installPacketFilter(new byte[0]);
+        verify(mWifiVendorHal.mLog, never()).trace(eq("% filter length %"));
+    }
+
+    /**
+     * Test that boolResult logs a false result
+     */
+    @Test
+    public void testBoolResultFalse() {
+        mWifiLog = spy(mWifiLog);
+        mWifiVendorHal.mLog = mWifiLog;
+        mWifiVendorHal.mVerboseLog = mWifiLog;
+        assertFalse(mWifiVendorHal.getBgScanCapabilities(new WifiNative.ScanCapabilities()));
+        verify(mWifiLog).err("% returns %");
+    }
+
+    /**
+     * Test that getBgScanCapabilities is hooked up to the HAL correctly
+     *
+     * A call before the vendor HAL is started should return a non-null result with version 0
+     *
+     * A call after the HAL is started should return the mocked values.
+     */
+    @Test
+    public void testGetBgScanCapabilities() throws Exception {
+        StaBackgroundScanCapabilities capabilities = new StaBackgroundScanCapabilities();
+        capabilities.maxCacheSize = 12;
+        capabilities.maxBuckets = 34;
+        capabilities.maxApCachePerScan = 56;
+        capabilities.maxReportingThreshold = 78;
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getBackgroundScanCapabilitiesCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, capabilities);
+            }
+        }).when(mIWifiStaIface).getBackgroundScanCapabilities(any(
+                IWifiStaIface.getBackgroundScanCapabilitiesCallback.class));
+
+        WifiNative.ScanCapabilities result = new WifiNative.ScanCapabilities();
+
+        assertFalse(mWifiVendorHal.getBgScanCapabilities(result));  // should fail - not started
+        assertTrue(mWifiVendorHal.startVendorHalSta());           // Start the vendor hal
+        assertTrue(mWifiVendorHal.getBgScanCapabilities(result));   // should succeed
+
+        assertEquals(12, result.max_scan_cache_size);
+        assertEquals(34, result.max_scan_buckets);
+        assertEquals(56, result.max_ap_cache_per_scan);
+        assertEquals(78, result.max_scan_reporting_threshold);
+    }
+
+    private void setupValidFrequenciesForBand(ArrayList<Integer> frequencies) throws Exception {
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(int band, IWifiStaIface.getValidFrequenciesForBandCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, frequencies);
+            }
+        }).when(mIWifiStaIface).getValidFrequenciesForBand(anyInt(), any(
+                IWifiStaIface.getValidFrequenciesForBandCallback.class));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(int band, IWifiApIface.getValidFrequenciesForBandCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, frequencies);
+            }
+        }).when(mIWifiApIface).getValidFrequenciesForBand(anyInt(), any(
+                IWifiApIface.getValidFrequenciesForBandCallback.class));
+
+    }
+
+    private int[] intArrayFromArrayList(ArrayList<Integer> in) {
+        int[] ans = new int[in.size()];
+        int i = 0;
+        for (Integer e : in) ans[i++] = e;
+        return ans;
+    }
+
+    /**
+     * Test that isGetChannelsForBandSupported works in STA mode
+     */
+    @Test
+    public void testGetChannelsForBandSupportedSta() throws Exception {
+        ArrayList<Integer> freq = new ArrayList<>();
+        freq.add(2405);
+
+        setupValidFrequenciesForBand(freq);
+
+        assertFalse(mWifiVendorHal.isGetChannelsForBandSupported());
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.isGetChannelsForBandSupported());
+    }
+
+    /**
+     * Test that isGetChannelsForBandSupported works in AP mode
+     */
+    @Test
+    public void testGetChannelsForBandSupportedAp() throws Exception {
+        ArrayList<Integer> freq = new ArrayList<>();
+        freq.add(2405);
+
+        setupValidFrequenciesForBand(freq);
+
+        assertFalse(mWifiVendorHal.isGetChannelsForBandSupported());
+
+        assertTrue(mWifiVendorHal.startVendorHalAp());
+
+        assertTrue(mWifiVendorHal.isGetChannelsForBandSupported());
+    }
+
+    /**
+     * Test translation to WifiManager.WIFI_FEATURE_*
+     *
+     * Just do a spot-check with a few feature bits here; since the code is table-
+     * driven we don't have to work hard to exercise all of it.
+     */
+    @Test
+    public void testFeatureMaskTranslation() {
+        int caps = (
+                IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN
+                | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
+            );
+        int expected = (
+                WifiManager.WIFI_FEATURE_SCANNER
+                | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS);
+        assertEquals(expected, mWifiVendorHal.wifiFeatureMaskFromStaCapabilities(caps));
+    }
+
+    /**
+     * Test enablement of link layer stats after startup
+     *
+     * Request link layer stats before HAL start
+     * - should not make it to the HAL layer
+     * Start the HAL in STA mode
+     * Request link layer stats twice more
+     * - enable request should make it to the HAL layer
+     * - HAL layer should have been called to make the requests (i.e., two calls total)
+     */
+    @Test
+    public void testLinkLayerStatsEnableAfterStartup() throws Exception {
+        doNothing().when(mIWifiStaIface).getLinkLayerStats(any());
+
+        assertNull(mWifiVendorHal.getWifiLinkLayerStats());
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertTrue(mWifiVendorHal.isHalStarted());
+
+        verify(mHalDeviceManager).start();
+        mWifiVendorHal.getWifiLinkLayerStats();
+        mWifiVendorHal.getWifiLinkLayerStats();
+        verify(mIWifiStaIface).enableLinkLayerStatsCollection(false); // mLinkLayerStatsDebug
+        verify(mIWifiStaIface, times(2)).getLinkLayerStats(any());
+    }
+
+    /**
+     * Test that link layer stats are not enabled and harmless in AP mode
+     *
+     * Start the HAL in AP mode
+     * - stats should not be enabled
+     * Request link layer stats
+     * - HAL layer should have been called to make the request
+     */
+    @Test
+    public void testLinkLayerStatsNotEnabledAndHarmlessInApMode() throws Exception {
+        doNothing().when(mIWifiStaIface).getLinkLayerStats(any());
+
+        assertTrue(mWifiVendorHal.startVendorHalAp());
+        assertTrue(mWifiVendorHal.isHalStarted());
+        assertNull(mWifiVendorHal.getWifiLinkLayerStats());
+
+        verify(mHalDeviceManager).start();
+
+        verify(mIWifiStaIface, never()).enableLinkLayerStatsCollection(false);
+        verify(mIWifiStaIface, never()).getLinkLayerStats(any());
+    }
+
+    /**
+     * Test that the link layer stats fields are populated correctly.
+     *
+     * This is done by filling with random values and then using toString on the
+     * original and converted values, comparing just the numerics in the result.
+     * This makes the assumption that the fields are in the same order in both string
+     * representations, which is not quite true. So apply some fixups before the final
+     * comparison.
+     */
+    @Test
+    public void testLinkLayerStatsAssignment() throws Exception {
+        Random r = new Random(1775968256);
+        StaLinkLayerStats stats = new StaLinkLayerStats();
+        randomizePacketStats(r, stats.iface.wmeBePktStats);
+        randomizePacketStats(r, stats.iface.wmeBkPktStats);
+        randomizePacketStats(r, stats.iface.wmeViPktStats);
+        randomizePacketStats(r, stats.iface.wmeVoPktStats);
+        randomizeRadioStats(r, stats.radios);
+
+        stats.timeStampInMs = 42; // currently dropped in conversion
+
+        String expected = numbersOnly(stats.toString());
+
+        WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats(stats);
+
+        String actual = numbersOnly(converted.toString());
+
+        // Do the required fixups to the both expected and actual
+        expected = rmValue(expected, stats.radios.get(0).rxTimeInMs);
+        expected = rmValue(expected, stats.radios.get(0).onTimeInMsForScan);
+
+        actual = rmValue(actual, stats.radios.get(0).rxTimeInMs);
+        actual = rmValue(actual, stats.radios.get(0).onTimeInMsForScan);
+        actual = actual + "42 ";
+
+        // The remaining fields should agree
+        assertEquals(expected, actual);
+    }
+
+    /** Just the digits with delimiting spaces, please */
+    private static String numbersOnly(String s) {
+        return s.replaceAll("[^0-9]+", " ");
+    }
+
+    /** Remove the given value from the space-delimited string, or die trying. */
+    private static String rmValue(String s, long value) throws Exception {
+        String ans = s.replaceAll(" " + value + " ", " ");
+        assertNotEquals(s, ans);
+        return ans;
+    }
+
+    /**
+     * Populate packet stats with non-negative random values
+     */
+    private static void randomizePacketStats(Random r, StaLinkLayerIfacePacketStats pstats) {
+        pstats.rxMpdu = r.nextLong() & 0xFFFFFFFFFFL; // more than 32 bits
+        pstats.txMpdu = r.nextLong() & 0xFFFFFFFFFFL;
+        pstats.lostMpdu = r.nextLong() & 0xFFFFFFFFFFL;
+        pstats.retries = r.nextLong() & 0xFFFFFFFFFFL;
+    }
+
+   /**
+     * Populate radio stats with non-negative random values
+     */
+    private static void randomizeRadioStats(Random r, ArrayList<StaLinkLayerRadioStats> rstats) {
+        StaLinkLayerRadioStats rstat = new StaLinkLayerRadioStats();
+        rstat.onTimeInMs = r.nextInt() & 0xFFFFFF;
+        rstat.txTimeInMs = r.nextInt() & 0xFFFFFF;
+        for (int i = 0; i < 4; i++) {
+            Integer v = r.nextInt() & 0xFFFFFF;
+            rstat.txTimeInMsPerLevel.add(v);
+        }
+        rstat.rxTimeInMs = r.nextInt() & 0xFFFFFF;
+        rstat.onTimeInMsForScan = r.nextInt() & 0xFFFFFF;
+        rstats.add(rstat);
+    }
+
+    /**
+     * Test that getFirmwareVersion() and getDriverVersion() work
+     *
+     * Calls before the STA is started are expected to return null.
+     */
+    @Test
+    public void testVersionGetters() throws Exception {
+        String firmwareVersion = "fuzzy";
+        String driverVersion = "dizzy";
+        IWifiChip.ChipDebugInfo chipDebugInfo = new IWifiChip.ChipDebugInfo();
+        chipDebugInfo.firmwareDescription = firmwareVersion;
+        chipDebugInfo.driverDescription = driverVersion;
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.requestChipDebugInfoCallback cb) throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, chipDebugInfo);
+            }
+        }).when(mIWifiChip).requestChipDebugInfo(any(IWifiChip.requestChipDebugInfoCallback.class));
+
+        assertNull(mWifiVendorHal.getFirmwareVersion());
+        assertNull(mWifiVendorHal.getDriverVersion());
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertEquals(firmwareVersion, mWifiVendorHal.getFirmwareVersion());
+        assertEquals(driverVersion, mWifiVendorHal.getDriverVersion());
+    }
+
+    /**
+     * For checkRoundTripIntTranslation lambdas
+     */
+    interface IntForInt {
+        int translate(int value);
+    }
+
+    /**
+     * Checks that translation from x to y and back again is the identity function
+     *
+     * @param xFromY reverse translator
+     * @param yFromX forward translator
+     * @param xLimit non-inclusive upper bound on x (lower bound is zero)
+     */
+    private void checkRoundTripIntTranslation(
+            IntForInt xFromY, IntForInt yFromX, int xFirst, int xLimit) throws Exception {
+        int ex = 0;
+        for (int i = xFirst; i < xLimit; i++) {
+            assertEquals(i, xFromY.translate(yFromX.translate(i)));
+        }
+        try {
+            yFromX.translate(xLimit);
+            assertTrue("expected an exception here", false);
+        } catch (IllegalArgumentException e) {
+            ex++;
+        }
+        try {
+            xFromY.translate(yFromX.translate(xLimit - 1) + 1);
+            assertTrue("expected an exception here", false);
+        } catch (IllegalArgumentException e) {
+            ex++;
+        }
+        assertEquals(2, ex);
+    }
+
+
+    /**
+     * Test translations of RTT type
+     */
+    @Test
+    public void testRttTypeTranslation() throws Exception {
+        checkRoundTripIntTranslation(
+                (y) -> WifiVendorHal.halRttTypeFromFrameworkRttType(y),
+                (x) -> WifiVendorHal.frameworkRttTypeFromHalRttType(x),
+                1, 3);
+    }
+
+    /**
+     * Test translations of peer type
+     */
+    @Test
+    public void testPeerTranslation() throws Exception {
+        checkRoundTripIntTranslation(
+                (y) -> WifiVendorHal.halPeerFromFrameworkPeer(y),
+                (x) -> WifiVendorHal.frameworkPeerFromHalPeer(x),
+                1, 6);
+    }
+
+    /**
+     * Test translations of channel width
+     */
+    @Test
+    public void testChannelWidth() throws Exception {
+        checkRoundTripIntTranslation(
+                (y) -> WifiVendorHal.halChannelWidthFromFrameworkChannelWidth(y),
+                (x) -> WifiVendorHal.frameworkChannelWidthFromHalChannelWidth(x),
+                0, 5);
+    }
+
+    /**
+     * Test translations of preamble type mask
+     */
+    @Test
+    public void testPreambleTranslation() throws Exception {
+        checkRoundTripIntTranslation(
+                (y) -> WifiVendorHal.halPreambleFromFrameworkPreamble(y),
+                (x) -> WifiVendorHal.frameworkPreambleFromHalPreamble(x),
+                0, 8);
+    }
+
+    /**
+     * Test translations of bandwidth mask
+     */
+    @Test
+    public void testBandwidthTranslations() throws Exception {
+        checkRoundTripIntTranslation(
+                (y) -> WifiVendorHal.halBwFromFrameworkBw(y),
+                (x) -> WifiVendorHal.frameworkBwFromHalBw(x),
+                0, 64);
+    }
+
+    @Test
+    public void testGetRttStuff() throws Exception {
+        RttManager.RttParams params = new RttManager.RttParams();
+        //TODO(b/34901744) populate
+        RttConfig config = WifiVendorHal.halRttConfigFromFrameworkRttParams(params);
+        //TODO(b/34901744) check
+    }
+
+    @Test
+    public void testGetRttCapabilities() throws Exception {
+        RttCapabilities capabilities = new RttCapabilities();
+        //TODO(b/34901744) populate
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiRttController.getCapabilitiesCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, capabilities);
+            }
+        }).when(mIWifiRttController).getCapabilities(any(
+                IWifiRttController.getCapabilitiesCallback.class));
+
+        assertNull(mWifiVendorHal.getRttCapabilities());
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        RttManager.RttCapabilities actual = mWifiVendorHal.getRttCapabilities();
+        //TODO(b/34901744) check
+
+    }
+
+    //TODO(b/34901744) negative RTT test cases as well.
+    // e.g. invoke RTT without putting the HAL in the correct mode.
+
+    /**
+     * Test that setScanningMacOui is hooked up to the HAL correctly
+     */
+    @Test
+    public void testSetScanningMacOui() throws Exception {
+        byte[] oui = NativeUtil.macAddressOuiToByteArray("DA:A1:19");
+        byte[] zzz = NativeUtil.macAddressOuiToByteArray("00:00:00");
+
+        when(mIWifiStaIface.setScanningMacOui(any())).thenReturn(mWifiStatusSuccess);
+
+        assertFalse(mWifiVendorHal.setScanningMacOui(oui)); // expect fail - STA not started
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertFalse(mWifiVendorHal.setScanningMacOui(null));  // expect fail - null
+        assertFalse(mWifiVendorHal.setScanningMacOui(new byte[]{(byte) 1})); // expect fail - len
+        assertTrue(mWifiVendorHal.setScanningMacOui(oui));
+        assertTrue(mWifiVendorHal.setScanningMacOui(zzz));
+
+        verify(mIWifiStaIface).setScanningMacOui(eq(oui));
+        verify(mIWifiStaIface).setScanningMacOui(eq(zzz));
+    }
+
+    @Test
+    public void testStartSendingOffloadedPacket() throws Exception {
+        byte[] srcMac = NativeUtil.macAddressToByteArray("4007b2088c81");
+        InetAddress src = InetAddress.parseNumericAddress("192.168.13.13");
+        InetAddress dst = InetAddress.parseNumericAddress("93.184.216.34");
+        int slot = 13;
+        int millis = 16000;
+
+        KeepalivePacketData kap = KeepalivePacketData.nattKeepalivePacket(src, 63000, dst, 4500);
+
+        when(mIWifiStaIface.startSendingKeepAlivePackets(
+                anyInt(), any(), anyShort(), any(), any(), anyInt()
+        )).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertTrue(0 == mWifiVendorHal.startSendingOffloadedPacket(slot, srcMac, kap, millis));
+
+        verify(mIWifiStaIface).startSendingKeepAlivePackets(
+                eq(slot), any(), anyShort(), any(), any(), eq(millis));
+    }
+
+    @Test
+    public void testStopSendingOffloadedPacket() throws Exception {
+        int slot = 13;
+
+        when(mIWifiStaIface.stopSendingKeepAlivePackets(anyInt())).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertTrue(0 == mWifiVendorHal.stopSendingOffloadedPacket(slot));
+
+        verify(mIWifiStaIface).stopSendingKeepAlivePackets(eq(slot));
+    }
+
+    /**
+     * Test the setup, invocation, and removal of a RSSI event handler
+     *
+     */
+    @Test
+    public void testRssiMonitoring() throws Exception {
+        when(mIWifiStaIface.startRssiMonitoring(anyInt(), anyInt(), anyInt()))
+                .thenReturn(mWifiStatusSuccess);
+        when(mIWifiStaIface.stopRssiMonitoring(anyInt()))
+                .thenReturn(mWifiStatusSuccess);
+
+        ArrayList<Byte> breach = new ArrayList<>(10);
+        byte hi = -21;
+        byte med = -42;
+        byte lo = -84;
+        Byte lower = -88;
+        WifiNative.WifiRssiEventHandler handler;
+        handler = ((cur) -> {
+            breach.add(cur);
+        });
+        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(hi, lo, handler)); // not started
+        assertEquals(-1, mWifiVendorHal.stopRssiMonitoring()); // not started
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(hi, lo, handler));
+        int theCmdId = mWifiVendorHal.sRssiMonCmdId;
+        breach.clear();
+        mIWifiStaIfaceEventCallback.onRssiThresholdBreached(theCmdId, new byte[6], lower);
+        assertEquals(breach.get(0), lower);
+        assertEquals(0, mWifiVendorHal.stopRssiMonitoring());
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(hi, lo, handler));
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(med, lo, handler)); // replacing works
+        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(hi, lo, null)); // null handler fails
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(hi, lo, handler));
+        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(lo, hi, handler)); // empty range
+    }
+
+    /**
+     * Test that getApfCapabilities is hooked up to the HAL correctly
+     *
+     * A call before the vendor HAL is started should return a non-null result with version 0
+     *
+     * A call after the HAL is started should return the mocked values.
+     */
+    @Test
+    public void testApfCapabilities() throws Exception {
+        int myVersion = 33;
+        int myMaxSize = 1234;
+
+        StaApfPacketFilterCapabilities capabilities = new StaApfPacketFilterCapabilities();
+        capabilities.version = myVersion;
+        capabilities.maxLength = myMaxSize;
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getApfPacketFilterCapabilitiesCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, capabilities);
+            }
+        }).when(mIWifiStaIface).getApfPacketFilterCapabilities(any(
+                IWifiStaIface.getApfPacketFilterCapabilitiesCallback.class));
+
+
+        assertEquals(0, mWifiVendorHal.getApfCapabilities().apfVersionSupported);
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        ApfCapabilities actual = mWifiVendorHal.getApfCapabilities();
+
+        assertEquals(myVersion, actual.apfVersionSupported);
+        assertEquals(myMaxSize, actual.maximumApfProgramSize);
+        assertEquals(android.system.OsConstants.ARPHRD_ETHER, actual.apfPacketFormat);
+        assertNotEquals(0, actual.apfPacketFormat);
+    }
+
+    /**
+     * Test that an APF program can be installed.
+     */
+    @Test
+    public void testInstallApf() throws Exception {
+        byte[] filter = new byte[] {19, 53, 10};
+
+        ArrayList<Byte> expected = new ArrayList<>(3);
+        for (byte b : filter) expected.add(b);
+
+        when(mIWifiStaIface.installApfPacketFilter(anyInt(), any(ArrayList.class)))
+                .thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertTrue(mWifiVendorHal.installPacketFilter(filter));
+
+        verify(mIWifiStaIface).installApfPacketFilter(eq(0), eq(expected));
+    }
+
+    /**
+     * Test that the country code is set in AP mode (when it should be).
+     */
+    @Test
+    public void testSetCountryCodeHal() throws Exception {
+        byte[] expected = new byte[]{(byte) 'C', (byte) 'A'};
+
+        when(mIWifiApIface.setCountryCode(any()))
+                .thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHalAp());
+
+        assertFalse(mWifiVendorHal.setCountryCodeHal(null));
+        assertFalse(mWifiVendorHal.setCountryCodeHal(""));
+        assertFalse(mWifiVendorHal.setCountryCodeHal("A"));
+        assertTrue(mWifiVendorHal.setCountryCodeHal("CA")); // Only one expected to succeed
+        assertFalse(mWifiVendorHal.setCountryCodeHal("ZZZ"));
+
+        verify(mIWifiApIface).setCountryCode(eq(expected));
+    }
+
+    /**
+     * Test that RemoteException is caught and logged.
+     */
+    @Test
+    public void testRemoteExceptionIsHandled() throws Exception {
+        mWifiLog = spy(mWifiLog);
+        mWifiVendorHal.mVerboseLog = mWifiLog;
+        when(mIWifiApIface.setCountryCode(any()))
+                .thenThrow(new RemoteException("oops"));
+        assertTrue(mWifiVendorHal.startVendorHalAp());
+        assertFalse(mWifiVendorHal.setCountryCodeHal("CA"));
+        assertFalse(mWifiVendorHal.isHalStarted());
+        verify(mWifiLog).err(any());
+    }
+
+    /**
+     * Test that startLoggingToDebugRingBuffer is plumbed to chip
+     *
+     * A call before the vendor hal is started should just return false.
+     * After starting in STA mode, the call should succeed, and pass ther right things down.
+     */
+    @Test
+    public void testStartLoggingRingBuffer() throws Exception {
+        when(mIWifiChip.startLoggingToDebugRingBuffer(
+                any(String.class), anyInt(), anyInt(), anyInt()
+        )).thenReturn(mWifiStatusSuccess);
+
+        assertFalse(mWifiVendorHal.startLoggingRingBuffer(1, 0x42, 0, 0, "One"));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertTrue(mWifiVendorHal.startLoggingRingBuffer(1, 0x42, 11, 3000, "One"));
+
+        verify(mIWifiChip).startLoggingToDebugRingBuffer("One", 1, 11, 3000);
+    }
+
+    /**
+     * Same test as testStartLoggingRingBuffer, but in AP mode rather than STA.
+     */
+    @Test
+    public void testStartLoggingRingBufferOnAp() throws Exception {
+        when(mIWifiChip.startLoggingToDebugRingBuffer(
+                any(String.class), anyInt(), anyInt(), anyInt()
+        )).thenReturn(mWifiStatusSuccess);
+
+        assertFalse(mWifiVendorHal.startLoggingRingBuffer(1, 0x42, 0, 0, "One"));
+        assertTrue(mWifiVendorHal.startVendorHalAp());
+        assertTrue(mWifiVendorHal.startLoggingRingBuffer(1, 0x42, 11, 3000, "One"));
+
+        verify(mIWifiChip).startLoggingToDebugRingBuffer("One", 1, 11, 3000);
+    }
+
+    /**
+     * Test that getRingBufferStatus gets and translates its stuff correctly
+     */
+    @Test
+    public void testRingBufferStatus() throws Exception {
+        WifiDebugRingBufferStatus one = new WifiDebugRingBufferStatus();
+        one.ringName = "One";
+        one.flags = WifiDebugRingBufferFlags.HAS_BINARY_ENTRIES;
+        one.ringId = 5607371;
+        one.sizeInBytes = 54321;
+        one.freeSizeInBytes = 42;
+        one.verboseLevel = WifiDebugRingBufferVerboseLevel.VERBOSE;
+        String oneExpect = "name: One flag: 1 ringBufferId: 5607371 ringBufferByteSize: 54321"
+                + " verboseLevel: 2 writtenBytes: 0 readBytes: 0 writtenRecords: 0";
+
+        WifiDebugRingBufferStatus two = new WifiDebugRingBufferStatus();
+        two.ringName = "Two";
+        two.flags = WifiDebugRingBufferFlags.HAS_ASCII_ENTRIES
+                | WifiDebugRingBufferFlags.HAS_PER_PACKET_ENTRIES;
+        two.ringId = 4512470;
+        two.sizeInBytes = 300;
+        two.freeSizeInBytes = 42;
+        two.verboseLevel = WifiDebugRingBufferVerboseLevel.DEFAULT;
+
+        ArrayList<WifiDebugRingBufferStatus> halBufferStatus = new ArrayList<>(2);
+        halBufferStatus.add(one);
+        halBufferStatus.add(two);
+
+        WifiNative.RingBufferStatus[] actual;
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.getDebugRingBuffersStatusCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, halBufferStatus);
+            }
+        }).when(mIWifiChip).getDebugRingBuffersStatus(any(
+                IWifiChip.getDebugRingBuffersStatusCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        actual = mWifiVendorHal.getRingBufferStatus();
+
+        assertEquals(halBufferStatus.size(), actual.length);
+        assertEquals(oneExpect, actual[0].toString());
+        assertEquals(two.ringId, actual[1].ringBufferId);
+
+    }
+
+    /**
+     * Test that getRingBufferData calls forceDumpToDebugRingBuffer
+     *
+     * Try once before hal start, and twice after (one success, one failure).
+     */
+    @Test
+    public void testForceRingBufferDump() throws Exception {
+        when(mIWifiChip.forceDumpToDebugRingBuffer(eq("Gunk"))).thenReturn(mWifiStatusSuccess);
+        when(mIWifiChip.forceDumpToDebugRingBuffer(eq("Glop"))).thenReturn(mWifiStatusFailure);
+
+        assertFalse(mWifiVendorHal.getRingBufferData("Gunk")); // hal not started
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.getRingBufferData("Gunk")); // mocked call succeeds
+        assertFalse(mWifiVendorHal.getRingBufferData("Glop")); // mocked call fails
+
+        verify(mIWifiChip).forceDumpToDebugRingBuffer("Gunk");
+        verify(mIWifiChip).forceDumpToDebugRingBuffer("Glop");
+    }
+
+    /**
+     * Tests the start of packet fate monitoring.
+     *
+     * Try once before hal start, and once after (one success, one failure).
+     */
+    @Test
+    public void testStartPktFateMonitoring() throws Exception {
+        when(mIWifiStaIface.startDebugPacketFateMonitoring()).thenReturn(mWifiStatusSuccess);
+
+        assertFalse(mWifiVendorHal.startPktFateMonitoring());
+        verify(mIWifiStaIface, never()).startDebugPacketFateMonitoring();
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertTrue(mWifiVendorHal.startPktFateMonitoring());
+        verify(mIWifiStaIface).startDebugPacketFateMonitoring();
+    }
+
+    /**
+     * Tests the retrieval of tx packet fates.
+     *
+     * Try once before hal start, and once after.
+     */
+    @Test
+    public void testGetTxPktFates() throws Exception {
+        byte[] frameContentBytes = new byte[30];
+        new Random().nextBytes(frameContentBytes);
+        WifiDebugTxPacketFateReport fateReport = new WifiDebugTxPacketFateReport();
+        fateReport.fate = WifiDebugTxPacketFate.DRV_QUEUED;
+        fateReport.frameInfo.driverTimestampUsec = new Random().nextLong();
+        fateReport.frameInfo.frameType = WifiDebugPacketFateFrameType.ETHERNET_II;
+        fateReport.frameInfo.frameContent.addAll(
+                NativeUtil.byteArrayToArrayList(frameContentBytes));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getDebugTxPacketFatesCallback cb) {
+                cb.onValues(mWifiStatusSuccess,
+                        new ArrayList<WifiDebugTxPacketFateReport>(Arrays.asList(fateReport)));
+            }
+        }).when(mIWifiStaIface)
+                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
+
+        WifiNative.TxFateReport[] retrievedFates = new WifiNative.TxFateReport[1];
+        assertFalse(mWifiVendorHal.getTxPktFates(retrievedFates));
+        verify(mIWifiStaIface, never())
+                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.getTxPktFates(retrievedFates));
+        verify(mIWifiStaIface)
+                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
+        assertEquals(WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED, retrievedFates[0].mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec,
+                retrievedFates[0].mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, retrievedFates[0].mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
+    }
+
+    /**
+     * Tests the retrieval of tx packet fates when the number of fates retrieved exceeds the
+     * input array.
+     *
+     * Try once before hal start, and once after.
+     */
+    @Test
+    public void testGetTxPktFatesExceedsInputArrayLength() throws Exception {
+        byte[] frameContentBytes = new byte[30];
+        new Random().nextBytes(frameContentBytes);
+        WifiDebugTxPacketFateReport fateReport = new WifiDebugTxPacketFateReport();
+        fateReport.fate = WifiDebugTxPacketFate.FW_DROP_OTHER;
+        fateReport.frameInfo.driverTimestampUsec = new Random().nextLong();
+        fateReport.frameInfo.frameType = WifiDebugPacketFateFrameType.MGMT_80211;
+        fateReport.frameInfo.frameContent.addAll(
+                NativeUtil.byteArrayToArrayList(frameContentBytes));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getDebugTxPacketFatesCallback cb) {
+                cb.onValues(mWifiStatusSuccess,
+                        new ArrayList<WifiDebugTxPacketFateReport>(Arrays.asList(
+                                fateReport, fateReport)));
+            }
+        }).when(mIWifiStaIface)
+                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
+
+        WifiNative.TxFateReport[] retrievedFates = new WifiNative.TxFateReport[1];
+        assertFalse(mWifiVendorHal.getTxPktFates(retrievedFates));
+        verify(mIWifiStaIface, never())
+                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.getTxPktFates(retrievedFates));
+        verify(mIWifiStaIface)
+                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
+        assertEquals(WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER, retrievedFates[0].mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec,
+                retrievedFates[0].mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_80211_MGMT, retrievedFates[0].mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
+    }
+
+    /**
+     * Tests the retrieval of rx packet fates.
+     *
+     * Try once before hal start, and once after.
+     */
+    @Test
+    public void testGetRxPktFates() throws Exception {
+        byte[] frameContentBytes = new byte[30];
+        new Random().nextBytes(frameContentBytes);
+        WifiDebugRxPacketFateReport fateReport = new WifiDebugRxPacketFateReport();
+        fateReport.fate = WifiDebugRxPacketFate.SUCCESS;
+        fateReport.frameInfo.driverTimestampUsec = new Random().nextLong();
+        fateReport.frameInfo.frameType = WifiDebugPacketFateFrameType.ETHERNET_II;
+        fateReport.frameInfo.frameContent.addAll(
+                NativeUtil.byteArrayToArrayList(frameContentBytes));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getDebugRxPacketFatesCallback cb) {
+                cb.onValues(mWifiStatusSuccess,
+                        new ArrayList<WifiDebugRxPacketFateReport>(Arrays.asList(fateReport)));
+            }
+        }).when(mIWifiStaIface)
+                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+
+        WifiNative.RxFateReport[] retrievedFates = new WifiNative.RxFateReport[1];
+        assertFalse(mWifiVendorHal.getRxPktFates(retrievedFates));
+        verify(mIWifiStaIface, never())
+                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.getRxPktFates(retrievedFates));
+        verify(mIWifiStaIface)
+                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+        assertEquals(WifiLoggerHal.RX_PKT_FATE_SUCCESS, retrievedFates[0].mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec,
+                retrievedFates[0].mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, retrievedFates[0].mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
+    }
+
+    /**
+     * Tests the retrieval of rx packet fates when the number of fates retrieved exceeds the
+     * input array.
+     *
+     * Try once before hal start, and once after.
+     */
+    @Test
+    public void testGetRxPktFatesExceedsInputArrayLength() throws Exception {
+        byte[] frameContentBytes = new byte[30];
+        new Random().nextBytes(frameContentBytes);
+        WifiDebugRxPacketFateReport fateReport = new WifiDebugRxPacketFateReport();
+        fateReport.fate = WifiDebugRxPacketFate.FW_DROP_FILTER;
+        fateReport.frameInfo.driverTimestampUsec = new Random().nextLong();
+        fateReport.frameInfo.frameType = WifiDebugPacketFateFrameType.MGMT_80211;
+        fateReport.frameInfo.frameContent.addAll(
+                NativeUtil.byteArrayToArrayList(frameContentBytes));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiStaIface.getDebugRxPacketFatesCallback cb) {
+                cb.onValues(mWifiStatusSuccess,
+                        new ArrayList<WifiDebugRxPacketFateReport>(Arrays.asList(
+                                fateReport, fateReport)));
+            }
+        }).when(mIWifiStaIface)
+                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+
+        WifiNative.RxFateReport[] retrievedFates = new WifiNative.RxFateReport[1];
+        assertFalse(mWifiVendorHal.getRxPktFates(retrievedFates));
+        verify(mIWifiStaIface, never())
+                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.getRxPktFates(retrievedFates));
+        verify(mIWifiStaIface)
+                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+        assertEquals(WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER, retrievedFates[0].mFate);
+        assertEquals(fateReport.frameInfo.driverTimestampUsec,
+                retrievedFates[0].mDriverTimestampUSec);
+        assertEquals(WifiLoggerHal.FRAME_TYPE_80211_MGMT, retrievedFates[0].mFrameType);
+        assertArrayEquals(frameContentBytes, retrievedFates[0].mFrameBytes);
+    }
+
+    /**
+     * Tests the failure to retrieve tx packet fates when the input array is empty.
+     */
+    @Test
+    public void testGetTxPktFatesEmptyInputArray() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertFalse(mWifiVendorHal.getTxPktFates(new WifiNative.TxFateReport[0]));
+        verify(mIWifiStaIface, never())
+                .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
+    }
+
+    /**
+     * Tests the failure to retrieve rx packet fates when the input array is empty.
+     */
+    @Test
+    public void testGetRxPktFatesEmptyInputArray() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertFalse(mWifiVendorHal.getRxPktFates(new WifiNative.RxFateReport[0]));
+        verify(mIWifiStaIface, never())
+                .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
+    }
+
+    /**
+     * Tests the nd offload enable/disable.
+     */
+    @Test
+    public void testEnableDisableNdOffload() throws Exception {
+        when(mIWifiStaIface.enableNdOffload(anyBoolean())).thenReturn(mWifiStatusSuccess);
+
+        assertFalse(mWifiVendorHal.configureNeighborDiscoveryOffload(true));
+        verify(mIWifiStaIface, never()).enableNdOffload(anyBoolean());
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.configureNeighborDiscoveryOffload(true));
+        verify(mIWifiStaIface).enableNdOffload(eq(true));
+        assertTrue(mWifiVendorHal.configureNeighborDiscoveryOffload(false));
+        verify(mIWifiStaIface).enableNdOffload(eq(false));
+    }
+
+    /**
+     * Tests the nd offload enable failure.
+     */
+    @Test
+    public void testEnableNdOffloadFailure() throws Exception {
+        when(mIWifiStaIface.enableNdOffload(eq(true))).thenReturn(mWifiStatusFailure);
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertFalse(mWifiVendorHal.configureNeighborDiscoveryOffload(true));
+        verify(mIWifiStaIface).enableNdOffload(eq(true));
+    }
+
+    /**
+     * Tests the retrieval of wlan wake reason stats.
+     */
+    @Test
+    public void testGetWlanWakeReasonCount() throws Exception {
+        WifiDebugHostWakeReasonStats stats = new WifiDebugHostWakeReasonStats();
+        Random rand = new Random();
+        stats.totalCmdEventWakeCnt = rand.nextInt();
+        stats.totalDriverFwLocalWakeCnt = rand.nextInt();
+        stats.totalRxPacketWakeCnt = rand.nextInt();
+        stats.rxPktWakeDetails.rxUnicastCnt = rand.nextInt();
+        stats.rxPktWakeDetails.rxMulticastCnt = rand.nextInt();
+        stats.rxIcmpPkWakeDetails.icmpPkt = rand.nextInt();
+        stats.rxIcmpPkWakeDetails.icmp6Pkt = rand.nextInt();
+        stats.rxMulticastPkWakeDetails.ipv4RxMulticastAddrCnt = rand.nextInt();
+        stats.rxMulticastPkWakeDetails.ipv6RxMulticastAddrCnt = rand.nextInt();
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.getDebugHostWakeReasonStatsCallback cb) {
+                cb.onValues(mWifiStatusSuccess, stats);
+            }
+        }).when(mIWifiChip).getDebugHostWakeReasonStats(
+                any(IWifiChip.getDebugHostWakeReasonStatsCallback.class));
+
+        assertNull(mWifiVendorHal.getWlanWakeReasonCount());
+        verify(mIWifiChip, never())
+                .getDebugHostWakeReasonStats(
+                        any(IWifiChip.getDebugHostWakeReasonStatsCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        WifiWakeReasonAndCounts retrievedStats = mWifiVendorHal.getWlanWakeReasonCount();
+        verify(mIWifiChip).getDebugHostWakeReasonStats(
+                any(IWifiChip.getDebugHostWakeReasonStatsCallback.class));
+        assertNotNull(retrievedStats);
+        assertEquals(stats.totalCmdEventWakeCnt, retrievedStats.totalCmdEventWake);
+        assertEquals(stats.totalDriverFwLocalWakeCnt, retrievedStats.totalDriverFwLocalWake);
+        assertEquals(stats.totalRxPacketWakeCnt, retrievedStats.totalRxDataWake);
+        assertEquals(stats.rxPktWakeDetails.rxUnicastCnt, retrievedStats.rxUnicast);
+        assertEquals(stats.rxPktWakeDetails.rxMulticastCnt, retrievedStats.rxMulticast);
+        assertEquals(stats.rxIcmpPkWakeDetails.icmpPkt, retrievedStats.icmp);
+        assertEquals(stats.rxIcmpPkWakeDetails.icmp6Pkt, retrievedStats.icmp6);
+        assertEquals(stats.rxMulticastPkWakeDetails.ipv4RxMulticastAddrCnt,
+                retrievedStats.ipv4RxMulticast);
+        assertEquals(stats.rxMulticastPkWakeDetails.ipv6RxMulticastAddrCnt,
+                retrievedStats.ipv6Multicast);
+    }
+
+    /**
+     * Tests the failure in retrieval of wlan wake reason stats.
+     */
+    @Test
+    public void testGetWlanWakeReasonCountFailure() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.getDebugHostWakeReasonStatsCallback cb) {
+                cb.onValues(mWifiStatusFailure, new WifiDebugHostWakeReasonStats());
+            }
+        }).when(mIWifiChip).getDebugHostWakeReasonStats(
+                any(IWifiChip.getDebugHostWakeReasonStatsCallback.class));
+
+        // This should work in both AP & STA mode.
+        assertTrue(mWifiVendorHal.startVendorHalAp());
+
+        assertNull(mWifiVendorHal.getWlanWakeReasonCount());
+        verify(mIWifiChip).getDebugHostWakeReasonStats(
+                any(IWifiChip.getDebugHostWakeReasonStatsCallback.class));
+    }
+
+    /**
+     * Test that getFwMemoryDump is properly plumbed
+     */
+    @Test
+    public void testGetFwMemoryDump() throws Exception {
+        byte [] sample = NativeUtil.hexStringToByteArray("268c7a3fbfa4661c0bdd6a36");
+        ArrayList<Byte> halBlob = NativeUtil.byteArrayToArrayList(sample);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.requestFirmwareDebugDumpCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, halBlob);
+            }
+        }).when(mIWifiChip).requestFirmwareDebugDump(any(
+                IWifiChip.requestFirmwareDebugDumpCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertArrayEquals(sample, mWifiVendorHal.getFwMemoryDump());
+    }
+
+    /**
+     * Test that getDriverStateDump is properly plumbed
+     *
+     * Just for variety, use AP mode here.
+     */
+    @Test
+    public void testGetDriverStateDump() throws Exception {
+        byte [] sample = NativeUtil.hexStringToByteArray("e83ff543cf80083e6459d20f");
+        ArrayList<Byte> halBlob = NativeUtil.byteArrayToArrayList(sample);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiChip.requestDriverDebugDumpCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, halBlob);
+            }
+        }).when(mIWifiChip).requestDriverDebugDump(any(
+                IWifiChip.requestDriverDebugDumpCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHalAp());
+        assertArrayEquals(sample, mWifiVendorHal.getDriverStateDump());
+    }
+
+    /**
+     * Test that background scan failure is handled correctly.
+     */
+    @Test
+    public void testBgScanFailureCallback() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiStaIfaceEventCallback);
+
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        startBgScan(eventHandler);
+
+        mIWifiStaIfaceEventCallback.onBackgroundScanFailure(mWifiVendorHal.mScan.cmdId);
+        verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
+    }
+
+    /**
+     * Test that background scan failure with wrong id is not reported.
+     */
+    @Test
+    public void testBgScanFailureCallbackWithInvalidCmdId() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiStaIfaceEventCallback);
+
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        startBgScan(eventHandler);
+
+        mIWifiStaIfaceEventCallback.onBackgroundScanFailure(mWifiVendorHal.mScan.cmdId + 1);
+        verify(eventHandler, never()).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
+    }
+
+    /**
+     * Test that background scan full results are handled correctly.
+     */
+    @Test
+    public void testBgScanFullScanResults() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiStaIfaceEventCallback);
+
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        startBgScan(eventHandler);
+
+        Pair<StaScanResult, ScanResult> result = createHidlAndFrameworkBgScanResult();
+        mIWifiStaIfaceEventCallback.onBackgroundFullScanResult(
+                mWifiVendorHal.mScan.cmdId, 5, result.first);
+
+        ArgumentCaptor<ScanResult> scanResultCaptor = ArgumentCaptor.forClass(ScanResult.class);
+        verify(eventHandler).onFullScanResult(scanResultCaptor.capture(), eq(5));
+
+        assertScanResultEqual(result.second, scanResultCaptor.getValue());
+    }
+
+    /**
+     * Test that background scan results are handled correctly.
+     */
+    @Test
+    public void testBgScanScanResults() throws Exception {
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiStaIfaceEventCallback);
+
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        startBgScan(eventHandler);
+
+        Pair<ArrayList<StaScanData>, ArrayList<WifiScanner.ScanData>> data =
+                createHidlAndFrameworkBgScanDatas();
+        mIWifiStaIfaceEventCallback.onBackgroundScanResults(
+                mWifiVendorHal.mScan.cmdId, data.first);
+
+        verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+        assertScanDatasEqual(
+                data.second, Arrays.asList(mWifiVendorHal.mScan.latestScanResults));
+    }
+
+    /**
+     * Test that starting a new background scan when one is active will stop the previous one.
+     */
+    @Test
+    public void testBgScanReplacement() throws Exception {
+        when(mIWifiStaIface.stopBackgroundScan(anyInt())).thenReturn(mWifiStatusSuccess);
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiStaIfaceEventCallback);
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        startBgScan(eventHandler);
+        int cmdId1 = mWifiVendorHal.mScan.cmdId;
+        startBgScan(eventHandler);
+        assertNotEquals(mWifiVendorHal.mScan.cmdId, cmdId1);
+        verify(mIWifiStaIface, times(2)).startBackgroundScan(anyInt(), any());
+        verify(mIWifiStaIface).stopBackgroundScan(cmdId1);
+    }
+
+    /**
+     * Test stopping a background scan.
+     */
+    @Test
+    public void testBgScanStop() throws Exception {
+        when(mIWifiStaIface.stopBackgroundScan(anyInt())).thenReturn(mWifiStatusSuccess);
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiStaIfaceEventCallback);
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        startBgScan(eventHandler);
+
+        int cmdId = mWifiVendorHal.mScan.cmdId;
+
+        mWifiVendorHal.stopBgScan();
+        mWifiVendorHal.stopBgScan(); // second call should not do anything
+        verify(mIWifiStaIface).stopBackgroundScan(cmdId); // Should be called just once
+    }
+
+    /**
+     * Test pausing and restarting a background scan.
+     */
+    @Test
+    public void testBgScanPauseAndRestart() throws Exception {
+        when(mIWifiStaIface.stopBackgroundScan(anyInt())).thenReturn(mWifiStatusSuccess);
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiStaIfaceEventCallback);
+        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
+        startBgScan(eventHandler);
+
+        int cmdId = mWifiVendorHal.mScan.cmdId;
+
+        mWifiVendorHal.pauseBgScan();
+        mWifiVendorHal.restartBgScan();
+        verify(mIWifiStaIface).stopBackgroundScan(cmdId); // Should be called just once
+        verify(mIWifiStaIface, times(2)).startBackgroundScan(eq(cmdId), any());
+    }
+
+    /**
+     * Test the handling of log handler set.
+     */
+    @Test
+    public void testSetLogHandler() throws Exception {
+        when(mIWifiChip.enableDebugErrorAlerts(anyBoolean())).thenReturn(mWifiStatusSuccess);
+
+        WifiNative.WifiLoggerEventHandler eventHandler =
+                mock(WifiNative.WifiLoggerEventHandler.class);
+
+        assertFalse(mWifiVendorHal.setLoggingEventHandler(eventHandler));
+        verify(mIWifiChip, never()).enableDebugErrorAlerts(anyBoolean());
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        assertTrue(mWifiVendorHal.setLoggingEventHandler(eventHandler));
+        verify(mIWifiChip).enableDebugErrorAlerts(eq(true));
+        reset(mIWifiChip);
+
+        // Second call should fail.
+        assertFalse(mWifiVendorHal.setLoggingEventHandler(eventHandler));
+        verify(mIWifiChip, never()).enableDebugErrorAlerts(anyBoolean());
+    }
+
+    /**
+     * Test the handling of log handler reset.
+     */
+    @Test
+    public void testResetLogHandler() throws Exception {
+        when(mIWifiChip.enableDebugErrorAlerts(anyBoolean())).thenReturn(mWifiStatusSuccess);
+        when(mIWifiChip.stopLoggingToDebugRingBuffer()).thenReturn(mWifiStatusSuccess);
+
+        assertFalse(mWifiVendorHal.resetLogHandler());
+        verify(mIWifiChip, never()).enableDebugErrorAlerts(anyBoolean());
+        verify(mIWifiChip, never()).stopLoggingToDebugRingBuffer();
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+
+        // Not set, so this should fail.
+        assertFalse(mWifiVendorHal.resetLogHandler());
+        verify(mIWifiChip, never()).enableDebugErrorAlerts(anyBoolean());
+        verify(mIWifiChip, never()).stopLoggingToDebugRingBuffer();
+
+        // Now set and then reset.
+        assertTrue(mWifiVendorHal.setLoggingEventHandler(
+                mock(WifiNative.WifiLoggerEventHandler.class)));
+        assertTrue(mWifiVendorHal.resetLogHandler());
+        verify(mIWifiChip).enableDebugErrorAlerts(eq(false));
+        verify(mIWifiChip).stopLoggingToDebugRingBuffer();
+        reset(mIWifiChip);
+
+        // Second reset should fail.
+        assertFalse(mWifiVendorHal.resetLogHandler());
+        verify(mIWifiChip, never()).enableDebugErrorAlerts(anyBoolean());
+        verify(mIWifiChip, never()).stopLoggingToDebugRingBuffer();
+    }
+
+    /**
+     * Test the handling of alert callback.
+     */
+    @Test
+    public void testAlertCallback() throws Exception {
+        when(mIWifiChip.enableDebugErrorAlerts(anyBoolean())).thenReturn(mWifiStatusSuccess);
+        when(mIWifiChip.stopLoggingToDebugRingBuffer()).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiChipEventCallback);
+
+        int errorCode = 5;
+        byte[] errorData = new byte[45];
+        new Random().nextBytes(errorData);
+
+        // Randomly raise the HIDL callback before we register for the log callback.
+        // This should be safely ignored. (Not trigger NPE.)
+        mIWifiChipEventCallback.onDebugErrorAlert(
+                errorCode, NativeUtil.byteArrayToArrayList(errorData));
+        mLooper.dispatchAll();
+
+        WifiNative.WifiLoggerEventHandler eventHandler =
+                mock(WifiNative.WifiLoggerEventHandler.class);
+        assertTrue(mWifiVendorHal.setLoggingEventHandler(eventHandler));
+        verify(mIWifiChip).enableDebugErrorAlerts(eq(true));
+
+        // Now raise the HIDL callback, this should be properly handled.
+        mIWifiChipEventCallback.onDebugErrorAlert(
+                errorCode, NativeUtil.byteArrayToArrayList(errorData));
+        mLooper.dispatchAll();
+        verify(eventHandler).onWifiAlert(eq(errorCode), eq(errorData));
+
+        // Now stop the logging and invoke the callback. This should be ignored.
+        reset(eventHandler);
+        assertTrue(mWifiVendorHal.resetLogHandler());
+        mIWifiChipEventCallback.onDebugErrorAlert(
+                errorCode, NativeUtil.byteArrayToArrayList(errorData));
+        mLooper.dispatchAll();
+        verify(eventHandler, never()).onWifiAlert(anyInt(), anyObject());
+    }
+
+    /**
+     * Test the handling of ring buffer callback.
+     */
+    @Test
+    public void testRingBufferDataCallback() throws Exception {
+        when(mIWifiChip.enableDebugErrorAlerts(anyBoolean())).thenReturn(mWifiStatusSuccess);
+        when(mIWifiChip.stopLoggingToDebugRingBuffer()).thenReturn(mWifiStatusSuccess);
+
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        assertNotNull(mIWifiChipEventCallback);
+
+        byte[] errorData = new byte[45];
+        new Random().nextBytes(errorData);
+
+        // Randomly raise the HIDL callback before we register for the log callback.
+        // This should be safely ignored. (Not trigger NPE.)
+        mIWifiChipEventCallback.onDebugRingBufferDataAvailable(
+                new WifiDebugRingBufferStatus(), NativeUtil.byteArrayToArrayList(errorData));
+        mLooper.dispatchAll();
+
+        WifiNative.WifiLoggerEventHandler eventHandler =
+                mock(WifiNative.WifiLoggerEventHandler.class);
+        assertTrue(mWifiVendorHal.setLoggingEventHandler(eventHandler));
+        verify(mIWifiChip).enableDebugErrorAlerts(eq(true));
+
+        // Now raise the HIDL callback, this should be properly handled.
+        mIWifiChipEventCallback.onDebugRingBufferDataAvailable(
+                new WifiDebugRingBufferStatus(), NativeUtil.byteArrayToArrayList(errorData));
+        mLooper.dispatchAll();
+        verify(eventHandler).onRingBufferData(
+                any(WifiNative.RingBufferStatus.class), eq(errorData));
+
+        // Now stop the logging and invoke the callback. This should be ignored.
+        reset(eventHandler);
+        assertTrue(mWifiVendorHal.resetLogHandler());
+        mIWifiChipEventCallback.onDebugRingBufferDataAvailable(
+                new WifiDebugRingBufferStatus(), NativeUtil.byteArrayToArrayList(errorData));
+        mLooper.dispatchAll();
+        verify(eventHandler, never()).onRingBufferData(anyObject(), anyObject());
+    }
+
+    /**
+     * Test the handling of Vendor HAL death.
+     */
+    @Test
+    public void testVendorHalDeath() {
+        // Invoke the HAL device manager status callback with ready set to false to indicate the
+        // death of the HAL.
+        when(mHalDeviceManager.isReady()).thenReturn(false);
+        mHalDeviceManagerStatusCallbacks.onStatusChanged();
+
+        verify(mVendorHalDeathHandler).onDeath();
+    }
+
+    private void startBgScan(WifiNative.ScanEventHandler eventHandler) throws Exception {
+        when(mIWifiStaIface.startBackgroundScan(
+                anyInt(), any(StaBackgroundScanParameters.class))).thenReturn(mWifiStatusSuccess);
+        WifiNative.ScanSettings settings = new WifiNative.ScanSettings();
+        settings.num_buckets = 1;
+        WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
+        bucketSettings.bucket = 0;
+        bucketSettings.period_ms = 16000;
+        bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
+        settings.buckets = new WifiNative.BucketSettings[] {bucketSettings};
+        assertTrue(mWifiVendorHal.startBgScan(settings, eventHandler));
+    }
+
+    // Create a pair of HIDL scan result and its corresponding framework scan result for
+    // comparison.
+    private Pair<StaScanResult, ScanResult> createHidlAndFrameworkBgScanResult() {
+        StaScanResult staScanResult = new StaScanResult();
+        Random random = new Random();
+        byte[] ssid = new byte[8];
+        random.nextBytes(ssid);
+        staScanResult.ssid.addAll(NativeUtil.byteArrayToArrayList(ssid));
+        random.nextBytes(staScanResult.bssid);
+        staScanResult.frequency = 2432;
+        staScanResult.rssi = -45;
+        staScanResult.timeStampInUs = 5;
+        WifiInformationElement ie1 = new WifiInformationElement();
+        byte[] ie1_data = new byte[56];
+        random.nextBytes(ie1_data);
+        ie1.id = 1;
+        ie1.data.addAll(NativeUtil.byteArrayToArrayList(ie1_data));
+        staScanResult.informationElements.add(ie1);
+
+        // Now create the corresponding Scan result structure.
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = NativeUtil.encodeSsid(staScanResult.ssid);
+        scanResult.BSSID = NativeUtil.macAddressFromByteArray(staScanResult.bssid);
+        scanResult.wifiSsid = WifiSsid.createFromByteArray(ssid);
+        scanResult.frequency = staScanResult.frequency;
+        scanResult.level = staScanResult.rssi;
+        scanResult.timestamp = staScanResult.timeStampInUs;
+        scanResult.bytes = new byte[57];
+        scanResult.bytes[0] = ie1.id;
+        System.arraycopy(ie1_data, 0, scanResult.bytes, 1, ie1_data.length);
+
+        return Pair.create(staScanResult, scanResult);
+    }
+
+    // Create a pair of HIDL scan datas and its corresponding framework scan datas for
+    // comparison.
+    private Pair<ArrayList<StaScanData>, ArrayList<WifiScanner.ScanData>>
+            createHidlAndFrameworkBgScanDatas() {
+        ArrayList<StaScanData> staScanDatas = new ArrayList<>();
+        StaScanData staScanData = new StaScanData();
+
+        Pair<StaScanResult, ScanResult> result = createHidlAndFrameworkBgScanResult();
+        staScanData.results.add(result.first);
+        staScanData.bucketsScanned = 5;
+        staScanData.flags = StaScanDataFlagMask.INTERRUPTED;
+        staScanDatas.add(staScanData);
+
+        ArrayList<WifiScanner.ScanData> scanDatas = new ArrayList<>();
+        ScanResult[] scanResults = new ScanResult[1];
+        scanResults[0] = result.second;
+        WifiScanner.ScanData scanData =
+                new WifiScanner.ScanData(mWifiVendorHal.mScan.cmdId, 1,
+                        staScanData.bucketsScanned, false, scanResults);
+        scanDatas.add(scanData);
+        return Pair.create(staScanDatas, scanDatas);
+    }
+
+    private void assertScanResultEqual(ScanResult expected, ScanResult actual) {
+        assertEquals(expected.SSID, actual.SSID);
+        assertEquals(expected.wifiSsid.getHexString(), actual.wifiSsid.getHexString());
+        assertEquals(expected.BSSID, actual.BSSID);
+        assertEquals(expected.frequency, actual.frequency);
+        assertEquals(expected.level, actual.level);
+        assertEquals(expected.timestamp, actual.timestamp);
+        assertArrayEquals(expected.bytes, actual.bytes);
+    }
+
+    private void assertScanResultsEqual(ScanResult[] expected, ScanResult[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertScanResultEqual(expected[i], actual[i]);
+        }
+    }
+
+    private void assertScanDataEqual(WifiScanner.ScanData expected, WifiScanner.ScanData actual) {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getFlags(), actual.getFlags());
+        assertEquals(expected.getBucketsScanned(), actual.getBucketsScanned());
+        assertScanResultsEqual(expected.getResults(), actual.getResults());
+    }
+
+    private void assertScanDatasEqual(
+            List<WifiScanner.ScanData> expected, List<WifiScanner.ScanData> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (int i = 0; i < expected.size(); i++) {
+            assertScanDataEqual(expected.get(i), actual.get(i));
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java b/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
new file mode 100644
index 0000000..2617331
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.IApInterface;
+import android.net.wifi.IClientInterface;
+import android.net.wifi.IPnoScanEvent;
+import android.net.wifi.IScanEvent;
+import android.net.wifi.IWifiScannerImpl;
+import android.net.wifi.IWificond;
+import android.net.wifi.WifiScanner;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.wificond.ChannelSettings;
+import com.android.server.wifi.wificond.HiddenNetwork;
+import com.android.server.wifi.wificond.NativeScanResult;
+import com.android.server.wifi.wificond.PnoSettings;
+import com.android.server.wifi.wificond.SingleScanSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WificondControl}.
+ */
+@SmallTest
+public class WificondControlTest {
+    private WifiInjector mWifiInjector;
+    private WifiMonitor mWifiMonitor;
+    private WificondControl mWificondControl;
+    private static final String TEST_INTERFACE_NAME = "test_wlan_if";
+    private static final byte[] TEST_SSID =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_BSSID =
+            new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
+                        (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
+    // This the IE buffer which is consistent with TEST_SSID.
+    private static final byte[] TEST_INFO_ELEMENT =
+            new byte[] {
+                    // Element ID for SSID.
+                    (byte) 0x00,
+                    // Length of the SSID: 0x0b or 11.
+                    (byte) 0x0b,
+                    // This is string "GoogleGuest"
+                    'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+
+    private static final int TEST_FREQUENCY = 2456;
+    private static final int TEST_SIGNAL_MBM = -4500;
+    private static final long TEST_TSF = 34455441;
+    private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }};
+    private static final boolean TEST_ASSOCIATED = true;
+    private static final NativeScanResult MOCK_NATIVE_SCAN_RESULT =
+            new NativeScanResult() {{
+                ssid = TEST_SSID;
+                bssid = TEST_BSSID;
+                infoElement = TEST_INFO_ELEMENT;
+                frequency = TEST_FREQUENCY;
+                signalMbm = TEST_SIGNAL_MBM;
+                capability = TEST_CAPABILITY;
+                associated = TEST_ASSOCIATED;
+            }};
+
+    private static final Set<Integer> SCAN_FREQ_SET =
+            new HashSet<Integer>() {{
+                add(2410);
+                add(2450);
+                add(5050);
+                add(5200);
+            }};
+    private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
+    private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
+
+    private static final Set<String> SCAN_HIDDEN_NETWORK_SSID_SET =
+            new HashSet<String>() {{
+                add(TEST_QUOTED_SSID_1);
+                add(TEST_QUOTED_SSID_2);
+            }};
+
+
+    private static final WifiNative.PnoSettings TEST_PNO_SETTINGS =
+            new WifiNative.PnoSettings() {{
+                isConnected = false;
+                periodInMs = 6000;
+                networkList = new WifiNative.PnoNetwork[2];
+                networkList[0] = new WifiNative.PnoNetwork();
+                networkList[1] = new WifiNative.PnoNetwork();
+                networkList[0].ssid = TEST_QUOTED_SSID_1;
+                networkList[0].flags = WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN;
+                networkList[1].ssid = TEST_QUOTED_SSID_2;
+                networkList[1].flags = 0;
+            }};
+
+    @Before
+    public void setUp() throws Exception {
+        mWifiInjector = mock(WifiInjector.class);
+        mWifiMonitor = mock(WifiMonitor.class);
+        mWificondControl = new WificondControl(mWifiInjector, mWifiMonitor);
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() calls Wificond.
+     */
+    @Test
+    public void testSetupDriverForClientMode() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+        verify(wificond).createClientInterface();
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() calls subscribeScanEvents().
+     */
+    @Test
+    public void testSetupDriverForClientModeCallsScanEventSubscripiton() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        verify(scanner).subscribeScanEvents(any(IScanEvent.class));
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() returns null when wificond is not started.
+     */
+    @Test
+    public void testSetupDriverForClientModeErrorWhenWificondIsNotStarted() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(null);
+
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(null, returnedClientInterface);
+    }
+
+    /**
+     * Verifies that setupDriverForClientMode() returns null when wificond failed to setup client
+     * interface.
+     */
+    @Test
+    public void testSetupDriverForClientModeErrorWhenWificondFailedToSetupInterface()
+            throws Exception {
+        IWificond wificond = mock(IWificond.class);
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(null);
+
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(null, returnedClientInterface);
+    }
+
+    /**
+     * Verifies that setupDriverForSoftApMode() calls wificond.
+     */
+    @Test
+    public void testSetupDriverForSoftApMode() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IApInterface apInterface = mock(IApInterface.class);
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createApInterface()).thenReturn(apInterface);
+
+        IApInterface returnedApInterface = mWificondControl.setupDriverForSoftApMode();
+        assertEquals(apInterface, returnedApInterface);
+        verify(wificond).createApInterface();
+    }
+
+    /**
+     * Verifies that setupDriverForSoftAp() returns null when wificond is not started.
+     */
+    @Test
+    public void testSetupDriverForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(null);
+
+        IApInterface returnedApInterface = mWificondControl.setupDriverForSoftApMode();
+
+        assertEquals(null, returnedApInterface);
+    }
+
+    /**
+     * Verifies that setupDriverForSoftApMode() returns null when wificond failed to setup
+     * AP interface.
+     */
+    @Test
+    public void testSetupDriverForSoftApModeErrorWhenWificondFailedToSetupInterface()
+            throws Exception {
+        IWificond wificond = mock(IWificond.class);
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createApInterface()).thenReturn(null);
+
+        IApInterface returnedApInterface = mWificondControl.setupDriverForSoftApMode();
+        assertEquals(null, returnedApInterface);
+    }
+
+    /**
+     * Verifies that enableSupplicant() calls wificond.
+     */
+    @Test
+    public void testEnableSupplicant() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(clientInterface.enableSupplicant()).thenReturn(true);
+
+        mWificondControl.setupDriverForClientMode();
+        assertTrue(mWificondControl.enableSupplicant());
+        verify(clientInterface).enableSupplicant();
+    }
+
+    /**
+     * Verifies that enableSupplicant() returns false when there is no configured
+     * client interface.
+     */
+    @Test
+    public void testEnableSupplicantErrorWhenNoClientInterfaceConfigured() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        // Configure client interface.
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Enabling supplicant should fail.
+        assertFalse(mWificondControl.enableSupplicant());
+    }
+
+    /**
+     * Verifies that disableSupplicant() calls wificond.
+     */
+    @Test
+    public void testDisableSupplicant() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(clientInterface.disableSupplicant()).thenReturn(true);
+
+        mWificondControl.setupDriverForClientMode();
+        assertTrue(mWificondControl.disableSupplicant());
+        verify(clientInterface).disableSupplicant();
+    }
+
+    /**
+     * Verifies that disableSupplicant() returns false when there is no configured
+     * client interface.
+     */
+    @Test
+    public void testDisableSupplicantErrorWhenNoClientInterfaceConfigured() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        // Configure client interface.
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Disabling supplicant should fail.
+        assertFalse(mWificondControl.disableSupplicant());
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls wificond.
+     */
+    @Test
+    public void testTearDownInterfaces() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        verify(wificond).tearDownInterfaces();
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls unsubscribeScanEvents() when there was
+     * a configured client interface.
+     */
+    @Test
+    public void testTearDownInterfacesRemovesScanEventSubscription() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        verify(scanner).unsubscribeScanEvents();
+    }
+
+
+    /**
+     * Verifies that tearDownInterfaces() returns false when wificond is not started.
+     */
+    @Test
+    public void testTearDownInterfacesErrorWhenWificondIsNotStarterd() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(null);
+
+        assertFalse(mWificondControl.tearDownInterfaces());
+    }
+
+    /**
+     * Verifies that signalPoll() calls wificond.
+     */
+    @Test
+    public void testSignalPoll() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        mWificondControl.setupDriverForClientMode();
+        mWificondControl.signalPoll();
+        verify(clientInterface).signalPoll();
+    }
+
+    /**
+     * Verifies that signalPoll() returns null when there is no configured client interface.
+     */
+    @Test
+    public void testSignalPollErrorWhenNoClientInterfaceConfigured() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        // Configure client interface.
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Signal poll should fail.
+        assertEquals(null, mWificondControl.signalPoll());
+    }
+
+    /**
+     * Verifies that getTxPacketCounters() calls wificond.
+     */
+    @Test
+    public void testGetTxPacketCounters() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        mWificondControl.setupDriverForClientMode();
+        mWificondControl.getTxPacketCounters();
+        verify(clientInterface).getPacketCounters();
+    }
+
+    /**
+     * Verifies that getTxPacketCounters() returns null when there is no configured client
+     * interface.
+     */
+    @Test
+    public void testGetTxPacketCountersErrorWhenNoClientInterfaceConfigured() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        // Configure client interface.
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Signal poll should fail.
+        assertEquals(null, mWificondControl.getTxPacketCounters());
+    }
+
+    /**
+     * Verifies that getScanResults() returns null when there is no configured client
+     * interface.
+     */
+    @Test
+    public void testGetScanResultsErrorWhenNoClientInterfaceConfigured() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = getMockClientInterface();
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+
+        // Configure client interface.
+        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+        assertEquals(clientInterface, returnedClientInterface);
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // getScanResults should fail.
+        assertEquals(0, mWificondControl.getScanResults().size());
+    }
+
+    /**
+     * Verifies that getScanResults() can parse NativeScanResult from wificond correctly,
+     */
+    @Test
+    public void testGetScanResults() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+        assertNotNull(scanner);
+
+        // Mock the returned array of NativeScanResult.
+        NativeScanResult[] mockScanResults = {MOCK_NATIVE_SCAN_RESULT};
+        when(scanner.getScanResults()).thenReturn(mockScanResults);
+
+        ArrayList<ScanDetail> returnedScanResults = mWificondControl.getScanResults();
+        assertEquals(mockScanResults.length, returnedScanResults.size());
+        // Since NativeScanResult is organized differently from ScanResult, this only checks
+        // a few fields.
+        for (int i = 0; i < mockScanResults.length; i++) {
+            assertArrayEquals(mockScanResults[i].ssid,
+                              returnedScanResults.get(i).getScanResult().SSID.getBytes());
+            assertEquals(mockScanResults[i].frequency,
+                         returnedScanResults.get(i).getScanResult().frequency);
+            assertEquals(mockScanResults[i].tsf,
+                         returnedScanResults.get(i).getScanResult().timestamp);
+        }
+    }
+
+    /**
+     * Verifies that Scan() can convert input parameters to SingleScanSettings correctly.
+     */
+    @Test
+    public void testScan() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        when(scanner.scan(any(SingleScanSettings.class))).thenReturn(true);
+
+        assertTrue(mWificondControl.scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET));
+        verify(scanner).scan(argThat(new ScanMatcher(
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET)));
+    }
+
+    /**
+     * Verifies that Scan() can handle null input parameters correctly.
+     */
+    @Test
+    public void testScanNullParameters() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        when(scanner.scan(any(SingleScanSettings.class))).thenReturn(true);
+
+        assertTrue(mWificondControl.scan(null, null));
+        verify(scanner).scan(argThat(new ScanMatcher(null, null)));
+    }
+
+    /**
+     * Verifies that Scan() can handle wificond scan failure.
+     */
+    @Test
+    public void testScanFailure() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        when(scanner.scan(any(SingleScanSettings.class))).thenReturn(false);
+        assertFalse(mWificondControl.scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET));
+        verify(scanner).scan(any(SingleScanSettings.class));
+    }
+
+    /**
+     * Verifies that startPnoScan() can convert input parameters to PnoSettings correctly.
+     */
+    @Test
+    public void testStartPnoScan() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        when(scanner.startPnoScan(any(PnoSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.startPnoScan(TEST_PNO_SETTINGS));
+        verify(scanner).startPnoScan(argThat(new PnoScanMatcher(TEST_PNO_SETTINGS)));
+    }
+
+    /**
+     * Verifies that stopPnoScan() calls underlying wificond.
+     */
+    @Test
+    public void testStopPnoScan() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        when(scanner.stopPnoScan()).thenReturn(true);
+        assertTrue(mWificondControl.stopPnoScan());
+        verify(scanner).stopPnoScan();
+    }
+
+    /**
+     * Verifies that stopPnoScan() can handle wificond failure.
+     */
+    @Test
+    public void testStopPnoScanFailure() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        when(scanner.stopPnoScan()).thenReturn(false);
+        assertFalse(mWificondControl.stopPnoScan());
+        verify(scanner).stopPnoScan();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+     * reuslt event.
+     */
+    @Test
+    public void testScanResultEvent() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+        verify(scanner).subscribeScanEvents(messageCaptor.capture());
+        IScanEvent scanEvent = messageCaptor.getValue();
+        assertNotNull(scanEvent);
+        scanEvent.OnScanResultReady();
+
+        verify(mWifiMonitor).broadcastScanResultEvent(any(String.class));
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+     * failed event.
+     */
+    @Test
+    public void testScanFailedEvent() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+        verify(scanner).subscribeScanEvents(messageCaptor.capture());
+        IScanEvent scanEvent = messageCaptor.getValue();
+        assertNotNull(scanEvent);
+        scanEvent.OnScanFailed();
+
+        verify(mWifiMonitor).broadcastScanFailedEvent(any(String.class));
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon pno scan
+     * reuslt event.
+     */
+    @Test
+    public void testPnoScanResultEvent() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+        verify(scanner).subscribePnoScanEvents(messageCaptor.capture());
+        IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+        assertNotNull(pnoScanEvent);
+        pnoScanEvent.OnPnoNetworkFound();
+
+        verify(mWifiMonitor).broadcastPnoScanResultEvent(any(String.class));
+    }
+
+    /**
+     * Verifies that abortScan() calls underlying wificond.
+     */
+    @Test
+    public void testAbortScan() throws Exception {
+        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
+
+        mWificondControl.abortScan();
+        verify(scanner).abortScan();
+    }
+
+    /**
+     * Helper method: create a mock IClientInterface which mocks all neccessary operations.
+     * Returns a mock IClientInterface.
+     */
+    private IClientInterface getMockClientInterface() throws Exception {
+        IClientInterface clientInterface = mock(IClientInterface.class);
+        IWifiScannerImpl scanner = mock(IWifiScannerImpl.class);
+
+        when(clientInterface.getWifiScannerImpl()).thenReturn(scanner);
+
+        return clientInterface;
+    }
+
+    /**
+     * Helper method: Setup interface to client mode for mWificondControl.
+     * Returns a mock IWifiScannerImpl.
+     */
+    private IWifiScannerImpl setupClientInterfaceAndCreateMockWificondScanner() throws Exception {
+        IWificond wificond = mock(IWificond.class);
+        IClientInterface clientInterface = mock(IClientInterface.class);
+        IWifiScannerImpl scanner = mock(IWifiScannerImpl.class);
+
+        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(clientInterface.getWifiScannerImpl()).thenReturn(scanner);
+        when(clientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+
+        assertEquals(clientInterface, mWificondControl.setupDriverForClientMode());
+
+        return scanner;
+    }
+
+    // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
+    // matches the provided frequency set and ssid set.
+    private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {
+        private final Set<Integer> mExpectedFreqs;
+        private final Set<String> mExpectedSsids;
+        ScanMatcher(Set<Integer> expectedFreqs, Set<String> expectedSsids) {
+            this.mExpectedFreqs = expectedFreqs;
+            this.mExpectedSsids = expectedSsids;
+        }
+
+        @Override
+        public boolean matches(SingleScanSettings settings) {
+            ArrayList<ChannelSettings> channelSettings = settings.channelSettings;
+            ArrayList<HiddenNetwork> hiddenNetworks = settings.hiddenNetworks;
+            if (mExpectedFreqs != null) {
+                Set<Integer> freqSet = new HashSet<Integer>();
+                for (ChannelSettings channel : channelSettings) {
+                    freqSet.add(channel.frequency);
+                }
+                if (!mExpectedFreqs.equals(freqSet)) {
+                    return false;
+                }
+            } else {
+                if (channelSettings != null && channelSettings.size() > 0) {
+                    return false;
+                }
+            }
+
+            if (mExpectedSsids != null) {
+                Set<String> ssidSet = new HashSet<String>();
+                for (HiddenNetwork network : hiddenNetworks) {
+                    ssidSet.add(NativeUtil.encodeSsid(
+                            NativeUtil.byteArrayToArrayList(network.ssid)));
+                }
+                if (!mExpectedSsids.equals(ssidSet)) {
+                    return false;
+                }
+
+            } else {
+                if (hiddenNetworks != null && hiddenNetworks.size() > 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "ScanMatcher{mExpectedFreqs=" + mExpectedFreqs
+                    + ", mExpectedSsids=" + mExpectedSsids + '}';
+        }
+    }
+
+    // Create a ArgumentMatcher which captures a PnoSettings parameter and checks if it
+    // matches the WifiNative.PnoSettings;
+    private class PnoScanMatcher implements ArgumentMatcher<PnoSettings> {
+        private final WifiNative.PnoSettings mExpectedPnoSettings;
+        PnoScanMatcher(WifiNative.PnoSettings expectedPnoSettings) {
+            this.mExpectedPnoSettings = expectedPnoSettings;
+        }
+        @Override
+        public boolean matches(PnoSettings settings) {
+            if (mExpectedPnoSettings == null) {
+                return false;
+            }
+            if (settings.intervalMs != mExpectedPnoSettings.periodInMs
+                    || settings.min2gRssi != mExpectedPnoSettings.min24GHzRssi
+                    || settings.min5gRssi != mExpectedPnoSettings.min5GHzRssi) {
+                return false;
+            }
+            if (settings.pnoNetworks == null || mExpectedPnoSettings.networkList == null) {
+                return false;
+            }
+            if (settings.pnoNetworks.size() != mExpectedPnoSettings.networkList.length) {
+                return false;
+            }
+
+            for (int i = 0; i < settings.pnoNetworks.size(); i++) {
+                if (!mExpectedPnoSettings.networkList[i].ssid.equals(NativeUtil.encodeSsid(
+                         NativeUtil.byteArrayToArrayList(settings.pnoNetworks.get(i).ssid)))) {
+                    return false;
+                }
+                boolean isNetworkHidden = (mExpectedPnoSettings.networkList[i].flags
+                        & WifiScanner.PnoSettings.PnoNetwork.FLAG_DIRECTED_SCAN) != 0;
+                if (isNetworkHidden != settings.pnoNetworks.get(i).isHidden) {
+                    return false;
+                }
+
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "PnoScanMatcher{" + "mExpectedPnoSettings=" + mExpectedPnoSettings + '}';
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
new file mode 100644
index 0000000..e7a383c
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -0,0 +1,823 @@
+/*
+ * 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.aware;
+
+import static android.hardware.wifi.V1_0.NanDataPathChannelCfg.REQUEST_CHANNEL_SETUP;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyShort;
+import static org.mockito.ArgumentMatchers.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.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.DiscoverySession;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
+import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareManager;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.PublishDiscoverySession;
+import android.net.wifi.aware.SubscribeConfig;
+import android.net.wifi.aware.SubscribeDiscoverySession;
+import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
+import android.net.wifi.aware.WifiAwareSession;
+import android.os.Handler;
+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.Field;
+import java.util.Arrays;
+
+/**
+ * Unit test harness for WifiAwareDataPathStateManager class.
+ */
+@SmallTest
+public class WifiAwareDataPathStateManagerTest {
+    private static final String sAwareInterfacePrefix = "aware_data";
+
+    private TestLooper mMockLooper;
+    private Handler mMockLooperHandler;
+    private WifiAwareStateManager mDut;
+    @Mock private WifiAwareNativeApi mMockNative;
+    @Mock private Context mMockContext;
+    @Mock private IWifiAwareManager mMockAwareService;
+    @Mock private ConnectivityManager mMockCm;
+    @Mock private INetworkManagementService mMockNwMgt;
+    @Mock private WifiAwareDataPathStateManager.NetworkInterfaceWrapper mMockNetworkInterface;
+    @Mock private IWifiAwareEventCallback mMockCallback;
+    @Mock IWifiAwareDiscoverySessionCallback 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();
+        mMockLooperHandler = new Handler(mMockLooper.getLooper());
+
+        mDut = new WifiAwareStateManager();
+        mDut.setNative(mMockNative);
+        mDut.start(mMockContext, mMockLooper.getLooper());
+        mDut.startLate();
+
+        when(mMockNative.getCapabilities(anyShort())).thenReturn(true);
+        when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
+                anyBoolean())).thenReturn(true);
+        when(mMockNative.disable(anyShort())).thenReturn(true);
+        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(true);
+        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+                .thenReturn(true);
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+                any(), anyInt())).thenReturn(true);
+        when(mMockNative.stopPublish(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.stopSubscribe(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.createAwareNetworkInterface(anyShort(), any())).thenReturn(true);
+        when(mMockNative.deleteAwareNetworkInterface(anyShort(), any())).thenReturn(true);
+        when(mMockNative.initiateDataPath(anyShort(), anyInt(), anyInt(), anyInt(),
+                any(), any(), any(), any(),
+                any())).thenReturn(true);
+        when(mMockNative.respondToDataPathRequest(anyShort(), anyBoolean(), anyInt(), any(),
+                any(), any(), any())).thenReturn(true);
+        when(mMockNative.endDataPath(anyShort(), anyInt())).thenReturn(true);
+
+        when(mMockNetworkInterface.configureAgentProperties(any(), any(), anyInt(), any(), any(),
+                any())).thenReturn(true);
+
+        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;
+
+        Capabilities capabilities = new 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.queryCapabilities();
+        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).createAwareNetworkInterface(transactionId.capture(),
+                    interfaceName.capture());
+            collector.checkThat("interface created -- " + i, sAwareInterfacePrefix + 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).deleteAwareNetworkInterface(transactionId.capture(),
+                    interfaceName.capture());
+            int interfaceIndex = Integer.valueOf(
+                    interfaceName.getValue().substring(sAwareInterfacePrefix.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).deleteAwareNetworkInterface(transactionId.capture(),
+                        interfaceName.capture());
+                collector.checkThat("interface delete pre-create -- " + i,
+                        sAwareInterfacePrefix + i, equalTo(interfaceName.getValue()));
+                mDut.onDeleteDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+                mMockLooper.dispatchAll();
+            }
+            inOrder.verify(mMockNative).createAwareNetworkInterface(transactionId.capture(),
+                    interfaceName.capture());
+            collector.checkThat("interface created -- " + i, sAwareInterfacePrefix + 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, false);
+    }
+
+    /**
+     * 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, false);
+    }
+
+    /**
+     * 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, false);
+    }
+
+    /**
+     * 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, false);
+    }
+
+    /**
+     * 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, false);
+    }
+
+    /**
+     * 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, false);
+    }
+
+    /**
+     * Validate the fail flow of the Initiator: use a session network specifier with a non-null
+     * token, but get an immediate failure
+     */
+    @Test
+    public void testDataPathInitiatorNoConfirmationHalFail() throws Exception {
+        testDataPathInitiatorUtility(false, true, true, true, true);
+    }
+
+    /**
+     * Validate the fail flow of a mis-configured request: Publisher as Initiator
+     */
+    @Test
+    public void testDataPathInitiatorOnPublisherError() throws Exception {
+        testDataPathInitiatorResponderMismatchUtility(true);
+    }
+
+    /*
+     * 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 {
+        testDataPathResponderUtility(false, true, true, false);
+    }
+
+    /**
+     * Validate the fail flow of a mis-configured request: Subscriber as Responder
+     */
+    @Test
+    public void testDataPathResponderOnSubscriberError() throws Exception {
+        testDataPathInitiatorResponderMismatchUtility(false);
+    }
+
+    /*
+     * Utilities
+     */
+
+    private void testDataPathInitiatorResponderMismatchUtility(boolean doPublish) throws Exception {
+        final int clientId = 123;
+        final int pubSubId = 11234;
+        final int ndpId = 2;
+        final byte[] pmk = "some bytes".getBytes();
+        final PeerHandle peerHandle = new PeerHandle(1341234);
+        final byte[] peerDiscoveryMac = HexEncoding.decode("000102030405".toCharArray(), false);
+
+        InOrder inOrder = inOrder(mMockNative, mMockCm, mMockCallback, mMockSessionCallback);
+
+        // (0) initialize
+        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerHandle,
+                peerDiscoveryMac, inOrder, doPublish);
+
+        // (1) request network
+        NetworkRequest nr = getSessionNetworkRequest(clientId, res.first, peerHandle, pmk,
+                doPublish);
+
+        // corrupt the network specifier: reverse the role (so it's mis-matched)
+        WifiAwareNetworkSpecifier ns =
+                (WifiAwareNetworkSpecifier) nr.networkCapabilities.getNetworkSpecifier();
+        ns = new WifiAwareNetworkSpecifier(
+                ns.type,
+                1 - ns.role, // corruption hack
+                ns.clientId,
+                ns.sessionId,
+                ns.peerId,
+                ns.peerMac,
+                ns.pmk,
+                ns.passphrase
+                );
+        nr.networkCapabilities.setNetworkSpecifier(ns);
+
+        Message reqNetworkMsg = Message.obtain();
+        reqNetworkMsg.what = NetworkFactory.CMD_REQUEST_NETWORK;
+        reqNetworkMsg.obj = nr;
+        reqNetworkMsg.arg1 = 0;
+        res.second.send(reqNetworkMsg);
+        mMockLooper.dispatchAll();
+
+        // consequences of failure:
+        //   Responder (publisher): responds with a rejection to any data-path requests
+        //   Initiator (subscribe): doesn't initiate (i.e. no HAL requests)
+        if (doPublish) {
+            // (2) get request & respond
+            mDut.onDataPathRequestNotification(pubSubId, peerDiscoveryMac, ndpId);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).respondToDataPathRequest(anyShort(), eq(false),
+                    eq(ndpId), eq(""), eq(null), eq(null), any());
+        }
+
+        verifyNoMoreInteractions(mMockNative, mMockCm);
+    }
+
+    private void testDataPathInitiatorUtility(boolean useDirect, boolean provideMac,
+            boolean providePmk, boolean getConfirmation, boolean immediateHalFailure)
+            throws Exception {
+        final int clientId = 123;
+        final int pubSubId = 11234;
+        final PeerHandle peerHandle = new PeerHandle(1341234);
+        final int ndpId = 2;
+        final byte[] pmk = "some bytes".getBytes();
+        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);
+
+        if (immediateHalFailure) {
+            when(mMockNative.initiateDataPath(anyShort(), anyInt(), anyInt(), anyInt(), any(),
+                    any(), any(), any(), any())).thenReturn(false);
+
+        }
+
+        // (0) initialize
+        Pair<Integer, Messenger> res = initDataPathEndPoint(clientId, pubSubId, peerHandle,
+                peerDiscoveryMac, inOrder, false);
+
+        // (1) request network
+        NetworkRequest nr;
+        if (useDirect) {
+            nr = getDirectNetworkRequest(clientId,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR,
+                    provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null);
+        } else {
+            nr = getSessionNetworkRequest(clientId, res.first, provideMac ? peerHandle : null,
+                    providePmk ? pmk : null, false);
+        }
+
+        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 : peerHandle.peerId),
+                eq(REQUEST_CHANNEL_SETUP), eq(2437), eq(peerDiscoveryMac),
+                eq(sAwareInterfacePrefix + "0"), eq(pmk), eq(null), any());
+        if (immediateHalFailure) {
+            // short-circuit the rest of this test
+            verifyNoMoreInteractions(mMockNative, mMockCm);
+            return;
+        }
+
+        mDut.onInitiateDataPathResponseSuccess(transactionId.getValue(), ndpId);
+        mMockLooper.dispatchAll();
+
+        // (2) get confirmation OR timeout
+        if (getConfirmation) {
+            mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0,
+                    peerToken.getBytes());
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
+                    any(), anyInt(), any());
+        } else {
+            assertTrue(mAlarmManager.dispatch(
+                    WifiAwareStateManager.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 providePmk, boolean getConfirmation) throws Exception {
+        final int clientId = 123;
+        final int pubSubId = 11234;
+        final PeerHandle peerHandle = new PeerHandle(1341234);
+        final int ndpId = 2;
+        final byte[] pmk = "some bytes".getBytes();
+        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, peerHandle,
+                peerDiscoveryMac, inOrder, true);
+
+        // (1) request network
+        NetworkRequest nr;
+        if (useDirect) {
+            nr = getDirectNetworkRequest(clientId,
+                    WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+                    provideMac ? peerDiscoveryMac : null, providePmk ? pmk : null);
+        } else {
+            nr = getSessionNetworkRequest(clientId, res.first, provideMac ? peerHandle : null,
+                    providePmk ? pmk : null, true);
+        }
+
+        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);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).respondToDataPathRequest(transactionId.capture(), eq(true),
+                eq(ndpId), eq(sAwareInterfacePrefix + "0"), eq(providePmk ? pmk : null), eq(null),
+                any());
+        mDut.onRespondToDataPathSetupRequestResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // (3) get confirmation OR timeout
+        if (getConfirmation) {
+            mDut.onDataPathConfirmNotification(ndpId, peerDataPathMac, true, 0,
+                    peerToken.getBytes());
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
+                    any(), anyInt(), any());
+        } else {
+            assertTrue(mAlarmManager.dispatch(
+                    WifiAwareStateManager.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 void installDataPathStateManagerMocks() throws Exception {
+        Field field = WifiAwareStateManager.class.getDeclaredField("mDataPathMgr");
+        field.setAccessible(true);
+        Object mDataPathMgr = field.get(mDut);
+
+        field = WifiAwareDataPathStateManager.class.getDeclaredField("mNwService");
+        field.setAccessible(true);
+        field.set(mDataPathMgr, mMockNwMgt);
+
+        field = WifiAwareDataPathStateManager.class.getDeclaredField("mNiWrapper");
+        field.setAccessible(true);
+        field.set(mDataPathMgr, mMockNetworkInterface);
+    }
+
+    private NetworkRequest getSessionNetworkRequest(int clientId, int sessionId,
+            PeerHandle peerHandle, byte[] pmk, boolean doPublish)
+            throws Exception {
+        final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mMockAwareService);
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<DiscoverySession> discoverySession = ArgumentCaptor
+                .forClass(DiscoverySession.class);
+
+        AttachCallback mockCallback = mock(AttachCallback.class);
+        DiscoverySessionCallback mockSessionCallback = mock(
+                DiscoverySessionCallback.class);
+
+        mgr.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        verify(mMockAwareService).connect(any(), any(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        verify(mockCallback).onAttached(sessionCaptor.capture());
+        if (doPublish) {
+            sessionCaptor.getValue().publish(publishConfig, mockSessionCallback,
+                    mMockLooperHandler);
+            verify(mMockAwareService).publish(eq(clientId), eq(publishConfig),
+                    sessionProxyCallback.capture());
+        } else {
+            sessionCaptor.getValue().subscribe(subscribeConfig, mockSessionCallback,
+                    mMockLooperHandler);
+            verify(mMockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+                    sessionProxyCallback.capture());
+        }
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        if (doPublish) {
+            verify(mockSessionCallback).onPublishStarted(
+                    (PublishDiscoverySession) discoverySession.capture());
+        } else {
+            verify(mockSessionCallback).onSubscribeStarted(
+                    (SubscribeDiscoverySession) discoverySession.capture());
+        }
+
+        NetworkSpecifier ns;
+        if (pmk == null) {
+            ns = discoverySession.getValue().createNetworkSpecifierOpen(peerHandle);
+        } else {
+            ns = discoverySession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+        }
+
+        NetworkCapabilities nc = new NetworkCapabilities();
+        nc.clearAll();
+        nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE);
+        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,
+            byte[] pmk) throws Exception {
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final WifiAwareManager mgr = new WifiAwareManager(mMockContext, mMockAwareService);
+
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+
+        AttachCallback mockCallback = mock(AttachCallback.class);
+
+        mgr.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        verify(mMockAwareService).connect(any(), any(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        verify(mockCallback).onAttached(sessionCaptor.capture());
+
+        NetworkSpecifier ns;
+        if (pmk == null) {
+            ns = sessionCaptor.getValue().createNetworkSpecifierOpen(role, peer);
+        } else {
+            ns = sessionCaptor.getValue().createNetworkSpecifierPmk(role, peer, pmk);
+        }
+        NetworkCapabilities nc = new NetworkCapabilities();
+        nc.clearAll();
+        nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE);
+        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,
+            PeerHandle peerHandle, byte[] peerDiscoveryMac, InOrder inOrder,
+            boolean doPublish)
+            throws Exception {
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.android.somePackage";
+        final String someMsg = "some arbitrary message from peer";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        Capabilities capabilities = new Capabilities();
+        capabilities.maxNdiInterfaces = 1;
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
+        ArgumentCaptor<String> strCaptor = ArgumentCaptor.forClass(String.class);
+
+        // (0) start/registrations
+        inOrder.verify(mMockCm).registerNetworkFactory(messengerCaptor.capture(),
+                strCaptor.capture());
+        collector.checkThat("factory name", "WIFI_AWARE_FACTORY", equalTo(strCaptor.getValue()));
+
+        // (1) get capabilities
+        mDut.queryCapabilities();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), capabilities);
+        mMockLooper.dispatchAll();
+
+        // (2) enable usage (creates interfaces)
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).createAwareNetworkInterface(transactionId.capture(),
+                strCaptor.capture());
+        collector.checkThat("interface created -- 0", sAwareInterfacePrefix + 0,
+                equalTo(strCaptor.getValue()));
+        mDut.onCreateDataPathInterfaceResponse(transactionId.getValue(), true, 0);
+        mMockLooper.dispatchAll();
+
+        // (3) create client & session & rx message
+        mDut.connect(clientId, uid, pid, callingPackage, mMockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockCallback).onConnectSuccess(clientId);
+        if (doPublish) {
+            mDut.publish(clientId, publishConfig, mMockSessionCallback);
+        } else {
+            mDut.subscribe(clientId, subscribeConfig, mMockSessionCallback);
+        }
+        mMockLooper.dispatchAll();
+        if (doPublish) {
+            inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        } else {
+            inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0),
+                    eq(subscribeConfig));
+        }
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), doPublish, pubSubId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockSessionCallback).onSessionStarted(sessionId.capture());
+        mDut.onMessageReceivedNotification(pubSubId, peerHandle.peerId, peerDiscoveryMac,
+                someMsg.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockSessionCallback).onMessageReceived(peerHandle.peerId,
+                someMsg.getBytes());
+
+        return new Pair<>(sessionId.getValue(), messengerCaptor.getValue());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
new file mode 100644
index 0000000..50a2e0d
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.aware;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.wifi.V1_0.IWifiNanIface;
+import android.hardware.wifi.V1_0.IfaceType;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+
+import com.android.server.wifi.HalDeviceManager;
+
+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 WifiAwareNativeManager.
+ */
+public class WifiAwareNativeManagerTest {
+    private WifiAwareNativeManager mDut;
+    @Mock private WifiAwareStateManager mWifiAwareStateManagerMock;
+    @Mock private HalDeviceManager mHalDeviceManager;
+    @Mock private WifiAwareNativeCallback mWifiAwareNativeCallback;
+    @Mock private IWifiNanIface mWifiNanIfaceMock;
+    private ArgumentCaptor<HalDeviceManager.ManagerStatusListener> mManagerStatusListenerCaptor =
+            ArgumentCaptor.forClass(HalDeviceManager.ManagerStatusListener.class);
+    private ArgumentCaptor<HalDeviceManager.InterfaceDestroyedListener>
+            mDestroyedListenerCaptor = ArgumentCaptor.forClass(
+            HalDeviceManager.InterfaceDestroyedListener.class);
+    private ArgumentCaptor<HalDeviceManager.InterfaceAvailableForRequestListener>
+            mAvailListenerCaptor = ArgumentCaptor.forClass(
+            HalDeviceManager.InterfaceAvailableForRequestListener.class);
+    private InOrder mInOrder;
+    @Rule public ErrorCollector collector = new ErrorCollector();
+
+    private WifiStatus mStatusOk;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mStatusOk = new WifiStatus();
+        mStatusOk.code = WifiStatusCode.SUCCESS;
+
+        when(mWifiNanIfaceMock.registerEventCallback(any())).thenReturn(mStatusOk);
+
+        mDut = new WifiAwareNativeManager(mWifiAwareStateManagerMock,
+                mHalDeviceManager, mWifiAwareNativeCallback);
+
+        mInOrder = inOrder(mWifiAwareStateManagerMock, mHalDeviceManager);
+    }
+
+    /**
+     * Test the control flow of the manager:
+     * 1. onStatusChange (ready/started)
+     * 2. null NAN iface
+     * 3. onAvailableForRequest
+     * 4. non-null NAN iface -> enableUsage
+     * 5. onStatusChange (!started) -> disableUsage
+     * 6. onStatusChange (ready/started)
+     * 7. non-null NAN iface -> enableUsage
+     * 8. onDestroyed -> disableUsage
+     * 9. onStatusChange (!started)
+     */
+    @Test
+    public void testControlFlow() {
+        // configure HalDeviceManager as ready/wifi started
+        when(mHalDeviceManager.isReady()).thenReturn(true);
+        when(mHalDeviceManager.isStarted()).thenReturn(true);
+
+        // validate (and capture) that register manage status callback
+        mInOrder.verify(mHalDeviceManager).registerStatusListener(
+                mManagerStatusListenerCaptor.capture(), any());
+
+        // 1 & 2 onStatusChange (ready/started): validate that trying to get a NAN interface
+        // (make sure gets a NULL)
+        when(mHalDeviceManager.createNanIface(any(), any())).thenReturn(null);
+
+        mManagerStatusListenerCaptor.getValue().onStatusChanged();
+        mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
+                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any());
+        mAvailListenerCaptor.getValue().onAvailableForRequest();
+
+        mInOrder.verify(mHalDeviceManager).createNanIface(
+                mDestroyedListenerCaptor.capture(), any());
+        collector.checkThat("null interface", mDut.getWifiNanIface(), nullValue());
+
+        // 3 & 4 onAvailableForRequest + non-null return value: validate that enables usage
+        when(mHalDeviceManager.createNanIface(any(), any())).thenReturn(mWifiNanIfaceMock);
+
+        mAvailListenerCaptor.getValue().onAvailableForRequest();
+
+        mInOrder.verify(mHalDeviceManager).createNanIface(
+                mDestroyedListenerCaptor.capture(), any());
+        mInOrder.verify(mWifiAwareStateManagerMock).enableUsage();
+        collector.checkThat("non-null interface", mDut.getWifiNanIface(),
+                equalTo(mWifiNanIfaceMock));
+
+        // 5 onStatusChange (!started): disable usage
+        when(mHalDeviceManager.isStarted()).thenReturn(false);
+        mManagerStatusListenerCaptor.getValue().onStatusChanged();
+
+        mInOrder.verify(mWifiAwareStateManagerMock).disableUsage();
+        collector.checkThat("null interface", mDut.getWifiNanIface(), nullValue());
+
+        // 6 & 7 onStatusChange (ready/started) + non-null NAN interface: enable usage
+        when(mHalDeviceManager.isStarted()).thenReturn(true);
+        mManagerStatusListenerCaptor.getValue().onStatusChanged();
+
+        mManagerStatusListenerCaptor.getValue().onStatusChanged();
+        mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
+                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any());
+        mAvailListenerCaptor.getValue().onAvailableForRequest();
+
+        mInOrder.verify(mHalDeviceManager).createNanIface(
+                mDestroyedListenerCaptor.capture(), any());
+        mInOrder.verify(mWifiAwareStateManagerMock).enableUsage();
+        collector.checkThat("non-null interface", mDut.getWifiNanIface(),
+                equalTo(mWifiNanIfaceMock));
+
+        // 8 onDestroyed: disable usage
+        mDestroyedListenerCaptor.getValue().onDestroyed();
+
+        mInOrder.verify(mWifiAwareStateManagerMock).disableUsage();
+        collector.checkThat("null interface", mDut.getWifiNanIface(), nullValue());
+
+        // 9 onStatusChange (!started): nothing more happens
+        when(mHalDeviceManager.isStarted()).thenReturn(false);
+        mManagerStatusListenerCaptor.getValue().onStatusChanged();
+
+        verifyNoMoreInteractions(mWifiAwareStateManagerMock);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java
new file mode 100644
index 0000000..616e68c
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.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.aware;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.mockito.ArgumentMatchers.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.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.util.test.BidirectionalAsyncChannelServer;
+
+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 WifiAwareManager class.
+ */
+@SmallTest
+public class WifiAwareRttStateManagerTest {
+    private WifiAwareRttStateManager 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 WifiAwareRttStateManager();
+        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;
+        WifiAwareClientState mockClient = mock(WifiAwareClientState.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/aware/WifiAwareServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
new file mode 100644
index 0000000..b5e12d2
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
@@ -0,0 +1,662 @@
+/*
+ * 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.aware;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+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.aware.Characteristics;
+import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
+import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
+import android.os.HandlerThread;
+import android.os.IBinder;
+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.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+
+/**
+ * Unit test harness for WifiAwareStateManager.
+ */
+@SmallTest
+public class WifiAwareServiceImplTest {
+    private static final int MAX_LENGTH = 255;
+
+    private WifiAwareServiceImplSpy mDut;
+    private int mDefaultUid = 1500;
+
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private HandlerThread mHandlerThreadMock;
+    @Mock
+    private PackageManager mPackageManagerMock;
+    @Mock
+    private WifiAwareStateManager mAwareStateManagerMock;
+    @Mock
+    private IBinder mBinderMock;
+    @Mock
+    private IWifiAwareEventCallback mCallbackMock;
+    @Mock
+    private IWifiAwareDiscoverySessionCallback mSessionCallbackMock;
+
+    private HandlerThread mHandlerThread;
+
+    /**
+     * Using instead of spy to avoid native crash failures - possibly due to
+     * spy's copying of state.
+     */
+    private class WifiAwareServiceImplSpy extends WifiAwareServiceImpl {
+        public int fakeUid;
+
+        WifiAwareServiceImplSpy(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_AWARE))
+                .thenReturn(true);
+        when(mAwareStateManagerMock.getCharacteristics()).thenReturn(getCharacteristics());
+
+        mDut = new WifiAwareServiceImplSpy(mContextMock);
+        mDut.fakeUid = mDefaultUid;
+        mDut.start(mHandlerThreadMock, mAwareStateManagerMock);
+        verify(mAwareStateManagerMock).start(eq(mContextMock), any());
+    }
+
+    /**
+     * Validate isUsageEnabled() function
+     */
+    @Test
+    public void testIsUsageEnabled() {
+        mDut.isUsageEnabled();
+
+        verify(mAwareStateManagerMock).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();
+        String callingPackage = "com.google.somePackage";
+
+        mDut.connect(mBinderMock, callingPackage, mCallbackMock,
+                configRequest, false);
+
+        verify(mAwareStateManagerMock).connect(anyInt(), anyInt(), anyInt(),
+                eq(callingPackage), eq(mCallbackMock), eq(configRequest), eq(false));
+    }
+
+    /**
+     * Validate disconnect() - correct pass-through args.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testDisconnect() throws Exception {
+        int clientId = doConnect();
+
+        mDut.disconnect(clientId, mBinderMock);
+
+        verify(mAwareStateManagerMock).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(mAwareStateManagerMock).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(mAwareStateManagerMock).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(mAwareStateManagerMock).disconnect(clientId);
+        validateInternalStateCleanedUp(clientId);
+    }
+
+    /**
+     * Validates that sequential connect() calls return increasing client IDs.
+     */
+    @Test
+    public void testClientIdIncrementing() {
+        int loopCount = 100;
+
+        InOrder inOrder = inOrder(mAwareStateManagerMock);
+        ArgumentCaptor<Integer> clientIdCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        int prevId = 0;
+        for (int i = 0; i < loopCount; ++i) {
+            mDut.connect(mBinderMock, "", mCallbackMock, null, false);
+            inOrder.verify(mAwareStateManagerMock).connect(clientIdCaptor.capture(), anyInt(),
+                    anyInt(), any(), eq(mCallbackMock), any(), eq(false));
+            int id = clientIdCaptor.getValue();
+            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(mAwareStateManagerMock).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();
+        IWifiAwareDiscoverySessionCallback mockCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+
+        mDut.publish(clientId, publishConfig, mockCallback);
+
+        verify(mAwareStateManagerMock).publish(clientId, publishConfig, mockCallback);
+    }
+
+    /**
+     * Validate that publish() verifies the input PublishConfig and fails on an invalid service
+     * name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishInvalidServiceName() {
+        doBadPublishConfiguration("Including invalid characters - spaces", null, null);
+    }
+
+    /**
+     * Validate that publish() verifies the input PublishConfig and fails on a "very long"
+     * service name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishServiceNameTooLong() {
+        byte[] byteArray = new byte[MAX_LENGTH + 1];
+        for (int i = 0; i < MAX_LENGTH + 1; ++i) {
+            byteArray[i] = 'a';
+        }
+        doBadPublishConfiguration(new String(byteArray), null, null);
+    }
+
+    /**
+     * Validate that publish() verifies the input PublishConfig and fails on a "very long" ssi.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishSsiTooLong() {
+        doBadPublishConfiguration("correctservicename", new byte[MAX_LENGTH + 1], null);
+    }
+
+    /**
+     * Validate that publish() verifies the input PublishConfig and fails on a "very long" match
+     * filter.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishMatchFilterTooLong() {
+        doBadPublishConfiguration("correctservicename", null, new byte[MAX_LENGTH + 1]);
+    }
+
+    /**
+     * Validate that publish() verifies the input PublishConfig and fails on a bad match filter -
+     * invalid LV.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishMatchFilterBadLv() {
+        byte[] badLv = { 0, 1, 127, 2, 126, 125, 3 };
+        doBadPublishConfiguration("correctservicename", null, badLv);
+    }
+
+    /**
+     * 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(mAwareStateManagerMock).updatePublish(clientId, sessionId, publishConfig);
+    }
+
+    /**
+     * Validate updatePublish() error checking.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdatePublishInvalid() {
+        int sessionId = 1232;
+        PublishConfig publishConfig = new PublishConfig.Builder()
+                .setServiceName("something with spaces").build();
+        int clientId = doConnect();
+
+        mDut.updatePublish(clientId, sessionId, publishConfig);
+
+        verify(mAwareStateManagerMock).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();
+        IWifiAwareDiscoverySessionCallback mockCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+
+        mDut.subscribe(clientId, subscribeConfig, mockCallback);
+
+        verify(mAwareStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
+    }
+
+    /**
+     * Validate that subscribe() verifies the input SubscribeConfig and fails on an invalid service
+     * name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeInvalidServiceName() {
+        doBadSubscribeConfiguration("Including invalid characters - spaces", null, null);
+    }
+
+    /**
+     * Validate that subscribe() verifies the input SubscribeConfig and fails on a "very long"
+     * service name.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeServiceNameTooLong() {
+        byte[] byteArray = new byte[MAX_LENGTH + 1];
+        for (int i = 0; i < MAX_LENGTH + 1; ++i) {
+            byteArray[i] = 'a';
+        }
+        doBadSubscribeConfiguration(new String(byteArray), null, null);
+    }
+
+    /**
+     * Validate that subscribe() verifies the input SubscribeConfig and fails on a "very long" ssi.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeSsiTooLong() {
+        doBadSubscribeConfiguration("correctservicename", new byte[MAX_LENGTH + 1], null);
+    }
+
+    /**
+     * Validate that subscribe() verifies the input SubscribeConfig and fails on a "very long" match
+     * filter.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeMatchFilterTooLong() {
+        doBadSubscribeConfiguration("correctservicename", null, new byte[MAX_LENGTH + 1]);
+    }
+
+    /**
+     * Validate that subscribe() verifies the input SubscribeConfig and fails on a bad match filter
+     * - invalid LV.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeMatchFilterBadLv() {
+        byte[] badLv = { 0, 1, 127, 2, 126, 125, 3 };
+        doBadSubscribeConfiguration("correctservicename", null, badLv);
+    }
+
+    /**
+     * Validate updateSubscribe() error checking.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateSubscribeInvalid() {
+        int sessionId = 1232;
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder()
+                .setServiceName("something.valid")
+                .setServiceSpecificInfo(new byte[MAX_LENGTH + 1]).build();
+        int clientId = doConnect();
+
+        mDut.updateSubscribe(clientId, sessionId, subscribeConfig);
+
+        verify(mAwareStateManagerMock).updateSubscribe(clientId, sessionId, subscribeConfig);
+    }
+
+    /**
+     * Validate updateSubscribe() validates configuration.
+     */
+    @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(mAwareStateManagerMock).updateSubscribe(clientId, sessionId, subscribeConfig);
+    }
+
+    /**
+     * Validate sendMessage() - correct pass-through args.
+     */
+    @Test
+    public void testSendMessage() {
+        int sessionId = 2394;
+        int peerId = 2032;
+        byte[] message = new byte[MAX_LENGTH];
+        int messageId = 2043;
+        int clientId = doConnect();
+
+        mDut.sendMessage(clientId, sessionId, peerId, message, messageId, 0);
+
+        verify(mAwareStateManagerMock).sendMessage(clientId, sessionId, peerId, message, messageId,
+                0);
+    }
+
+    /**
+     * Validate sendMessage() validates that message length is correct.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSendMessageTooLong() {
+        int sessionId = 2394;
+        int peerId = 2032;
+        byte[] message = new byte[MAX_LENGTH + 1];
+        int messageId = 2043;
+        int clientId = doConnect();
+
+        mDut.sendMessage(clientId, sessionId, peerId, message, messageId, 0);
+
+        verify(mAwareStateManagerMock).sendMessage(clientId, sessionId, peerId, message, 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(mAwareStateManagerMock).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 WifiAwareServiceImpl: 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 void doBadPublishConfiguration(String serviceName, byte[] ssi, byte[] matchFilter)
+            throws IllegalArgumentException {
+        // using the hidden constructor since may be passing invalid parameters which would be
+        // caught by the Builder. Want to test whether service side will catch invalidly
+        // constructed configs.
+        PublishConfig publishConfig = new PublishConfig(serviceName.getBytes(), ssi, matchFilter,
+                PublishConfig.PUBLISH_TYPE_UNSOLICITED, 0, true);
+        int clientId = doConnect();
+        IWifiAwareDiscoverySessionCallback mockCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+
+        mDut.publish(clientId, publishConfig, mockCallback);
+
+        verify(mAwareStateManagerMock).publish(clientId, publishConfig, mockCallback);
+    }
+
+    private void doBadSubscribeConfiguration(String serviceName, byte[] ssi, byte[] matchFilter)
+            throws IllegalArgumentException {
+        // using the hidden constructor since may be passing invalid parameters which would be
+        // caught by the Builder. Want to test whether service side will catch invalidly
+        // constructed configs.
+        SubscribeConfig subscribeConfig = new SubscribeConfig(serviceName.getBytes(), ssi,
+                matchFilter, SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE, 0, true);
+        int clientId = doConnect();
+        IWifiAwareDiscoverySessionCallback mockCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+
+        mDut.subscribe(clientId, subscribeConfig, mockCallback);
+
+        verify(mAwareStateManagerMock).subscribe(clientId, subscribeConfig, mockCallback);
+    }
+
+    private int doConnect() {
+        String callingPackage = "com.google.somePackage";
+
+        mDut.connect(mBinderMock, callingPackage, mCallbackMock, null, false);
+
+        ArgumentCaptor<Integer> clientId = ArgumentCaptor.forClass(Integer.class);
+        verify(mAwareStateManagerMock).connect(clientId.capture(), anyInt(), anyInt(),
+                eq(callingPackage), eq(mCallbackMock), eq(new ConfigRequest.Builder().build()),
+                eq(false));
+
+        return clientId.getValue();
+    }
+
+    private static Characteristics getCharacteristics() {
+        Capabilities cap = new Capabilities();
+        cap.maxConcurrentAwareClusters = 1;
+        cap.maxPublishes = 2;
+        cap.maxSubscribes = 2;
+        cap.maxServiceNameLen = MAX_LENGTH;
+        cap.maxMatchFilterLen = MAX_LENGTH;
+        cap.maxTotalMatchFilterLen = 255;
+        cap.maxServiceSpecificInfoLen = MAX_LENGTH;
+        cap.maxExtendedServiceSpecificInfoLen = MAX_LENGTH;
+        cap.maxNdiInterfaces = 1;
+        cap.maxNdpSessions = 1;
+        cap.maxAppInfoLen = 255;
+        cap.maxQueuedTransmitMessages = 6;
+        return cap.toPublicCharacteristics();
+    }
+
+    private int getInternalStateUid(int clientId) throws Exception {
+        Field field = WifiAwareServiceImpl.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 = WifiAwareServiceImpl.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/aware/WifiAwareStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
new file mode 100644
index 0000000..2797c0e
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
@@ -0,0 +1,2843 @@
+/*
+ * 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.aware;
+
+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.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyShort;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+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.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.app.test.MockAnswerUtil;
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.wifi.V1_0.NanStatusType;
+import android.net.ConnectivityManager;
+import android.net.wifi.RttManager;
+import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
+import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
+import android.net.wifi.aware.WifiAwareManager;
+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 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.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Unit test harness for WifiAwareStateManager.
+ */
+@SmallTest
+public class WifiAwareStateManagerTest {
+    private TestLooper mMockLooper;
+    private Random mRandomNg = new Random(15687);
+    private WifiAwareStateManager mDut;
+    @Mock private WifiAwareNativeApi mMockNative;
+    @Mock private Context mMockContext;
+    @Mock private AppOpsManager mMockAppOpsManager;
+    @Mock private WifiAwareRttStateManager mMockAwareRttStateManager;
+    TestAlarmManager mAlarmManager;
+    @Mock private WifiAwareDataPathStateManager mMockAwareDataPathStatemanager;
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    private static final byte[] ALL_ZERO_MAC = new byte[] {0, 0, 0, 0, 0, 0};
+
+    /**
+     * Pre-test configuration. Initialize and install 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(
+                mock(ConnectivityManager.class));
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
+        when(mMockContext.checkPermission(eq(android.Manifest.permission.ACCESS_FINE_LOCATION),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mMockContext.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mMockAppOpsManager.noteOp(eq(AppOpsManager.OP_FINE_LOCATION), anyInt(),
+                any())).thenReturn(AppOpsManager.MODE_ERRORED);
+        when(mMockAppOpsManager.noteOp(eq(AppOpsManager.OP_COARSE_LOCATION), anyInt(),
+                any())).thenReturn(AppOpsManager.MODE_ERRORED);
+
+        mMockLooper = new TestLooper();
+
+        mDut = new WifiAwareStateManager();
+        mDut.setNative(mMockNative);
+        mDut.start(mMockContext, mMockLooper.getLooper());
+        installMocksInStateManager(mDut, mMockAwareRttStateManager, mMockAwareDataPathStatemanager);
+
+        when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
+                anyBoolean())).thenReturn(true);
+        when(mMockNative.disable(anyShort())).thenReturn(true);
+        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(true);
+        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+                .thenReturn(true);
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+                any(), anyInt())).thenReturn(true);
+        when(mMockNative.stopPublish(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.stopSubscribe(anyShort(), anyInt())).thenReturn(true);
+        when(mMockNative.getCapabilities(anyShort())).thenReturn(true);
+    }
+
+    /**
+     * Validate that Aware data-path interfaces are brought up and down correctly.
+     */
+    @Test
+    public void testAwareDataPathInterfaceUpDown() throws Exception {
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mMockAwareDataPathStatemanager);
+
+        // (1) enable usage
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder, true);
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockAwareDataPathStatemanager).createAllInterfaces();
+        collector.checkThat("usage enabled", mDut.isUsageEnabled(), equalTo(true));
+
+        // (2) disable usage
+        mDut.disableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockAwareDataPathStatemanager).onAwareDownCleanupDataPaths();
+        inOrder.verify(mMockNative).disable((short) 0);
+        validateCorrectAwareStatusChangeBroadcast(inOrder, false);
+        inOrder.verify(mMockAwareDataPathStatemanager).deleteAllInterfaces();
+        collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
+
+        verifyNoMoreInteractions(mMockNative, mMockAwareDataPathStatemanager);
+    }
+
+    /**
+     * 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 int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+
+        // (1) check initial state
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(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);
+        validateCorrectAwareStatusChangeBroadcast(inOrder, false);
+
+        // (3) try connecting and validate that get nothing (app should be aware of non-availability
+        // through state change broadcast and/or query API)
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        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 int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
+
+        // (1) check initial state
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (3) disable usage & verify callbacks
+        mDut.disableUsage();
+        mMockLooper.dispatchAll();
+        collector.checkThat("usage disabled", mDut.isUsageEnabled(), equalTo(false));
+        inOrder.verify(mMockNative).disable((short) 0);
+        validateCorrectAwareStatusChangeBroadcast(inOrder, false);
+        validateInternalClientInfoCleanedUp(clientId);
+
+        // (4) try connecting again and validate that just get an onAwareDown
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        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));
+        validateCorrectAwareStatusChangeBroadcast(inOrder, true);
+
+        // (7) connect (should be successful)
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        verifyNoMoreInteractions(mMockNative, mockCallback);
+    }
+
+    /**
+     * Validates that a HAL failure on enable and configure results in failed callback.
+     */
+    @Test
+    public void testHalFailureEnableAndConfigure() throws Exception {
+        final int clientId = 12341;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mMockContext, mMockNative, mockCallback);
+
+        when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
+                anyBoolean())).thenReturn(false);
+
+        // (1) check initial state
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        validateCorrectAwareStatusChangeBroadcast(inOrder, true);
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (2) connect with HAL failure
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        inOrder.verify(mockCallback).onConnectFail(NanStatusType.INTERNAL_FAILURE);
+
+        validateInternalClientInfoCleanedUp(clientId);
+        verifyNoMoreInteractions(mMockNative, mockCallback);
+    }
+
+    /**
+     * Validates that all events are delivered with correct arguments. Validates
+     * that IdentityChanged not delivered if configuration disables delivery.
+     */
+    @Test
+    public void testAwareEventsDelivery() 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 pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int reason = NanStatusType.INTERNAL_FAILURE;
+        final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] someMac2 = HexEncoding.decode("060708090A0B".toCharArray(), false);
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setMasterPreference(masterPref)
+                .build();
+
+        IWifiAwareEventCallback mockCallback1 = mock(IWifiAwareEventCallback.class);
+        IWifiAwareEventCallback mockCallback2 = mock(IWifiAwareEventCallback.class);
+        ArgumentCaptor<Short> transactionIdCapture = ArgumentCaptor.forClass(Short.class);
+        InOrder inOrder = inOrder(mockCallback1, mockCallback2, mMockNative);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionIdCapture.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionIdCapture.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect 1st and 2nd clients
+        mDut.connect(clientId1, uid, pid, callingPackage, mockCallback1, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
+                eq(configRequest), eq(false), eq(true));
+        short transactionId = transactionIdCapture.getValue();
+        mDut.onConfigSuccessResponse(transactionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback1).onConnectSuccess(clientId1);
+
+        mDut.connect(clientId2, uid, pid, callingPackage, mockCallback2, configRequest, true);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionIdCapture.capture(),
+                eq(configRequest), eq(true), eq(false));
+        transactionId = transactionIdCapture.getValue();
+        mDut.onConfigSuccessResponse(transactionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback2).onConnectSuccess(clientId2);
+
+        // (2) deliver Aware events - without LOCATIONING permission
+        mDut.onClusterChangeNotification(WifiAwareClientState.CLUSTER_CHANGE_EVENT_STARTED,
+                someMac);
+        mDut.onInterfaceAddressChangeNotification(someMac);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockCallback2).onIdentityChanged(ALL_ZERO_MAC);
+
+        // (3) deliver new identity - still without LOCATIONING permission (should get an event)
+        mDut.onInterfaceAddressChangeNotification(someMac2);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockCallback2).onIdentityChanged(ALL_ZERO_MAC);
+
+        // (4) deliver same identity - still without LOCATIONING permission (should
+        // not get an event)
+        mDut.onInterfaceAddressChangeNotification(someMac2);
+        mMockLooper.dispatchAll();
+
+        // (5) deliver new identity - with LOCATIONING permission
+        when(mMockContext.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mMockAppOpsManager.noteOp(eq(AppOpsManager.OP_COARSE_LOCATION), anyInt(),
+                any())).thenReturn(AppOpsManager.MODE_ALLOWED);
+        mDut.onInterfaceAddressChangeNotification(someMac);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockCallback2).onIdentityChanged(someMac);
+
+        // (6) Aware down (no feedback)
+        mDut.onAwareDownNotification(reason);
+        mMockLooper.dispatchAll();
+
+        validateInternalClientInfoCleanedUp(clientId1);
+        validateInternalClientInfoCleanedUp(clientId2);
+
+        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 testHalNoResponseTimeout() throws Exception {
+        final int clientId = 12341;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.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 (successfully)
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (2) publish + timeout
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(anyShort(), eq(0), eq(publishConfig));
+        assertTrue(mAlarmManager.dispatch(WifiAwareStateManager.HAL_COMMAND_TIMEOUT_TAG));
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(NanStatusType.INTERNAL_FAILURE);
+        validateInternalNoSessions(clientId);
+
+        // (3) publish + success
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), true, 9999);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(anyInt());
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Validates publish flow: (1) initial publish (2) fail informed by notification, (3) fail due
+     * to immediate HAL failure. Expected: get a failure callback.
+     */
+    @Test
+    public void testPublishFail() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int reasonFail = NanStatusType.INTERNAL_FAILURE;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (1) initial publish
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+
+        // (2) publish failure callback (i.e. firmware tried and failed)
+        mDut.onSessionConfigFailResponse(transactionId.getValue(), true, reasonFail);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        validateInternalNoSessions(clientId);
+
+        // (3) publish and get immediate failure (i.e. HAL failed)
+        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(false);
+
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+        validateInternalNoSessions(clientId);
+
+        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 testPublishSuccessTerminated() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int reasonTerminate = NanStatusType.SUCCESS;
+        final int publishId = 15;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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();
+
+        // (5) app terminates session
+        mDut.terminateSession(clientId, sessionId.getValue());
+        mMockLooper.dispatchAll();
+
+        // (6) app updates session (app already knows that terminated - will get
+        // a local FAIL).
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
+        mMockLooper.dispatchAll();
+
+        validateInternalSessionInfoCleanedUp(clientId, sessionId.getValue());
+
+        verifyNoMoreInteractions(mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate the publish flow: (1) initial publish + (2) success + (3) update + (4) update
+     * fails (callback from firmware) + (5) update + (6). Expected: session is still alive after
+     * update failure so second update succeeds (no callbacks) + (7) update + immediate failure from
+     * HAL.
+     */
+    @Test
+    public void testPublishUpdateFail() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int publishId = 15;
+        final int reasonFail = NanStatusType.INTERNAL_FAILURE;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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) update publish
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(publishId),
+                eq(publishConfig));
+
+        // (4) update fails
+        mDut.onSessionConfigFailResponse(transactionId.getValue(), true, reasonFail);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+
+        // (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();
+
+        // (7) another update + immediate failure
+        when(mMockNative.publish(anyShort(), anyInt(), any())).thenReturn(false);
+
+        mDut.updatePublish(clientId, sessionId.getValue(), publishConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(publishId),
+                eq(publishConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+
+        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 testDisconnectWhilePublishPending() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int publishId = 15;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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 (callback from firmware), (3) fail
+     * due to immeidate HAL failure. Expected: get a failure callback.
+     */
+    @Test
+    public void testSubscribeFail() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int reasonFail = NanStatusType.INTERNAL_FAILURE;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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);
+
+        // (3) subscribe and get immediate failure (i.e. HAL failed)
+        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+                .thenReturn(false);
+
+        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeConfig));
+
+        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 pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int reasonTerminate = NanStatusType.SUCCESS;
+        final int subscribeId = 15;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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();
+
+        // (5) app terminates session
+        mDut.terminateSession(clientId, sessionId.getValue());
+        mMockLooper.dispatchAll();
+
+        // (6) app updates session
+        mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
+        mMockLooper.dispatchAll();
+
+        validateInternalSessionInfoCleanedUp(clientId, sessionId.getValue());
+
+        verifyNoMoreInteractions(mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate the subscribe flow: (1) initial subscribe + (2) success + (3) update + (4) update
+     * fails (callback from firmware) + (5) update + (6). Expected: session is still alive after
+     * update failure so second update succeeds (no callbacks). + (7) update + immediate failure
+     * from HAL.
+     */
+    @Test
+    public void testSubscribeUpdateFail() throws Exception {
+        final int clientId = 2005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int subscribeId = 15;
+        final int reasonFail = NanStatusType.INTERNAL_FAILURE;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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();
+
+        // (7) another update + immediate failure
+        when(mMockNative.subscribe(anyShort(), anyInt(), any()))
+                .thenReturn(false);
+
+        mDut.updateSubscribe(clientId, sessionId.getValue(), subscribeConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(subscribeId),
+                eq(subscribeConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(reasonFail);
+
+        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 pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int subscribeId = 15;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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 clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String serviceName = "some-service-name";
+        final String ssi = "some much longer and more arbitrary data";
+        final int reasonFail = NanStatusType.INTERNAL_FAILURE;
+        final int subscribeId = 15;
+        final 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 String peerMsg = "some message from peer";
+        final int messageId = 6948;
+        final int messageId2 = 6949;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi.getBytes())
+                .setSubscribeType(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE)
+                .build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(),
+                peerMatchFilter.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerMatchFilter.getBytes());
+
+        // (3) message Rx
+        mDut.onMessageReceivedNotification(subscribeId, requestorId, peerMac, peerMsg.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(requestorId, peerMsg.getBytes());
+
+        // (4) message Tx successful queuing
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId));
+        short tid1 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(tid1);
+        mMockLooper.dispatchAll();
+
+        // (5) message Tx successful queuing
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId2,
+                0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId2));
+        short tid2 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(tid2);
+        mMockLooper.dispatchAll();
+
+        // (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(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Summary: in a single publish session interact with multiple peers
+     * (different MAC addresses).
+     */
+    @Test
+    public void testMultipleMessageSources() throws Exception {
+        final int clientId = 300;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int clusterLow = 7;
+        final int clusterHigh = 7;
+        final int masterPref = 0;
+        final String serviceName = "some-service-name";
+        final int publishId = 88;
+        final int peerId1 = 568;
+        final int peerId2 = 873;
+        final byte[] peerMac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerMac2 = HexEncoding.decode("060708090A0B".toCharArray(), false);
+        final String msgFromPeer1 = "hey from 000102...";
+        final String msgFromPeer2 = "hey from 0607...";
+        final String msgToPeer1 = "hey there 000102...";
+        final String msgToPeer2 = "hey there 0506...";
+        final int msgToPeerId1 = 546;
+        final int msgToPeerId2 = 9654;
+        final int reason = NanStatusType.INTERNAL_FAILURE;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
+
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setPublishType(PublishConfig.PUBLISH_TYPE_UNSOLICITED).build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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());
+        mDut.onMessageReceivedNotification(publishId, peerId2, peerMac2, msgFromPeer2.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId1, msgFromPeer1.getBytes());
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId2, msgFromPeer2.getBytes());
+
+        // (4) sending messages back to same peers: one Tx fails, other succeeds
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId2, msgToPeer2.getBytes(),
+                msgToPeerId2, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId2),
+                eq(peerMac2), eq(msgToPeer2.getBytes()), eq(msgToPeerId2));
+        short transactionIdVal = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionIdVal);
+        mDut.onMessageSendSuccessNotification(transactionIdVal);
+
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId1, msgToPeer1.getBytes(),
+                msgToPeerId1, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(msgToPeerId2);
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId1),
+                eq(peerMac1), eq(msgToPeer1.getBytes()), eq(msgToPeerId1));
+        transactionIdVal = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionIdVal);
+        mDut.onMessageSendFailNotification(transactionIdVal, reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(msgToPeerId1, reason);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId1);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId2);
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Summary: interact with a peer which changed its identity (MAC address)
+     * but which keeps its requestor instance ID. Should be transparent.
+     */
+    @Test
+    public void testMessageWhilePeerChangesIdentity() throws Exception {
+        final int clientId = 300;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int clusterLow = 7;
+        final int clusterHigh = 7;
+        final int masterPref = 0;
+        final String serviceName = "some-service-name";
+        final int publishId = 88;
+        final int peerId = 568;
+        final byte[] peerMacOrig = HexEncoding.decode("000102030405".toCharArray(), false);
+        final byte[] peerMacLater = HexEncoding.decode("060708090A0B".toCharArray(), false);
+        final String msgFromPeer1 = "hey from 000102...";
+        final String msgFromPeer2 = "hey from 0607...";
+        final String msgToPeer1 = "hey there 000102...";
+        final String msgToPeer2 = "hey there 0506...";
+        final int msgToPeerId1 = 546;
+        final int msgToPeerId2 = 9654;
+        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
+
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setPublishType(PublishConfig.PUBLISH_TYPE_UNSOLICITED).build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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 & responded to
+        mDut.onMessageReceivedNotification(publishId, peerId, peerMacOrig, msgFromPeer1.getBytes());
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId, msgToPeer1.getBytes(),
+                msgToPeerId1, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId, msgFromPeer1.getBytes());
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId),
+                eq(peerMacOrig), eq(msgToPeer1.getBytes()), eq(msgToPeerId1));
+        mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+        mDut.onMessageSendSuccessNotification(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(msgToPeerId1);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId1);
+
+        // (4) message received with same peer ID but different MAC
+        mDut.onMessageReceivedNotification(publishId, peerId, peerMacLater,
+                msgFromPeer2.getBytes());
+        mDut.sendMessage(clientId, sessionId.getValue(), peerId, msgToPeer2.getBytes(),
+                msgToPeerId2, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerId, msgFromPeer2.getBytes());
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(publishId), eq(peerId),
+                eq(peerMacLater), eq(msgToPeer2.getBytes()), eq(msgToPeerId2));
+        mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+        mDut.onMessageSendSuccessNotification(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(msgToPeerId2);
+        validateInternalSendMessageQueuesCleanedUp(msgToPeerId2);
+
+        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 int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        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();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(),
+                peerMatchFilter.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerMatchFilter.getBytes());
+
+        // (3) send message to invalid peer ID
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId + 5, ssi.getBytes(),
+                messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId,
+                NanStatusType.INTERNAL_FAILURE);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate that on send message errors are handled correctly: immediate send error, queue fail
+     * error (not queue full), and timeout. Behavior: correct callback is dispatched and a later
+     * firmware notification is ignored. Intersperse with one successfull transmission.
+     */
+    @Test
+    public void testSendMessageErrorsImmediateQueueTimeout() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String ssi = "some much longer and more arbitrary data";
+        final int subscribeId = 15;
+        final 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();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(),
+                peerMatchFilter.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerMatchFilter.getBytes());
+
+        // (3) send 2 messages and enqueue successfully
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId));
+        short transactionId1 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionId1);
+        mMockLooper.dispatchAll();
+
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                messageId + 1, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId + 1));
+        short transactionId2 = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(transactionId2);
+        mMockLooper.dispatchAll();
+
+        // (4) send a message and get a queueing failure (not queue full)
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId + 2,
+                0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId + 2));
+        short transactionId3 = transactionId.getValue();
+        mDut.onMessageSendQueuedFailResponse(transactionId3, NanStatusType.INTERNAL_FAILURE);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId + 2,
+                NanStatusType.INTERNAL_FAILURE);
+        validateInternalSendMessageQueuesCleanedUp(messageId + 2);
+
+        // (5) send a message and get an immediate failure (configure first)
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+                any(), anyInt())).thenReturn(false);
+
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId + 3,
+                0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId + 3));
+        short transactionId4 = transactionId.getValue();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId + 3,
+                NanStatusType.INTERNAL_FAILURE);
+        validateInternalSendMessageQueuesCleanedUp(messageId + 3);
+
+        // (6) message send timeout
+        assertTrue(mAlarmManager.dispatch(WifiAwareStateManager.HAL_SEND_MESSAGE_TIMEOUT_TAG));
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId,
+                NanStatusType.INTERNAL_FAILURE);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        // (7) firmware response (unlikely - but good to check)
+        mDut.onMessageSendSuccessNotification(transactionId1);
+        mDut.onMessageSendSuccessNotification(transactionId2);
+
+        // bogus: these didn't even go to firmware or weren't queued
+        mDut.onMessageSendSuccessNotification(transactionId3);
+        mDut.onMessageSendFailNotification(transactionId4, NanStatusType.INTERNAL_FAILURE);
+        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 int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        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();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(),
+                peerMatchFilter.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerMatchFilter.getBytes());
+
+        // (3) send message and enqueue successfully
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(),
+                messageId, retryCount);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), 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(), NanStatusType.NO_OTA_ACK);
+            mMockLooper.dispatchAll();
+            inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                    eq(requestorId), eq(peerMac), eq(ssi.getBytes()), 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 int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        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();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(),
+                peerMatchFilter.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerMatchFilter.getBytes());
+
+        // (3) send message and enqueue successfully
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, ssi.getBytes(), messageId,
+                retryCount);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), 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(), NanStatusType.NO_OTA_ACK);
+            mMockLooper.dispatchAll();
+
+            if (i != retryCount) {
+                inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                        eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(messageId));
+                mDut.onMessageSendQueuedSuccessResponse(transactionId.getValue());
+                mMockLooper.dispatchAll();
+            }
+        }
+
+        inOrder.verify(mockSessionCallback).onMessageSendFail(messageId,
+                NanStatusType.NO_OTA_ACK);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    /**
+     * Validate that the host-side message queue functions. Tests the perfect case of queue always
+     * succeeds and all messages are received on first attempt.
+     */
+    @Test
+    public void testSendMessageQueueSequence() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String serviceName = "some-service-name";
+        final int subscribeId = 15;
+        final int requestorId = 22;
+        final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
+        final int messageIdBase = 6948;
+        final int numberOfMessages = 30;
+        final int queueDepth = 6;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> messageIdCaptor = 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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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, null, null);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, null, null);
+
+        // (3) transmit messages
+        SendMessageQueueModelAnswer answerObj = new SendMessageQueueModelAnswer(queueDepth,
+                null, null, null);
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+                any(), anyInt())).thenAnswer(answerObj);
+
+        int remainingMessages = numberOfMessages;
+        for (int i = 0; i < numberOfMessages; ++i) {
+            mDut.sendMessage(clientId, sessionId.getValue(), requestorId, null, messageIdBase + i,
+                    0);
+            mMockLooper.dispatchAll();
+            // at 1/2 interval have the system simulate transmitting a queued message over-the-air
+            if (i % 2 == 1) {
+                assertTrue(answerObj.process());
+                remainingMessages--;
+                mMockLooper.dispatchAll();
+            }
+        }
+        for (int i = 0; i < remainingMessages; ++i) {
+            assertTrue(answerObj.process());
+            mMockLooper.dispatchAll();
+        }
+        assertEquals("queue empty", 0, answerObj.queueSize());
+
+        inOrder.verify(mockSessionCallback, times(numberOfMessages)).onMessageSendSuccess(
+                messageIdCaptor.capture());
+        for (int i = 0; i < numberOfMessages; ++i) {
+            assertEquals("message ID: " + i, (long) messageIdBase + i,
+                    (long) messageIdCaptor.getAllValues().get(i));
+        }
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Validate that the host-side message queue functions. A combination of imperfect conditions:
+     * - Failure to queue: synchronous firmware error
+     * - Failure to queue: asyncronous firmware error
+     * - Failure to transmit: OTA (which will be retried)
+     * - Failure to transmit: other
+     */
+    @Test
+    public void testSendMessageQueueSequenceImperfect() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String serviceName = "some-service-name";
+        final int subscribeId = 15;
+        final int requestorId = 22;
+        final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
+        final int messageIdBase = 6948;
+        final int numberOfMessages = 300;
+        final int queueDepth = 6;
+        final int retransmitCount = 3; // not the maximum
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> messageIdCaptor = 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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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, null, null);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, null, null);
+
+        // (3) transmit messages: configure a mix of failures/success
+        Set<Integer> failQueueCommandImmediately = new HashSet<>();
+        Set<Integer> failQueueCommandLater = new HashSet<>();
+        Map<Integer, Integer> numberOfRetries = new HashMap<>();
+
+        int numOfSuccesses = 0;
+        int numOfFailuresInternalFailure = 0;
+        int numOfFailuresNoOta = 0;
+        for (int i = 0; i < numberOfMessages; ++i) {
+            // random results:
+            // - 0-50: success
+            // - 51-60: retransmit value (which will fail for >5)
+            // - 61-70: fail queue later
+            // - 71-80: fail queue immediately
+            // - 81-90: fail retransmit with non-OTA failure
+            int random = mRandomNg.nextInt(90);
+            if (random <= 50) {
+                numberOfRetries.put(messageIdBase + i, 0);
+                numOfSuccesses++;
+            } else if (random <= 60) {
+                numberOfRetries.put(messageIdBase + i, random - 51);
+                if (random - 51 > retransmitCount) {
+                    numOfFailuresNoOta++;
+                } else {
+                    numOfSuccesses++;
+                }
+            } else if (random <= 70) {
+                failQueueCommandLater.add(messageIdBase + i);
+                numOfFailuresInternalFailure++;
+            } else if (random <= 80) {
+                failQueueCommandImmediately.add(messageIdBase + i);
+                numOfFailuresInternalFailure++;
+            } else {
+                numberOfRetries.put(messageIdBase + i, -1);
+                numOfFailuresInternalFailure++;
+            }
+        }
+
+        Log.v("WifiAwareStateManagerTest",
+                "failQueueCommandImmediately=" + failQueueCommandImmediately
+                        + ", failQueueCommandLater=" + failQueueCommandLater + ", numberOfRetries="
+                        + numberOfRetries + ", numOfSuccesses=" + numOfSuccesses
+                        + ", numOfFailuresInternalFailure=" + numOfFailuresInternalFailure
+                        + ", numOfFailuresNoOta=" + numOfFailuresNoOta);
+
+        SendMessageQueueModelAnswer answerObj = new SendMessageQueueModelAnswer(queueDepth,
+                failQueueCommandImmediately, failQueueCommandLater, numberOfRetries);
+        when(mMockNative.sendMessage(anyShort(), anyInt(), anyInt(), any(),
+                any(), anyInt())).thenAnswer(answerObj);
+
+        for (int i = 0; i < numberOfMessages; ++i) {
+            mDut.sendMessage(clientId, sessionId.getValue(), requestorId, null, messageIdBase + i,
+                    retransmitCount);
+            mMockLooper.dispatchAll();
+        }
+
+        while (answerObj.queueSize() != 0) {
+            assertTrue(answerObj.process());
+            mMockLooper.dispatchAll();
+        }
+
+        verify(mockSessionCallback, times(numOfSuccesses)).onMessageSendSuccess(anyInt());
+        verify(mockSessionCallback, times(numOfFailuresInternalFailure)).onMessageSendFail(anyInt(),
+                eq(NanStatusType.INTERNAL_FAILURE));
+        verify(mockSessionCallback, times(numOfFailuresNoOta)).onMessageSendFail(anyInt(),
+                eq(NanStatusType.NO_OTA_ACK));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * Validate that can send empty message successfully: null, byte[0], ""
+     */
+    @Test
+    public void testSendEmptyMessages() throws Exception {
+        final int clientId = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final String serviceName = "some-service-name";
+        final String ssi = "some much longer and more arbitrary data";
+        final int subscribeId = 15;
+        final 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().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi.getBytes())
+                .setSubscribeType(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE)
+                .build();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<byte[]> byteArrayCaptor = ArgumentCaptor.forClass(byte[].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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(),
+                peerMatchFilter.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerMatchFilter.getBytes());
+
+        // (3) message null Tx successful queuing
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, null, messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), isNull(byte[].class), eq(messageId));
+        short tid = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(tid);
+        mMockLooper.dispatchAll();
+
+        // (4) final Tx results (on-air results)
+        mDut.onMessageSendSuccessNotification(tid);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(messageId);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        // (5) message byte[0] Tx successful queuing
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, new byte[0], messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), eq(new byte[0]), eq(messageId));
+        tid = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(tid);
+        mMockLooper.dispatchAll();
+
+        // (6) final Tx results (on-air results)
+        mDut.onMessageSendSuccessNotification(tid);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(messageId);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        // (7) message "" Tx successful queuing
+        mDut.sendMessage(clientId, sessionId.getValue(), requestorId, "".getBytes(), messageId, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
+                eq(requestorId), eq(peerMac), byteArrayCaptor.capture(), eq(messageId));
+        collector.checkThat("Empty message contents", "",
+                equalTo(new String(byteArrayCaptor.getValue())));
+        tid = transactionId.getValue();
+        mDut.onMessageSendQueuedSuccessResponse(tid);
+        mMockLooper.dispatchAll();
+
+        // (8) final Tx results (on-air results)
+        mDut.onMessageSendSuccessNotification(tid);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(messageId);
+        validateInternalSendMessageQueuesCleanedUp(messageId);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative);
+    }
+
+    private class SendMessageQueueModelAnswer extends MockAnswerUtil.AnswerWithArguments {
+        private final int mMaxQueueDepth;
+
+        // keyed by message ID
+        private final Set<Integer> mFailQueueCommandImmediately; // return a false
+        private final Set<Integer> mFailQueueCommandLater; // return an error != TX_QUEUE_FULL
+
+        // # of times to return NO_OTA_ACK before returning SUCCESS. So a 0 means success on first
+        // try, a very large number means - never succeed (since max retry is 5).
+        // a -1 impiles a non-OTA failure: on first attempt
+        private final Map<Integer, Integer> mRetryLimit;
+
+        private final LinkedList<Short> mQueue = new LinkedList<>(); // transaction ID (tid)
+        private final Map<Short, Integer> mMessageIdsByTid = new HashMap<>(); // tid -> message ID
+        private final Map<Integer, Integer> mTriesUsedByMid = new HashMap<>(); // mid -> # of retx
+
+        SendMessageQueueModelAnswer(int maxQueueDepth, Set<Integer> failQueueCommandImmediately,
+                Set<Integer> failQueueCommandLater, Map<Integer, Integer> numberOfRetries) {
+            mMaxQueueDepth = maxQueueDepth;
+            mFailQueueCommandImmediately = failQueueCommandImmediately;
+            mFailQueueCommandLater = failQueueCommandLater;
+            mRetryLimit = numberOfRetries;
+
+            if (mRetryLimit != null) {
+                for (int mid : mRetryLimit.keySet()) {
+                    mTriesUsedByMid.put(mid, 0);
+                }
+            }
+        }
+
+        public boolean answer(short transactionId, int pubSubId, int requestorInstanceId,
+                byte[] dest, byte[] message, int messageId) throws Exception {
+            if (mFailQueueCommandImmediately != null && mFailQueueCommandImmediately.contains(
+                    messageId)) {
+                return false;
+            }
+
+            if (mFailQueueCommandLater != null && mFailQueueCommandLater.contains(messageId)) {
+                mDut.onMessageSendQueuedFailResponse(transactionId, NanStatusType.INTERNAL_FAILURE);
+            } else {
+                if (mQueue.size() <= mMaxQueueDepth) {
+                    mQueue.addLast(transactionId);
+                    mMessageIdsByTid.put(transactionId, messageId);
+                    mDut.onMessageSendQueuedSuccessResponse(transactionId);
+                } else {
+                    mDut.onMessageSendQueuedFailResponse(transactionId,
+                            NanStatusType.FOLLOWUP_TX_QUEUE_FULL);
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Processes the first message in the queue: i.e. responds as if sent over-the-air
+         * (successfully or failed)
+         */
+        boolean process() {
+            if (mQueue.size() == 0) {
+                return false;
+            }
+            short tid = mQueue.poll();
+            int mid = mMessageIdsByTid.get(tid);
+
+            if (mRetryLimit != null && mRetryLimit.containsKey(mid)) {
+                int numRetries = mRetryLimit.get(mid);
+                if (numRetries == -1) {
+                    mDut.onMessageSendFailNotification(tid, NanStatusType.INTERNAL_FAILURE);
+                } else {
+                    int currentRetries = mTriesUsedByMid.get(mid);
+                    if (currentRetries > numRetries) {
+                        return false; // shouldn't be retrying!?
+                    } else if (currentRetries == numRetries) {
+                        mDut.onMessageSendSuccessNotification(tid);
+                    } else {
+                        mDut.onMessageSendFailNotification(tid, NanStatusType.NO_OTA_ACK);
+                    }
+                    mTriesUsedByMid.put(mid, currentRetries + 1);
+                }
+            } else {
+                mDut.onMessageSendSuccessNotification(tid);
+            }
+
+            return true;
+        }
+
+        /**
+         * Returns the number of elements in the queue.
+         */
+        int queueSize() {
+            return mQueue.size();
+        }
+    }
+
+    /**
+     * 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 pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        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();
+
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<WifiAwareClientState> clientCaptor =
+                ArgumentCaptor.forClass(WifiAwareClientState.class);
+        ArgumentCaptor<RttManager.RttParams[]> rttParamsCaptor =
+                ArgumentCaptor.forClass(RttManager.RttParams[].class);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative,
+                mMockAwareRttStateManager);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(),
+                peerMatchFilter.getBytes());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
+        inOrder.verify(mockSessionCallback).onMatch(requestorId, peerSsi.getBytes(),
+                peerMatchFilter.getBytes());
+
+        // (3) start ranging: pass along a valid peer ID and an invalid one
+        mDut.startRanging(clientId, sessionId.getValue(), params, rangingId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockAwareRttStateManager).startRanging(eq(rangingId),
+                clientCaptor.capture(), rttParamsCaptor.capture());
+        collector.checkThat("RttParams[0].bssid", "06:07:08:09:0A:0B",
+                equalTo(rttParamsCaptor.getValue()[0].bssid));
+        collector.checkThat("RttParams[1].bssid", "", equalTo(rttParamsCaptor.getValue()[1].bssid));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative,
+                mMockAwareRttStateManager);
+    }
+
+    /**
+     * 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 clientId1 = 9999;
+        final int clientId2 = 1001;
+        final int clientId3 = 1005;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int masterPref1 = 111;
+        final int masterPref3 = 115;
+        final int dwInterval1Band24 = 2;
+        final int dwInterval3Band24 = 1;
+        final int dwInterval3Band5 = 0;
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<ConfigRequest> crCapture = ArgumentCaptor.forClass(ConfigRequest.class);
+
+        ConfigRequest configRequest1 = new ConfigRequest.Builder()
+                .setClusterLow(5).setClusterHigh(100)
+                .setMasterPreference(masterPref1)
+                .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ, dwInterval1Band24)
+                .build();
+
+        ConfigRequest configRequest2 = new ConfigRequest.Builder()
+                .setSupport5gBand(true) // compatible
+                .setClusterLow(7).setClusterHigh(155) // incompatible!
+                .setMasterPreference(0) // compatible
+                .build();
+
+        ConfigRequest configRequest3  = new ConfigRequest.Builder()
+                .setSupport5gBand(true) // compatible (will use true)
+                .setClusterLow(5).setClusterHigh(100) // identical (hence compatible)
+                .setMasterPreference(masterPref3) // compatible (will use max)
+                // compatible: will use min
+                .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ, dwInterval3Band24)
+                // compatible: will use interval3 since interval1 not init
+                .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, dwInterval3Band5)
+                .build();
+
+        IWifiAwareEventCallback mockCallback1 = mock(IWifiAwareEventCallback.class);
+        IWifiAwareEventCallback mockCallback2 = mock(IWifiAwareEventCallback.class);
+        IWifiAwareEventCallback mockCallback3 = mock(IWifiAwareEventCallback.class);
+
+        InOrder inOrder = inOrder(mMockNative, mockCallback1, mockCallback2, mockCallback3);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) config1 (valid)
+        mDut.connect(clientId1, uid, pid, callingPackage, mockCallback1, configRequest1, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                crCapture.capture(), eq(false), eq(true));
+        collector.checkThat("merge: stage 1", crCapture.getValue(), equalTo(configRequest1));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback1).onConnectSuccess(clientId1);
+
+        // (2) config2 (incompatible with config1)
+        mDut.connect(clientId2, uid, pid, callingPackage, mockCallback2, configRequest2, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback2).onConnectFail(NanStatusType.INTERNAL_FAILURE);
+        validateInternalClientInfoCleanedUp(clientId2);
+
+        // (3) config3 (compatible with config1)
+        mDut.connect(clientId3, uid, pid, callingPackage, mockCallback3, configRequest3, true);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                crCapture.capture(), eq(true), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback3).onConnectSuccess(clientId3);
+
+        collector.checkThat("support 5g: or", true, equalTo(crCapture.getValue().mSupport5gBand));
+        collector.checkThat("master preference: max", Math.max(masterPref1, masterPref3),
+                equalTo(crCapture.getValue().mMasterPreference));
+        collector.checkThat("dw interval on 2.4: ~min",
+                Math.min(dwInterval1Band24, dwInterval3Band24),
+                equalTo(crCapture.getValue().mDiscoveryWindowInterval[ConfigRequest
+                        .NAN_BAND_24GHZ]));
+        collector.checkThat("dw interval on 5: ~min", dwInterval3Band5,
+                equalTo(crCapture.getValue().mDiscoveryWindowInterval[ConfigRequest
+                        .NAN_BAND_5GHZ]));
+
+        // (4) disconnect config3: downgrade to config1
+        mDut.disconnect(clientId3);
+        mMockLooper.dispatchAll();
+        validateInternalClientInfoCleanedUp(clientId3);
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                crCapture.capture(), eq(false), eq(false));
+
+        collector.checkThat("configRequest1", configRequest1, equalTo(crCapture.getValue()));
+
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+
+        // (5) disconnect config1: disable
+        mDut.disconnect(clientId1);
+        mMockLooper.dispatchAll();
+        validateInternalClientInfoCleanedUp(clientId1);
+        inOrder.verify(mMockNative).disable((short) 0);
+
+        verifyNoMoreInteractions(mMockNative, mockCallback1, mockCallback2, mockCallback3);
+    }
+
+    /**
+     * Summary: disconnect a client while there are pending transactions.
+     */
+    @Test
+    public void testDisconnectWithPendingTransactions() throws Exception {
+        final int clientId = 125;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int clusterLow = 5;
+        final int clusterHigh = 100;
+        final int masterPref = 111;
+        final String serviceName = "some-service-name";
+        final String ssi = "some much longer and more arbitrary data";
+        final int publishId = 22;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
+
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
+                serviceName).setServiceSpecificInfo(ssi.getBytes()).setPublishType(
+                PublishConfig.PUBLISH_TYPE_UNSOLICITED).build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (2) publish (no response yet)
+        mDut.publish(clientId, publishConfig, mockSessionCallback);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishConfig));
+
+        // (3) disconnect (but doesn't get executed until get a RESPONSE to the
+        // previous publish)
+        mDut.disconnect(clientId);
+        mMockLooper.dispatchAll();
+
+        // (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();
+
+        // (6) got some callback on original publishId - should be ignored
+        mDut.onSessionTerminatedNotification(publishId, 0, true);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mMockNative, mockCallback, mockSessionCallback);
+    }
+
+    /**
+     * 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 testUnknownTransactionType() throws Exception {
+        final int clientId = 129;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        final int clusterLow = 15;
+        final int clusterHigh = 192;
+        final int masterPref = 234;
+        final String serviceName = "some-service-name";
+        final String ssi = "some much longer and more arbitrary data";
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setMasterPreference(masterPref).build();
+
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
+                serviceName).setServiceSpecificInfo(ssi.getBytes()).setPublishType(
+                PublishConfig.PUBLISH_TYPE_UNSOLICITED).build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockPublishSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback, mockPublishSessionCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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 testNoOpTransaction() throws Exception {
+        final int clientId = 1294;
+        final int uid = 1000;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), 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;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        InOrder inOrder = inOrder(mMockNative, mockCallback);
+
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect and succeed
+        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        short transactionIdConfig = transactionId.getValue();
+        mDut.onConfigSuccessResponse(transactionIdConfig);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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], new byte[0]);
+        mDut.onSessionTerminatedNotification(-1, -1, true);
+        mDut.onSessionTerminatedNotification(-1, -1, false);
+        mDut.onMessageReceivedNotification(-1, -1, new byte[0], new byte[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 pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        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);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
+                eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(NanStatusType.INTERNAL_FAILURE);
+
+        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 pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        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);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        // (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(NanStatusType.INTERNAL_FAILURE);
+
+        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;
+        final int pid = 2000;
+        final String callingPackage = "com.google.somePackage";
+        int loopCount = 100;
+
+        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);
+        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
+                IWifiAwareDiscoverySessionCallback.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, pid, callingPackage, mockCallback, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess(clientId);
+
+        int prevId = 0;
+        for (int i = 0; i < loopCount; ++i) {
+            // (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("Session ID incrementing", sessionId.getValue() > prevId);
+            }
+            prevId = sessionId.getValue();
+        }
+    }
+
+    /*
+     * Tests of internal state of WifiAwareStateManager: 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.
+     */
+
+    /**
+     * 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.
+     *
+     * @param clientId The ID of the client which should be deleted.
+     */
+    private void validateInternalClientInfoCleanedUp(int clientId) throws Exception {
+        WifiAwareClientState 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
+     * (deleted) after a session is terminated through API (not callback!). To
+     * be used in every test which terminates a session.
+     *
+     * @param clientId The ID of the client containing the session.
+     * @param sessionId The ID of the terminated session.
+     */
+    private void validateInternalSessionInfoCleanedUp(int clientId, int sessionId)
+            throws Exception {
+        WifiAwareClientState client = getInternalClientState(mDut, clientId);
+        collector.checkThat("Client record exists clientId=" + clientId, client, notNullValue());
+        WifiAwareDiscoverySessionState 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 {
+        WifiAwareClientState client = getInternalClientState(mDut, clientId);
+        collector.checkThat("Client record exists clientId=" + clientId, client, notNullValue());
+
+        Field field = WifiAwareClientState.class.getDeclaredField("mSessions");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        SparseArray<WifiAwareDiscoverySessionState> sessions =
+                (SparseArray<WifiAwareDiscoverySessionState>) field.get(client);
+
+        collector.checkThat("No sessions exist for clientId=" + clientId, sessions.size(),
+                equalTo(0));
+    }
+
+    /**
+     * Validates that the broadcast sent on Aware status change is correct.
+     *
+     * @param expectedEnabled The expected change status - i.e. are we expected
+     *            to announce that Aware is enabled (true) or disabled (false).
+     */
+    private void validateCorrectAwareStatusChangeBroadcast(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(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED));
+    }
+
+    /*
+     * Utilities
+     */
+    private void dumpDut(String prefix) {
+        StringWriter sw = new StringWriter();
+        mDut.dump(null, new PrintWriter(sw), null);
+        Log.e("WifiAwareStateManagerTest", prefix + sw.toString());
+    }
+
+    private static void installMocksInStateManager(WifiAwareStateManager awareStateManager,
+            WifiAwareRttStateManager mockRtt, WifiAwareDataPathStateManager mockDpMgr)
+            throws Exception {
+        Field field = WifiAwareStateManager.class.getDeclaredField("mRtt");
+        field.setAccessible(true);
+        field.set(awareStateManager, mockRtt);
+
+        field = WifiAwareStateManager.class.getDeclaredField("mDataPathMgr");
+        field.setAccessible(true);
+        field.set(awareStateManager, mockDpMgr);
+    }
+
+    private static WifiAwareClientState getInternalClientState(WifiAwareStateManager dut,
+            int clientId) throws Exception {
+        Field field = WifiAwareStateManager.class.getDeclaredField("mClients");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        SparseArray<WifiAwareClientState> clients = (SparseArray<WifiAwareClientState>) field.get(
+                dut);
+
+        return clients.get(clientId);
+    }
+
+    private static WifiAwareDiscoverySessionState getInternalSessionState(
+            WifiAwareClientState client, int sessionId) throws Exception {
+        Field field = WifiAwareClientState.class.getDeclaredField("mSessions");
+        field.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        SparseArray<WifiAwareDiscoverySessionState> sessions =
+                (SparseArray<WifiAwareDiscoverySessionState>) field.get(client);
+
+        return sessions.get(sessionId);
+    }
+
+    private void validateInternalSendMessageQueuesCleanedUp(int messageId) throws Exception {
+        Field field = WifiAwareStateManager.class.getDeclaredField("mSm");
+        field.setAccessible(true);
+        WifiAwareStateManager.WifiAwareStateMachine sm =
+                (WifiAwareStateManager.WifiAwareStateMachine) field.get(mDut);
+
+        field = WifiAwareStateManager.WifiAwareStateMachine.class.getDeclaredField(
+                "mHostQueuedSendMessages");
+        field.setAccessible(true);
+        SparseArray<Message> hostQueuedSendMessages = (SparseArray<Message>) field.get(sm);
+
+        field = WifiAwareStateManager.WifiAwareStateMachine.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 Capabilities getCapabilities() {
+        Capabilities cap = new Capabilities();
+        cap.maxConcurrentAwareClusters = 1;
+        cap.maxPublishes = 2;
+        cap.maxSubscribes = 2;
+        cap.maxServiceNameLen = 255;
+        cap.maxMatchFilterLen = 255;
+        cap.maxTotalMatchFilterLen = 255;
+        cap.maxServiceSpecificInfoLen = 255;
+        cap.maxExtendedServiceSpecificInfoLen = 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/hotspot2/ANQPDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPDataTest.java
new file mode 100644
index 0000000..b4e667b
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPDataTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.ANQPData}.
+ *
+ * TODO(b/33000864): add more test once the ANQP elements cleanup are completed, which will
+ * allow easy construction of ANQP elements for testing.
+ */
+@SmallTest
+public class ANQPDataTest {
+    @Mock Clock mClock;
+
+    /**
+     * Sets up test.
+     */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        // Returning the initial timestamp.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+    }
+
+    /**
+     * Verify creation of ANQPData with null elements.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void createWithNullElements() throws Exception {
+        ANQPData data = new ANQPData(mClock, null);
+        Map<Constants.ANQPElementType, ANQPElement> elements = data.getElements();
+        assertTrue(elements.isEmpty());
+    }
+
+    /**
+     * Verify the data expiration behavior.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyExpiration() throws Exception {
+        ANQPData data = new ANQPData(mClock, null);
+        assertFalse(data.expired(ANQPData.DATA_LIFETIME_MILLISECONDS - 1));
+        assertTrue(data.expired(ANQPData.DATA_LIFETIME_MILLISECONDS));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPMatcherTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPMatcherTest.java
new file mode 100644
index 0000000..5fbd4aa
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPMatcherTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.EAPConstants;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.hotspot2.anqp.CellularNetwork;
+import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
+import com.android.server.wifi.hotspot2.anqp.NAIRealmData;
+import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
+import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
+import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
+import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
+import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
+import com.android.server.wifi.hotspot2.anqp.eap.InnerAuthEAP;
+import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.ANQPMatcher}.
+ */
+@SmallTest
+public class ANQPMatcherTest {
+    /**
+     * Verify that domain name match will fail when a null Domain Name ANQP element is provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchDomainNameWithNullElement() throws Exception {
+        assertFalse(ANQPMatcher.matchDomainName(null, "test.com", null, null));
+    }
+
+    /**
+     * Verify that domain name match will succeed when the specified FQDN matches a domain name
+     * in the Domain Name ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchDomainNameUsingFQDN() throws Exception {
+        String fqdn = "test.com";
+        String[] domains = new String[] {fqdn};
+        DomainNameElement element = new DomainNameElement(Arrays.asList(domains));
+        assertTrue(ANQPMatcher.matchDomainName(element, fqdn, null, null));
+    }
+
+    /**
+     * Verify that domain name match will succeed when the specified IMSI parameter and IMSI list
+     * matches a 3GPP network domain in the Domain Name ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchDomainNameUsingIMSI() throws Exception {
+        IMSIParameter imsiParam = new IMSIParameter("1234", true);
+        List<String> simImsiList = Arrays.asList(new String[] {"123457890", "123498723"});
+        // 3GPP network domain with MCC=123 and MNC=456.
+        String[] domains = new String[] {"wlan.mnc457.mcc123.3gppnetwork.org"};
+        DomainNameElement element = new DomainNameElement(Arrays.asList(domains));
+        assertTrue(ANQPMatcher.matchDomainName(element, null, imsiParam, simImsiList));
+    }
+
+    /**
+     * Verify that roaming consortium match will fail when a null Roaming Consortium ANQP
+     * element is provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchRoamingConsortiumWithNullElement() throws Exception {
+        assertFalse(ANQPMatcher.matchRoamingConsortium(null, new long[0]));
+    }
+
+    /**
+     * Verify that a roaming consortium match will succeed when the specified OI matches
+     * an OI in the Roaming Consortium ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchRoamingConsortium() throws Exception {
+        long oi = 0x1234L;
+        RoamingConsortiumElement element =
+                new RoamingConsortiumElement(Arrays.asList(new Long[] {oi}));
+        assertTrue(ANQPMatcher.matchRoamingConsortium(element, new long[] {oi}));
+    }
+
+    /**
+     * Verify that an indeterminate match will be returned when matching a null NAI Realm
+     * ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealmWithNullElement() throws Exception {
+        assertEquals(AuthMatch.INDETERMINATE, ANQPMatcher.matchNAIRealm(null, "test.com",
+                EAPConstants.EAP_TLS, new InnerAuthEAP(EAPConstants.EAP_TTLS)));
+    }
+
+    /**
+     * Verify that an indeterminate match will be returned when matching a NAI Realm
+     * ANQP element contained no NAI realm data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealmWithEmtpyRealmData() throws Exception {
+        NAIRealmElement element = new NAIRealmElement(new ArrayList<NAIRealmData>());
+        assertEquals(AuthMatch.INDETERMINATE, ANQPMatcher.matchNAIRealm(element, "test.com",
+                EAPConstants.EAP_TLS, null));
+    }
+
+    /**
+     * Verify that a realm match will be returned when the specified realm matches a realm
+     * in the NAI Realm ANQP element with no EAP methods.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealmWithRealmMatch() throws Exception {
+        String realm = "test.com";
+        NAIRealmData realmData = new NAIRealmData(
+                Arrays.asList(new String[] {realm}), new ArrayList<EAPMethod>());
+        NAIRealmElement element = new NAIRealmElement(
+                Arrays.asList(new NAIRealmData[] {realmData}));
+        assertEquals(AuthMatch.REALM, ANQPMatcher.matchNAIRealm(element, realm,
+                EAPConstants.EAP_TLS, null));
+    }
+
+    /**
+     * Verify that a realm and method match will be returned when the specified realm and EAP
+     * method matches a realm in the NAI Realm ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealmWithRealmMethodMatch() throws Exception {
+        // Test data.
+        String realm = "test.com";
+        int eapMethodID = EAPConstants.EAP_TLS;
+
+        // Setup NAI Realm element.
+        EAPMethod method = new EAPMethod(eapMethodID, new HashMap<Integer, Set<AuthParam>>());
+        NAIRealmData realmData = new NAIRealmData(
+                Arrays.asList(new String[] {realm}), Arrays.asList(new EAPMethod[] {method}));
+        NAIRealmElement element = new NAIRealmElement(
+                Arrays.asList(new NAIRealmData[] {realmData}));
+
+        assertEquals(AuthMatch.REALM | AuthMatch.METHOD,
+                ANQPMatcher.matchNAIRealm(element, realm, eapMethodID, null));
+    }
+
+    /**
+     * Verify that an exact match will be returned when the specified realm, EAP
+     * method, and the authentication parameter matches a realm with the associated EAP method and
+     * authentication parameter in the NAI Realm ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealmWithExactMatch() throws Exception {
+        // Test data.
+        String realm = "test.com";
+        int eapMethodID = EAPConstants.EAP_TTLS;
+        NonEAPInnerAuth authParam = new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAP);
+        Set<AuthParam> authSet = new HashSet<>();
+        authSet.add(authParam);
+        Map<Integer, Set<AuthParam>> authMap = new HashMap<>();
+        authMap.put(authParam.getAuthTypeID(), authSet);
+
+        // Setup NAI Realm element.
+        EAPMethod method = new EAPMethod(eapMethodID, authMap);
+        NAIRealmData realmData = new NAIRealmData(
+                Arrays.asList(new String[] {realm}), Arrays.asList(new EAPMethod[] {method}));
+        NAIRealmElement element = new NAIRealmElement(
+                Arrays.asList(new NAIRealmData[] {realmData}));
+
+        assertEquals(AuthMatch.EXACT,
+                ANQPMatcher.matchNAIRealm(element, realm, eapMethodID, authParam));
+    }
+
+    /**
+     * Verify that a mismatch (AuthMatch.NONE) will be returned when the specified EAP method
+     * doesn't match with the corresponding EAP method in the NAI Realm ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealmWithEAPMethodMismatch() throws Exception {
+        // Test data.
+        String realm = "test.com";
+        int eapMethodID = EAPConstants.EAP_TTLS;
+        NonEAPInnerAuth authParam = new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAP);
+        Set<AuthParam> authSet = new HashSet<>();
+        authSet.add(authParam);
+        Map<Integer, Set<AuthParam>> authMap = new HashMap<>();
+        authMap.put(authParam.getAuthTypeID(), authSet);
+
+        // Setup NAI Realm element.
+        EAPMethod method = new EAPMethod(eapMethodID, authMap);
+        NAIRealmData realmData = new NAIRealmData(
+                Arrays.asList(new String[] {realm}), Arrays.asList(new EAPMethod[] {method}));
+        NAIRealmElement element = new NAIRealmElement(
+                Arrays.asList(new NAIRealmData[] {realmData}));
+
+        assertEquals(AuthMatch.NONE,
+                ANQPMatcher.matchNAIRealm(element, realm, EAPConstants.EAP_TLS, null));
+    }
+
+    /**
+     * Verify that a mismatch (AuthMatch.NONE) will be returned when the specified authentication
+     * parameter doesn't match with the corresponding authentication parameter in the NAI Realm
+     * ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealmWithAuthTypeMismatch() throws Exception {
+        // Test data.
+        String realm = "test.com";
+        int eapMethodID = EAPConstants.EAP_TTLS;
+        NonEAPInnerAuth authParam = new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAP);
+        Set<AuthParam> authSet = new HashSet<>();
+        authSet.add(authParam);
+        Map<Integer, Set<AuthParam>> authMap = new HashMap<>();
+        authMap.put(authParam.getAuthTypeID(), authSet);
+
+        // Setup NAI Realm element.
+        EAPMethod method = new EAPMethod(eapMethodID, authMap);
+        NAIRealmData realmData = new NAIRealmData(
+                Arrays.asList(new String[] {realm}), Arrays.asList(new EAPMethod[] {method}));
+        NAIRealmElement element = new NAIRealmElement(
+                Arrays.asList(new NAIRealmData[] {realmData}));
+
+        // Mismatch in authentication type.
+        assertEquals(AuthMatch.NONE,
+                ANQPMatcher.matchNAIRealm(element, realm, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_PAP)));
+    }
+
+    /**
+     * Verify that 3GPP Network match will fail when a null element is provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchThreeGPPNetworkWithNullElement() throws Exception {
+        IMSIParameter imsiParam = new IMSIParameter("1234", true);
+        List<String> simImsiList = Arrays.asList(new String[] {"123456789", "123498723"});
+        assertFalse(ANQPMatcher.matchThreeGPPNetwork(null, imsiParam, simImsiList));
+    }
+
+    /**
+     * Verify that 3GPP network will succeed when the given 3GPP Network ANQP element contained
+     * a MCC-MNC that matches the both IMSI parameter and an IMSI from the IMSI list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchThreeGPPNetwork() throws Exception {
+        IMSIParameter imsiParam = new IMSIParameter("1234", true);
+        List<String> simImsiList = Arrays.asList(new String[] {"123456789", "123498723"});
+
+        CellularNetwork network = new CellularNetwork(Arrays.asList(new String[] {"123456"}));
+        ThreeGPPNetworkElement element =
+                new ThreeGPPNetworkElement(Arrays.asList(new CellularNetwork[] {network}));
+        // The MCC-MNC provided in 3GPP Network ANQP element matches both IMSI parameter
+        // and an IMSI from the installed SIM card.
+        assertTrue(ANQPMatcher.matchThreeGPPNetwork(element, imsiParam, simImsiList));
+    }
+
+    /**
+     * Verify that 3GPP network will failed when the given 3GPP Network ANQP element contained
+     * a MCC-MNC that match the IMSI parameter but not the IMSI list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchThreeGPPNetworkWithoutSimImsiMatch() throws Exception {
+        IMSIParameter imsiParam = new IMSIParameter("1234", true);
+        List<String> simImsiList = Arrays.asList(new String[] {"123457890", "123498723"});
+
+        CellularNetwork network = new CellularNetwork(Arrays.asList(new String[] {"123456"}));
+        ThreeGPPNetworkElement element =
+                new ThreeGPPNetworkElement(Arrays.asList(new CellularNetwork[] {network}));
+        // The MCC-MNC provided in 3GPP Network ANQP element doesn't match any of the IMSIs
+        // from the installed SIM card.
+        assertFalse(ANQPMatcher.matchThreeGPPNetwork(element, imsiParam, simImsiList));
+    }
+
+    /**
+     * Verify that 3GPP network will failed when the given 3GPP Network ANQP element contained
+     * a MCC-MNC that doesn't match with the IMSI parameter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchThreeGPPNetworkWithImsiParamMismatch() throws Exception {
+        IMSIParameter imsiParam = new IMSIParameter("1234", true);
+        List<String> simImsiList = Arrays.asList(new String[] {"123457890", "123498723"});
+
+        CellularNetwork network = new CellularNetwork(Arrays.asList(new String[] {"123356"}));
+        ThreeGPPNetworkElement element =
+                new ThreeGPPNetworkElement(Arrays.asList(new CellularNetwork[] {network}));
+        // The MCC-MNC provided in 3GPP Network ANQP element doesn't match the IMSI parameter.
+        assertFalse(ANQPMatcher.matchThreeGPPNetwork(element, imsiParam, simImsiList));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPNetworkKeyTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPNetworkKeyTest.java
new file mode 100644
index 0000000..1586d76
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPNetworkKeyTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.ANQPNetworkKey}.
+ */
+@SmallTest
+public class ANQPNetworkKeyTest {
+    private static final String SSID = "TestSSID";
+    private static final long BSSID = 0x123456L;
+    private static final long HESSID = 0x789012L;
+    private static final int ANQP_DOMAIN_ID = 1;
+
+    /**
+     * Verify that building a SSID based key works as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void buildStandardESSKey() throws Exception {
+        ANQPNetworkKey expectedKey = new ANQPNetworkKey(SSID, 0, 0, ANQP_DOMAIN_ID);
+        ANQPNetworkKey actualKey = ANQPNetworkKey.buildKey(SSID, BSSID, 0, ANQP_DOMAIN_ID);
+        assertEquals(expectedKey, actualKey);
+    }
+
+    /**
+     * Verify that building a HESSID based key works as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void buildHessidKey() throws Exception {
+        ANQPNetworkKey expectedKey = new ANQPNetworkKey(null, 0, HESSID, ANQP_DOMAIN_ID);
+        ANQPNetworkKey actualKey = ANQPNetworkKey.buildKey(SSID, BSSID, HESSID, ANQP_DOMAIN_ID);
+        assertEquals(expectedKey, actualKey);
+    }
+
+    /**
+     * Verify that building a key based on an AP (SSID + BSSID) works as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void buildAPKey() throws Exception {
+        ANQPNetworkKey expectedKey = new ANQPNetworkKey(SSID, BSSID, 0, 0);
+        ANQPNetworkKey actualKey = ANQPNetworkKey.buildKey(SSID, BSSID, HESSID, 0);
+        assertEquals(expectedKey, actualKey);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java
new file mode 100644
index 0000000..10c1472
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java
@@ -0,0 +1,308 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.ANQPRequestManager}.
+ */
+@SmallTest
+public class ANQPRequestManagerTest {
+    private static final long TEST_BSSID = 0x123456L;
+    private static final ANQPNetworkKey TEST_ANQP_KEY =
+            new ANQPNetworkKey("TestSSID", TEST_BSSID, 0, 0);
+
+    private static final List<Constants.ANQPElementType> R1_ANQP_WITHOUT_RC = Arrays.asList(
+            Constants.ANQPElementType.ANQPVenueName,
+            Constants.ANQPElementType.ANQPIPAddrAvailability,
+            Constants.ANQPElementType.ANQPNAIRealm,
+            Constants.ANQPElementType.ANQP3GPPNetwork,
+            Constants.ANQPElementType.ANQPDomName);
+
+    private static final List<Constants.ANQPElementType> R1_ANQP_WITH_RC = Arrays.asList(
+            Constants.ANQPElementType.ANQPVenueName,
+            Constants.ANQPElementType.ANQPIPAddrAvailability,
+            Constants.ANQPElementType.ANQPNAIRealm,
+            Constants.ANQPElementType.ANQP3GPPNetwork,
+            Constants.ANQPElementType.ANQPDomName,
+            Constants.ANQPElementType.ANQPRoamingConsortium);
+
+    private static final List<Constants.ANQPElementType> R1R2_ANQP_WITHOUT_RC = Arrays.asList(
+            Constants.ANQPElementType.ANQPVenueName,
+            Constants.ANQPElementType.ANQPIPAddrAvailability,
+            Constants.ANQPElementType.ANQPNAIRealm,
+            Constants.ANQPElementType.ANQP3GPPNetwork,
+            Constants.ANQPElementType.ANQPDomName,
+            Constants.ANQPElementType.HSFriendlyName,
+            Constants.ANQPElementType.HSWANMetrics,
+            Constants.ANQPElementType.HSConnCapability,
+            Constants.ANQPElementType.HSOSUProviders);
+
+    private static final List<Constants.ANQPElementType> R1R2_ANQP_WITH_RC = Arrays.asList(
+            Constants.ANQPElementType.ANQPVenueName,
+            Constants.ANQPElementType.ANQPIPAddrAvailability,
+            Constants.ANQPElementType.ANQPNAIRealm,
+            Constants.ANQPElementType.ANQP3GPPNetwork,
+            Constants.ANQPElementType.ANQPDomName,
+            Constants.ANQPElementType.ANQPRoamingConsortium,
+            Constants.ANQPElementType.HSFriendlyName,
+            Constants.ANQPElementType.HSWANMetrics,
+            Constants.ANQPElementType.HSConnCapability,
+            Constants.ANQPElementType.HSOSUProviders);
+
+    @Mock PasspointEventHandler mHandler;
+    @Mock Clock mClock;
+    ANQPRequestManager mManager;
+
+    /**
+     * Test setup.
+     */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        mManager = new ANQPRequestManager(mHandler, mClock);
+    }
+
+    /**
+     * Verify that the expected set of ANQP elements are being requested when the targeted AP
+     * doesn't provide roaming consortium OIs and doesn't support Hotspot 2.0 Release 2 ANQP
+     * elements, based on the IEs in the scan result .
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestR1ANQPElementsWithoutRC() throws Exception {
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+    }
+
+    /**
+     * Verify that the expected set of ANQP elements are being requested when the targeted AP
+     * does provide roaming consortium OIs and doesn't support Hotspot 2.0 Release ANQP elements,
+     * based on the IEs in the scan result.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestR1ANQPElementsWithRC() throws Exception {
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITH_RC)).thenReturn(true);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, true, false));
+    }
+
+    /**
+     * Verify that the expected set of ANQP elements are being requested when the targeted AP
+     * doesn't provide roaming consortium OIs and does support Hotspot 2.0 Release ANQP elements,
+     * based on the IEs in the scan result.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestR1R2ANQPElementsWithoutRC() throws Exception {
+        when(mHandler.requestANQP(TEST_BSSID, R1R2_ANQP_WITHOUT_RC)).thenReturn(true);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, true));
+    }
+
+    /**
+     * Verify that the expected set of ANQP elements are being requested when the targeted AP
+     * does provide roaming consortium OIs and support Hotspot 2.0 Release ANQP elements,
+     * based on the IEs in the scan result.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestR1R2ANQPElementsWithRC() throws Exception {
+        when(mHandler.requestANQP(TEST_BSSID, R1R2_ANQP_WITH_RC)).thenReturn(true);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, true, true));
+    }
+
+    /**
+     * Verify that attempt to request ANQP elements from an AP will fail when there is a request
+     * already pending.  The request will succeed when the hold off time is up.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestANQPElementsWithPendingRequest() throws Exception {
+        // Send the initial request.
+        long startTime = 0;
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        reset(mHandler);
+
+        // Attempt another request will fail while one is still pending and hold off time is not up
+        // yet.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime + 1);
+        assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        verify(mHandler, never()).requestANQP(anyLong(), anyObject());
+        reset(mHandler);
+
+        // Attempt other request will succeed after the hold off time is up.
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(startTime + ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+    }
+
+    /**
+     * Verify that an immediate attempt to request ANQP elements from an AP will succeed when
+     * the previous request is failed on sending.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestANQPElementsAfterRequestSendFailure() throws Exception {
+        // Initial request failed to send.
+        long startTime = 0;
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(false);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime);
+        assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        reset(mHandler);
+
+        // Verify that new request is not being held off after previous send failure.
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+    }
+
+    /**
+     * Verify that an immediate attempt to request ANQP elements from an AP will succeed when
+     * the previous request is completed with success.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestANQPElementsAfterRequestSucceeded() throws Exception {
+        // Send the initial request.
+        long startTime = 0;
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        reset(mHandler);
+
+        // Request completed with success. Verify that the key associated with the request
+        // is returned.
+        assertEquals(TEST_ANQP_KEY, mManager.onRequestCompleted(TEST_BSSID, true));
+
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime + 1);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+    }
+
+    /**
+     * Verify that an immediate attempt to request ANQP elements from an AP will fail when
+     * the previous request is completed with failure.  The request will succeed after the
+     * hold off time is up.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestANQPElementsAfterRequestFailed() throws Exception {
+        // Send the initial request.
+        long startTime = 0;
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        reset(mHandler);
+
+        // Request completed with failure.  Verify that the key associated with the request
+        // is returned
+        assertEquals(TEST_ANQP_KEY, mManager.onRequestCompleted(TEST_BSSID, false));
+
+        // Attempt another request will fail since the hold off time is not up yet.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime + 1);
+        assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        verify(mHandler, never()).requestANQP(anyLong(), anyObject());
+
+        // Attempt another request will succeed after the hold off time is up.
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(startTime + ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+    }
+
+    /**
+     * Verify the hold off time for each unanswered query, and that it will stay the same after
+     * reaching the max hold off count {@link ANQPRequestManager#MAX_HOLDOFF_COUNT}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void requestANQPElementsWithMaxRetries() throws Exception {
+        long currentTime = 0;
+
+        // Initial request.
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        reset(mHandler);
+
+        // Sending the request with the hold off time based on the current hold off count.
+        for (int i = 0; i <= ANQPRequestManager.MAX_HOLDOFF_COUNT; i++) {
+            long currentHoldOffTime = ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS * (1 << i);
+            currentTime += (currentHoldOffTime - 1);
+
+            // Request will fail before the hold off time is up.
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime);
+            assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+            verify(mHandler, never()).requestANQP(anyLong(), anyObject());
+
+            // Request will succeed when the hold off time is up.
+            currentTime += 1;
+            when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime);
+            assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+            reset(mHandler);
+        }
+
+        // Verify that the hold off time is max out at the maximum hold off count.
+        currentTime += (ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS
+                * (1 << ANQPRequestManager.MAX_HOLDOFF_COUNT) - 1);
+
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime);
+        assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        verify(mHandler, never()).requestANQP(anyLong(), anyObject());
+
+        currentTime += 1;
+        when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime);
+        assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_ANQP_KEY, false, false));
+        reset(mHandler);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/AnqpCacheTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/AnqpCacheTest.java
new file mode 100644
index 0000000..8c0d982
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/AnqpCacheTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.hotspot2.ANQPData;
+import com.android.server.wifi.hotspot2.AnqpCache;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.AnqpCache}.
+ *
+ * TODO(b/33000864): add more test once the ANQP elements cleanup are completed, which will
+ * allow easy construction of ANQP elements for testing.
+ */
+@SmallTest
+public class AnqpCacheTest {
+    private static final ANQPNetworkKey ENTRY_KEY = new ANQPNetworkKey("test", 0L, 0L, 1);
+
+    @Mock Clock mClock;
+    AnqpCache mCache;
+
+    /**
+     * Sets up test.
+     */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        // Returning the initial timestamp.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        mCache = new AnqpCache(mClock);
+    }
+
+    /**
+     * Verify expectation for addEntry and getEntry.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addAndGetEntry() throws Exception {
+        mCache.addEntry(ENTRY_KEY, null);
+        ANQPData data = mCache.getEntry(ENTRY_KEY);
+        assertNotNull(data);
+        assertTrue(data.getElements().isEmpty());
+    }
+
+    /**
+     * Verify that getting a non-existing entry will return a null.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getNonExistEntry() throws Exception {
+        assertNull(mCache.getEntry(ENTRY_KEY));
+    }
+
+    /**
+     * Verify the expectation for the sweep function (expired entries will be removed).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void sweepRemoveExpiredEntry() throws Exception {
+        mCache.addEntry(ENTRY_KEY, null);
+
+        // Sweep the cache when the entry is not expired.
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(AnqpCache.CACHE_SWEEP_INTERVAL_MILLISECONDS);
+        mCache.sweep();
+        assertNotNull(mCache.getEntry(ENTRY_KEY));
+
+        // Sweep the cache when the entry is expired.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(ANQPData.DATA_LIFETIME_MILLISECONDS);
+        mCache.sweep();
+        assertNull(mCache.getEntry(ENTRY_KEY));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/DomainMatcherTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/DomainMatcherTest.java
new file mode 100644
index 0000000..c015921
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/DomainMatcherTest.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.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.DomainMatcher}.
+ */
+@SmallTest
+public class DomainMatcherTest {
+    private static final String PRIMARY_DOMAIN = "google.com";
+    private static final String SECONDARY_DOMAIN1 = "android.com";
+    private static final String SECONDARY_DOMAIN2 = "testing.test.com";
+
+    /**
+     * Test data for isSubDomain function.
+     */
+    private static final Map<String, Integer> TEST_DOMAIN_MAP = new HashMap<>();
+    static {
+        TEST_DOMAIN_MAP.put("", DomainMatcher.MATCH_NONE);
+        TEST_DOMAIN_MAP.put("com", DomainMatcher.MATCH_NONE);
+        TEST_DOMAIN_MAP.put("test.com", DomainMatcher.MATCH_NONE);
+        TEST_DOMAIN_MAP.put("google.com", DomainMatcher.MATCH_PRIMARY);
+        TEST_DOMAIN_MAP.put("test.google.com", DomainMatcher.MATCH_PRIMARY);
+        TEST_DOMAIN_MAP.put("android.com", DomainMatcher.MATCH_SECONDARY);
+        TEST_DOMAIN_MAP.put("test.android.com", DomainMatcher.MATCH_SECONDARY);
+        TEST_DOMAIN_MAP.put("testing.test.com", DomainMatcher.MATCH_SECONDARY);
+        TEST_DOMAIN_MAP.put("adbcd.testing.test.com", DomainMatcher.MATCH_SECONDARY);
+    }
+
+    /**
+     * Test data for arg2SubdomainOfArg1 function.
+     */
+    private static final Map<Pair<String, String>, Boolean> TEST_ARG_DOMAIN_MAP = new HashMap<>();
+    static {
+        TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test.com", "abc.test.com"), true);
+        TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test.com", "ad.abc.test.com"), true);
+        TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("com", "test.com"), true);
+        TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("abc.test.com", "test.com"), false);
+        TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test1.com", "test.com"), false);
+        TEST_ARG_DOMAIN_MAP.put(new Pair<String, String>("test.com", "com"), false);
+    }
+
+    /**
+     * Verify that creating a matcher with empty domains doesn't cause any exceptions.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void createMatcherWithEmptyDomains() throws Exception {
+        DomainMatcher domainMatcher = new DomainMatcher(null, null);
+        assertEquals(DomainMatcher.MATCH_NONE, domainMatcher.isSubDomain("google.com"));
+    }
+
+    /**
+     * Verify that matching a null domain doesn't cause any exceptions.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNullDomain() throws Exception {
+        DomainMatcher domainMatcher = new DomainMatcher(PRIMARY_DOMAIN,
+                Arrays.asList(SECONDARY_DOMAIN1, SECONDARY_DOMAIN2));
+        assertEquals(DomainMatcher.MATCH_NONE, domainMatcher.isSubDomain(null));
+    }
+
+    /**
+     * Verify the domain matching expectations based on the predefined {@link #TEST_DOMAIN_MAP}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchTestDomains() throws Exception {
+        DomainMatcher domainMatcher = new DomainMatcher(PRIMARY_DOMAIN,
+                Arrays.asList(SECONDARY_DOMAIN1, SECONDARY_DOMAIN2));
+        for (Map.Entry<String, Integer> entry : TEST_DOMAIN_MAP.entrySet()) {
+            assertEquals(entry.getValue().intValue(), domainMatcher.isSubDomain(entry.getKey()));
+        }
+    }
+
+    /**
+     * Verify that the correct match status is returned when a domain matches both primary
+     * and secondary domain (primary domain have precedence over secondary).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchDomainWithBothPrimaryAndSecondary() throws Exception {
+        DomainMatcher domainMatcher = new DomainMatcher(PRIMARY_DOMAIN,
+                Arrays.asList(PRIMARY_DOMAIN));
+        assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain(PRIMARY_DOMAIN));
+    }
+
+    /**
+     * Verify domain matching expectation when the secondary domain is a sub-domain of the
+     * primary domain.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchDomainWhenSecondaryIsSubdomainOfPrimary() throws Exception {
+        DomainMatcher domainMatcher = new DomainMatcher("google.com",
+                Arrays.asList("test.google.com"));
+        assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("google.com"));
+        assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("test.google.com"));
+        assertEquals(DomainMatcher.MATCH_PRIMARY,
+                domainMatcher.isSubDomain("abcd.test.google.com"));
+    }
+
+    /**
+     * Verify domain matching expectations when the secondary domain is a sub-domain of the
+     * primary domain.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchDomainWhenPrimaryIsSubdomainOfSecondary() throws Exception {
+        DomainMatcher domainMatcher = new DomainMatcher("test.google.com",
+                Arrays.asList("google.com"));
+        assertEquals(DomainMatcher.MATCH_SECONDARY, domainMatcher.isSubDomain("google.com"));
+        assertEquals(DomainMatcher.MATCH_SECONDARY, domainMatcher.isSubDomain("test2.google.com"));
+        assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("test.google.com"));
+        assertEquals(DomainMatcher.MATCH_PRIMARY,
+                domainMatcher.isSubDomain("adcd.test.google.com"));
+    }
+
+    /**
+     * Verify domain matching expectations when the domain names contained empty label (domain
+     * name that contained "..").
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchDomainWithEmptyLabel() throws Exception {
+        DomainMatcher domainMatcher = new DomainMatcher("test.google..com",
+                Arrays.asList("google..com"));
+        assertEquals(DomainMatcher.MATCH_PRIMARY, domainMatcher.isSubDomain("test.google..com"));
+        assertEquals(DomainMatcher.MATCH_SECONDARY, domainMatcher.isSubDomain("google..com"));
+    }
+
+    /**
+     * Verify domain matching expectation for arg2SubdomainOfArg1 based on predefined
+     * {@link #TEST_ARG_DOMAIN_MAP}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyArg2SubdomainOfArg1() throws Exception {
+        for (Map.Entry<Pair<String, String>, Boolean> entry : TEST_ARG_DOMAIN_MAP.entrySet()) {
+            assertEquals(entry.getValue().booleanValue(),
+                    DomainMatcher.arg2SubdomainOfArg1(entry.getKey().first, entry.getKey().second));
+        }
+    }
+
+    /**
+     * Verify that arg2SubdomainOfArg1 works as expected when pass in null domains.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void arg2SubdomainOfArg1WithNullDomain() throws Exception {
+        assertFalse(DomainMatcher.arg2SubdomainOfArg1(null, "test.com"));
+        assertFalse(DomainMatcher.arg2SubdomainOfArg1("test.com", null));
+        assertFalse(DomainMatcher.arg2SubdomainOfArg1(null, null));
+    }
+
+    /**
+     * Verify that arg2SubdomainOfArg1 works as expected when domain contains empty label.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void arg2SubdomainOfArg1WithEmptyLabel() throws Exception {
+        assertTrue(DomainMatcher.arg2SubdomainOfArg1("test..com", "adsf.test..com"));
+    }
+
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/LegacyPasspointConfigParserTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/LegacyPasspointConfigParserTest.java
new file mode 100644
index 0000000..10ebceb
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/LegacyPasspointConfigParserTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import static org.junit.Assert.*;
+
+import android.os.FileUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.LegacyPasspointConfigParser}.
+ */
+@SmallTest
+public class LegacyPasspointConfigParserTest {
+    private static final String TEST_CONFIG =
+            "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
+                    + "8:MgmtTree+\n"
+                    + "17:PerProviderSubscription+\n"
+                    + "4:r1i1+\n"
+                    + "6:HomeSP+\n"
+                    + "c:FriendlyName=12:Test Provider 1™\n"
+                    + "4:FQDN=9:test1.net\n"
+                    + "13:RoamingConsortiumOI=9:1234,5678\n"
+                    + ".\n"
+                    + "a:Credential+\n"
+                    + "10:UsernamePassword+\n"
+                    + "8:Username=5:user1\n"
+                    + "8:Password=5:pass1\n"
+                    + "\n"
+                    + "9:EAPMethod+\n"
+                    + "7:EAPType=2:21\n"
+                    + "b:InnerMethod=3:PAP\n"
+                    + ".\n"
+                    + ".\n"
+                    + "5:Realm=9:test1.com\n"
+                    + ".\n"
+                    + ".\n"
+                    + ".\n"
+                    + "17:PerProviderSubscription+\n"
+                    + "4:r1i2+\n"
+                    + "6:HomeSP+\n"
+                    + "c:FriendlyName=f:Test Provider 2\n"
+                    + "4:FQDN=9:test2.net\n"
+                    + ".\n"
+                    + "a:Credential+\n"
+                    + "3:SIM+\n"
+                    + "4:IMSI=4:1234\n"
+                    + "7:EAPType=2:18\n"
+                    + ".\n"
+                    + "5:Realm=9:test2.com\n"
+                    + ".\n"
+                    + ".\n"
+                    + ".\n"
+                    + ".\n";
+
+    /**
+     * Helper function for generating {@link LegacyPasspointConfig} objects based on the predefined
+     * test configuration string {@link #TEST_CONFIG}
+     *
+     * @return Map of FQDN to {@link LegacyPasspointConfig}
+     */
+    private Map<String, LegacyPasspointConfig> generateTestConfig() {
+        Map<String, LegacyPasspointConfig> configs = new HashMap<>();
+
+        LegacyPasspointConfig config1 = new LegacyPasspointConfig();
+        config1.mFqdn = "test1.net";
+        config1.mFriendlyName = "Test Provider 1™";
+        config1.mRoamingConsortiumOis = new long[] {0x1234, 0x5678};
+        config1.mRealm = "test1.com";
+        configs.put("test1.net", config1);
+
+        LegacyPasspointConfig config2 = new LegacyPasspointConfig();
+        config2.mFqdn = "test2.net";
+        config2.mFriendlyName = "Test Provider 2";
+        config2.mRealm = "test2.com";
+        config2.mImsi = "1234";
+        configs.put("test2.net", config2);
+
+        return configs;
+    }
+
+    /**
+     * Helper function for parsing configuration data.
+     *
+     * @param data The configuration data to parse
+     * @return Map of FQDN to {@link LegacyPasspointConfig}
+     * @throws Exception
+     */
+    private Map<String, LegacyPasspointConfig> parseConfig(String data) throws Exception {
+        // Write configuration data to file.
+        File configFile = File.createTempFile("LegacyPasspointConfig", "");
+        FileUtils.stringToFile(configFile, data);
+
+        // Parse the configuration file.
+        LegacyPasspointConfigParser parser = new LegacyPasspointConfigParser();
+        Map<String, LegacyPasspointConfig> configMap =
+                parser.parseConfig(configFile.getAbsolutePath());
+
+        configFile.delete();
+        return configMap;
+    }
+
+    /**
+     * Verify that the expected {@link LegacyPasspointConfig} objects are return when parsing
+     * predefined test configuration data {@link #TEST_CONFIG}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseTestConfig() throws Exception {
+        Map<String, LegacyPasspointConfig> parsedConfig = parseConfig(TEST_CONFIG);
+        assertEquals(generateTestConfig(), parsedConfig);
+    }
+
+    /**
+     * Verify that an empty map is return when parsing a configuration containing an empty
+     * configuration (MgmtTree).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseEmptyConfig() throws Exception {
+        String emptyConfig = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
+                + "8:MgmtTree+\n"
+                + ".\n";
+        Map<String, LegacyPasspointConfig> parsedConfig = parseConfig(emptyConfig);
+        assertTrue(parsedConfig.isEmpty());
+    }
+
+    /**
+     * Verify that an empty map is return when parsing an empty configuration data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseEmptyData() throws Exception {
+        Map<String, LegacyPasspointConfig> parsedConfig = parseConfig("");
+        assertTrue(parsedConfig.isEmpty());
+    }
+
+    /**
+     * Verify that an IOException is thrown when parsing a configuration containing an unknown
+     * root name.  The expected root name is "MgmtTree".
+     *
+     * @throws Exception
+     */
+    @Test(expected = IOException.class)
+    public void parseConfigWithUnknownRootName() throws Exception {
+        String config = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
+                + "8:TestTest+\n"
+                + ".\n";
+        parseConfig(config);
+    }
+
+    /**
+     * Verify that an IOException is thrown when parsing a configuration containing a line with
+     * mismatched string length for the name.
+     *
+     * @throws Exception
+     */
+    @Test(expected = IOException.class)
+    public void parseConfigWithMismatchedStringLengthInName() throws Exception {
+        String config = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
+                + "9:MgmtTree+\n"
+                + ".\n";
+        parseConfig(config);
+    }
+
+    /**
+     * Verify that an IOException is thrown when parsing a configuration containing a line with
+     * mismatched string length for the value.
+     *
+     * @throws Exception
+     */
+    @Test(expected = IOException.class)
+    public void parseConfigWithMismatchedStringLengthInValue() throws Exception {
+        String config = "tree 3:1.2(urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0)\n"
+                + "8:MgmtTree+\n"
+                + "4:test=5:test\n"
+                + ".\n";
+        parseConfig(config);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigStoreDataTest.java
new file mode 100644
index 0000000..8e808ef
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigStoreDataTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.WifiKeyStore;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointConfigStoreData}.
+ */
+@SmallTest
+public class PasspointConfigStoreDataTest {
+    private static final String TEST_CA_CERTIFICATE_ALIAS = "CaCert";
+    private static final String TEST_CLIENT_CERTIFICATE_ALIAS = "ClientCert";
+    private static final String TEST_CLIENT_PRIVATE_KEY_ALIAS = "ClientPrivateKey";
+    private static final long TEST_PROVIDER_ID = 1;
+    private static final int TEST_CREATOR_UID = 1234;
+
+    @Mock WifiKeyStore mKeyStore;
+    @Mock SIMAccessor mSimAccessor;
+    @Mock PasspointConfigStoreData.DataSource mDataSource;
+    PasspointConfigStoreData mConfigStoreData;
+
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mConfigStoreData = new PasspointConfigStoreData(mKeyStore, mSimAccessor, mDataSource);
+    }
+
+    /**
+     * Helper function for generating a {@link PasspointConfiguration} for testing the XML
+     * serialization/deserialization logic.
+     *
+     * @return {@link PasspointConfiguration}
+     * @throws Exception
+     */
+    private PasspointConfiguration createFullPasspointConfiguration() throws Exception {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        byte[] certFingerprint = new byte[32];
+        Arrays.fill(certFingerprint, (byte) 0x1f);
+
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setUpdateIdentifier(12);
+        config.setCredentialPriority(99);
+
+        // AAA Server trust root.
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        trustRootCertList.put("server1.trust.root.com", certFingerprint);
+        config.setTrustRootCertList(trustRootCertList);
+
+        // Subscription update.
+        UpdateParameter subscriptionUpdate = new UpdateParameter();
+        subscriptionUpdate.setUpdateIntervalInMinutes(120);
+        subscriptionUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_SSP);
+        subscriptionUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER);
+        subscriptionUpdate.setServerUri("subscription.update.com");
+        subscriptionUpdate.setUsername("subscriptionUser");
+        subscriptionUpdate.setBase64EncodedPassword("subscriptionPass");
+        subscriptionUpdate.setTrustRootCertUrl("subscription.update.cert.com");
+        subscriptionUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+        config.setSubscriptionUpdate(subscriptionUpdate);
+
+        // Subscription parameters.
+        config.setSubscriptionCreationTimeInMillis(format.parse("2016-02-01T10:00:00Z").getTime());
+        config.setSubscriptionExpirationTimeInMillis(
+                format.parse("2016-03-01T10:00:00Z").getTime());
+        config.setSubscriptionType("Gold");
+        config.setUsageLimitDataLimit(921890);
+        config.setUsageLimitStartTimeInMillis(format.parse("2016-12-01T10:00:00Z").getTime());
+        config.setUsageLimitTimeLimitInMinutes(120);
+        config.setUsageLimitUsageTimePeriodInMinutes(99910);
+
+        // HomeSP configuration.
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFriendlyName("Century House");
+        homeSp.setFqdn("mi6.co.uk");
+        homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+        homeSp.setIconUrl("icon.test.com");
+        Map<String, Long> homeNetworkIds = new HashMap<>();
+        homeNetworkIds.put("TestSSID", 0x12345678L);
+        homeNetworkIds.put("NullHESSID", null);
+        homeSp.setHomeNetworkIds(homeNetworkIds);
+        homeSp.setMatchAllOis(new long[] {0x11223344});
+        homeSp.setMatchAnyOis(new long[] {0x55667788});
+        homeSp.setOtherHomePartners(new String[] {"other.fqdn.com"});
+        config.setHomeSp(homeSp);
+
+        // Credential configuration.
+        Credential credential = new Credential();
+        credential.setCreationTimeInMillis(format.parse("2016-01-01T10:00:00Z").getTime());
+        credential.setExpirationTimeInMillis(format.parse("2016-02-01T10:00:00Z").getTime());
+        credential.setRealm("shaken.stirred.com");
+        credential.setCheckAaaServerCertStatus(true);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername("james");
+        userCredential.setPassword("Ym9uZDAwNw==");
+        userCredential.setMachineManaged(true);
+        userCredential.setSoftTokenApp("TestApp");
+        userCredential.setAbleToShare(true);
+        userCredential.setEapType(21);
+        userCredential.setNonEapInnerMethod("MS-CHAP-V2");
+        credential.setUserCredential(userCredential);
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertType("x509v3");
+        certCredential.setCertSha256Fingerprint(certFingerprint);
+        credential.setCertCredential(certCredential);
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi("imsi");
+        simCredential.setEapType(24);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+
+        // Policy configuration.
+        Policy policy = new Policy();
+        List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.setFqdn("test1.fqdn.com");
+        partner1.setFqdnExactMatch(true);
+        partner1.setPriority(127);
+        partner1.setCountries("us,fr");
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.setFqdn("test2.fqdn.com");
+        partner2.setFqdnExactMatch(false);
+        partner2.setPriority(200);
+        partner2.setCountries("*");
+        preferredRoamingPartnerList.add(partner1);
+        preferredRoamingPartnerList.add(partner2);
+        policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
+        policy.setMinHomeDownlinkBandwidth(23412);
+        policy.setMinHomeUplinkBandwidth(9823);
+        policy.setMinRoamingDownlinkBandwidth(9271);
+        policy.setMinRoamingUplinkBandwidth(2315);
+        policy.setExcludedSsidList(new String[] {"excludeSSID"});
+        Map<Integer, String> requiredProtoPortMap = new HashMap<>();
+        requiredProtoPortMap.put(12, "34,92,234");
+        policy.setRequiredProtoPortMap(requiredProtoPortMap);
+        policy.setMaximumBssLoadValue(23);
+        UpdateParameter policyUpdate = new UpdateParameter();
+        policyUpdate.setUpdateIntervalInMinutes(120);
+        policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+        policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+        policyUpdate.setServerUri("policy.update.com");
+        policyUpdate.setUsername("updateUser");
+        policyUpdate.setBase64EncodedPassword("updatePass");
+        policyUpdate.setTrustRootCertUrl("update.cert.com");
+        policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+        policy.setPolicyUpdate(policyUpdate);
+        config.setPolicy(policy);
+        return config;
+    }
+
+    /**
+     * Helper function for serializing store data to a XML block.
+     *
+     * @param share Flag indicating share or user data
+     * @return byte[]
+     * @throws Exception
+     */
+    private byte[] serializeData(boolean share) throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mConfigStoreData.serializeData(out, share);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for deserializing store data from a XML block.
+     *
+     * @param data The XML block data bytes
+     * @param share Flag indicating share or user data
+     * @throws Exception
+     */
+    private void deserializeData(byte[] data, boolean share) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mConfigStoreData.deserializeData(in, in.getDepth(), share);
+    }
+
+    /**
+     * Verify that the serialization and deserialization of user store data works as expected.
+     * The data used for serialization matches the result of the deserialization.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeAndDeserializeUserStoreData() throws Exception {
+        // Setup expected data.
+        List<PasspointProvider> providerList = new ArrayList<>();
+        providerList.add(new PasspointProvider(createFullPasspointConfiguration(),
+                mKeyStore, mSimAccessor, TEST_PROVIDER_ID, TEST_CREATOR_UID,
+                TEST_CA_CERTIFICATE_ALIAS, TEST_CLIENT_CERTIFICATE_ALIAS,
+                TEST_CLIENT_PRIVATE_KEY_ALIAS));
+
+        // Serialize data for user store.
+        when(mDataSource.getProviders()).thenReturn(providerList);
+        byte[] data = serializeData(false);
+
+        // Deserialize data for user store and verify the content.
+        ArgumentCaptor<ArrayList> providersCaptor = ArgumentCaptor.forClass(ArrayList.class);
+        deserializeData(data, false);
+        verify(mDataSource).setProviders(providersCaptor.capture());
+        assertEquals(providerList, providersCaptor.getValue());
+    }
+
+    /**
+     * Verify that the serialization and deserialization of share store data works as expected.
+     * The data used for serialization matches the result of the deserialization.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeAndDeserializeShareStoreData() throws Exception {
+        // Setup expected data.
+        long providerIndex = 412;
+
+        // Serialize data for share store.
+        when(mDataSource.getProviderIndex()).thenReturn(providerIndex);
+        byte[] data = serializeData(true);
+
+        // Deserialize data for share store and verify the content.
+        deserializeData(data, true);
+        verify(mDataSource).setProviderIndex(providerIndex);
+    }
+
+    /**
+     * Verify that deserialization of an empty user store data doesn't cause any exception and
+     * the corresponding data source should not be updated.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeEmptyUserStoreData() throws Exception {
+        deserializeData(new byte[0], false);
+        verify(mDataSource, never()).setProviders(any(ArrayList.class));
+    }
+
+    /**
+     * Verify that deserialization of an empty share store data doesn't cause any exception
+     * and the corresponding data source should not be updated.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeEmptyShareStoreData() throws Exception {
+        deserializeData(new byte[0], true);
+        verify(mDataSource, never()).setProviderIndex(anyLong());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java
new file mode 100644
index 0000000..2a6c881
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointEventHandlerTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointEventHandler}.
+ * TODO(zqiu): add more test when switch over to use wificond.
+ */
+@SmallTest
+public class PasspointEventHandlerTest {
+
+    private static final String TAG = "PasspointEventHandlerTest";
+
+    private static final long BSSID = 0x112233445566L;
+    private static final String BSSID_STR = "11:22:33:44:55:66";
+    private static final String ICON_FILENAME = "icon.test";
+
+    @Mock WifiNative mWifiNative;
+    @Mock PasspointEventHandler.Callbacks mCallbacks;
+    PasspointEventHandler mHandler;
+
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        mHandler = new PasspointEventHandler(mWifiNative, mCallbacks);
+    }
+
+    /**
+     * Test for requesting Hotspot 2.0 R1 ANQP element.
+     */
+    @Test
+    public void requestR1AnqpElement() {
+        List<Constants.ANQPElementType> elementToRequest =
+                Arrays.asList(Constants.ANQPElementType.ANQPRoamingConsortium);
+        HashSet<Integer> expAnqpIds =
+                new HashSet<>(Arrays.asList(Constants.getANQPElementID(
+                        Constants.ANQPElementType.ANQPRoamingConsortium)));
+        HashSet<Integer> expHs20Subtypes = new HashSet<>();
+
+        // wpa_supplicant succeeded the request.
+        when(mWifiNative.requestAnqp(eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+                .thenReturn(true);
+        assertTrue(mHandler.requestANQP(BSSID, elementToRequest));
+
+        // wpa_supplicant failed the request.
+        when(mWifiNative.requestAnqp(eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+                .thenReturn(false);
+        assertFalse(mHandler.requestANQP(BSSID, elementToRequest));
+    }
+
+    /**
+     * Test for requesting Hotspot 2.0 R2 ANQP element.
+     */
+    @Test
+    public void requestR2AnqpElement() {
+        List<Constants.ANQPElementType> elementToRequest =
+                Arrays.asList(Constants.ANQPElementType.HSFriendlyName);
+        HashSet<Integer> expAnqpIds = new HashSet<>();
+        HashSet<Integer> expHs20Subtypes =
+                new HashSet<>(Arrays.asList(Constants.getHS20ElementID(
+                        Constants.ANQPElementType.HSFriendlyName)));
+
+        // wpa_supplicant succeeded the request.
+        when(mWifiNative.requestAnqp(eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+                .thenReturn(true);
+        assertTrue(mHandler.requestANQP(BSSID, elementToRequest));
+
+        // wpa_supplicant failed the request.
+        when(mWifiNative.requestAnqp(eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+                .thenReturn(false);
+        assertFalse(mHandler.requestANQP(BSSID, elementToRequest));
+    }
+
+    /**
+     * Test for requesting both Hotspot 2.0 R1 and R2 ANQP elements.
+     */
+    @Test
+    public void requestMixAnqpElements() {
+        List<Constants.ANQPElementType> elementToRequest =
+                Arrays.asList(Constants.ANQPElementType.ANQPRoamingConsortium,
+                              Constants.ANQPElementType.HSFriendlyName);
+        HashSet<Integer> expAnqpIds =
+                new HashSet<>(Arrays.asList(Constants.getANQPElementID(
+                        Constants.ANQPElementType.ANQPRoamingConsortium)));
+        HashSet<Integer> expHs20Subtypes =
+                new HashSet<>(Arrays.asList(Constants.getHS20ElementID(
+                        Constants.ANQPElementType.HSFriendlyName)));
+
+        // wpa_supplicant succeeded the request.
+        when(mWifiNative.requestAnqp(eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+                .thenReturn(true);
+        assertTrue(mHandler.requestANQP(BSSID, elementToRequest));
+
+        // wpa_supplicant failed the request.
+        when(mWifiNative.requestAnqp(eq(BSSID_STR), eq(expAnqpIds), eq(expHs20Subtypes)))
+                .thenReturn(false);
+        assertFalse(mHandler.requestANQP(BSSID, elementToRequest));
+    }
+
+    /**
+     * Test for requesting both Hotspot 2.0 R2 icon file.
+     */
+    @Test
+    public void requestIconFile() {
+        // wpa_supplicant succeeded the request.
+        when(mWifiNative.requestIcon(eq(BSSID_STR), eq(ICON_FILENAME))).thenReturn(true);
+        assertTrue(mHandler.requestIcon(BSSID, ICON_FILENAME));
+
+        // wpa_supplicant failed the request.
+        when(mWifiNative.requestIcon(eq(BSSID_STR), eq(ICON_FILENAME))).thenReturn(false);
+        assertFalse(mHandler.requestIcon(BSSID, ICON_FILENAME));
+    }
+
+    /**
+     * Test for ANQP request completed with error.
+     */
+    @Test
+    public void anqpRequestCompletedWithError() {
+        mHandler.notifyANQPDone(new AnqpEvent(BSSID, null));
+        verify(mCallbacks).onANQPResponse(BSSID, null);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
new file mode 100644
index 0000000..70ae354
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -0,0 +1,1086 @@
+/*
+ * 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.hotspot2;
+
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_DEAUTH_IMMINENT;
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_ICON;
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_OSU_PROVIDERS_LIST;
+import static android.net.wifi.WifiManager.ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION;
+import static android.net.wifi.WifiManager.EXTRA_ANQP_ELEMENT_DATA;
+import static android.net.wifi.WifiManager.EXTRA_BSSID_LONG;
+import static android.net.wifi.WifiManager.EXTRA_DELAY;
+import static android.net.wifi.WifiManager.EXTRA_ESS;
+import static android.net.wifi.WifiManager.EXTRA_FILENAME;
+import static android.net.wifi.WifiManager.EXTRA_ICON;
+import static android.net.wifi.WifiManager.EXTRA_SUBSCRIPTION_REMEDIATION_METHOD;
+import static android.net.wifi.WifiManager.EXTRA_URL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyMap;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.wifi.EAPConstants;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+import android.util.Pair;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.FakeKeys;
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.WifiConfigManager;
+import com.android.server.wifi.WifiConfigStore;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
+import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
+import com.android.server.wifi.hotspot2.anqp.RawByteElement;
+import com.android.server.wifi.util.ScanResultUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointManager}.
+ */
+@SmallTest
+public class PasspointManagerTest {
+    private static final long BSSID = 0x112233445566L;
+    private static final String ICON_FILENAME = "test";
+    private static final String TEST_FQDN = "test1.test.com";
+    private static final String TEST_FRIENDLY_NAME = "friendly name";
+    private static final String TEST_REALM = "realm.test.com";
+    private static final String TEST_IMSI = "1234*";
+    private static final IMSIParameter TEST_IMSI_PARAM = IMSIParameter.build(TEST_IMSI);
+
+    private static final String TEST_SSID = "TestSSID";
+    private static final long TEST_BSSID = 0x112233445566L;
+    private static final String TEST_BSSID_STRING = "11:22:33:44:55:66";
+    private static final long TEST_HESSID = 0x5678L;
+    private static final int TEST_ANQP_DOMAIN_ID = 0;
+    private static final ANQPNetworkKey TEST_ANQP_KEY = ANQPNetworkKey.buildKey(
+            TEST_SSID, TEST_BSSID, TEST_HESSID, TEST_ANQP_DOMAIN_ID);
+    private static final int TEST_CREATOR_UID = 1234;
+
+    @Mock Context mContext;
+    @Mock WifiNative mWifiNative;
+    @Mock WifiKeyStore mWifiKeyStore;
+    @Mock Clock mClock;
+    @Mock SIMAccessor mSimAccessor;
+    @Mock PasspointObjectFactory mObjectFactory;
+    @Mock PasspointEventHandler.Callbacks mCallbacks;
+    @Mock AnqpCache mAnqpCache;
+    @Mock ANQPRequestManager mAnqpRequestManager;
+    @Mock CertificateVerifier mCertVerifier;
+    @Mock WifiConfigManager mWifiConfigManager;
+    @Mock WifiConfigStore mWifiConfigStore;
+    @Mock PasspointConfigStoreData.DataSource mDataSource;
+    PasspointManager mManager;
+
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        when(mObjectFactory.makeAnqpCache(mClock)).thenReturn(mAnqpCache);
+        when(mObjectFactory.makeANQPRequestManager(any(), eq(mClock)))
+                .thenReturn(mAnqpRequestManager);
+        when(mObjectFactory.makeCertificateVerifier()).thenReturn(mCertVerifier);
+        mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
+                mSimAccessor, mObjectFactory, mWifiConfigManager, mWifiConfigStore);
+        ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
+                ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
+        verify(mObjectFactory).makePasspointEventHandler(any(WifiNative.class),
+                                                         callbacks.capture());
+        ArgumentCaptor<PasspointConfigStoreData.DataSource> dataSource =
+                ArgumentCaptor.forClass(PasspointConfigStoreData.DataSource.class);
+        verify(mObjectFactory).makePasspointConfigStoreData(
+                any(WifiKeyStore.class), any(SIMAccessor.class), dataSource.capture());
+        mCallbacks = callbacks.getValue();
+        mDataSource = dataSource.getValue();
+    }
+
+    /**
+     * Verify {@link WifiManager#ACTION_PASSPOINT_ICON} broadcast intent.
+     * @param bssid BSSID of the AP
+     * @param fileName Name of the icon file
+     * @param data icon data byte array
+     */
+    private void verifyIconIntent(long bssid, String fileName, byte[] data) {
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL),
+                eq(android.Manifest.permission.ACCESS_WIFI_STATE));
+        assertEquals(ACTION_PASSPOINT_ICON, intent.getValue().getAction());
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_BSSID_LONG));
+        assertEquals(bssid, intent.getValue().getExtras().getLong(EXTRA_BSSID_LONG));
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_FILENAME));
+        assertEquals(fileName, intent.getValue().getExtras().getString(EXTRA_FILENAME));
+        if (data != null) {
+            assertTrue(intent.getValue().getExtras().containsKey(EXTRA_ICON));
+            Icon icon = (Icon) intent.getValue().getExtras().getParcelable(EXTRA_ICON);
+            assertTrue(Arrays.equals(data, icon.getDataBytes()));
+        } else {
+            assertFalse(intent.getValue().getExtras().containsKey(EXTRA_ICON));
+        }
+    }
+
+    /**
+     * Verify that the given Passpoint configuration matches the one that's added to
+     * the PasspointManager.
+     *
+     * @param expectedConfig The expected installed Passpoint configuration
+     */
+    private void verifyInstalledConfig(PasspointConfiguration expectedConfig) {
+        List<PasspointConfiguration> installedConfigs = mManager.getProviderConfigs();
+        assertEquals(1, installedConfigs.size());
+        assertEquals(expectedConfig, installedConfigs.get(0));
+    }
+
+    /**
+     * Create a mock PasspointProvider with default expectations.
+     *
+     * @param config The configuration associated with the provider
+     * @return {@link com.android.server.wifi.hotspot2.PasspointProvider}
+     */
+    private PasspointProvider createMockProvider(PasspointConfiguration config) {
+        PasspointProvider provider = mock(PasspointProvider.class);
+        when(provider.installCertsAndKeys()).thenReturn(true);
+        when(provider.getConfig()).thenReturn(config);
+        return provider;
+    }
+
+    /**
+     * Helper function for creating a test configuration with user credential.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private PasspointConfiguration createTestConfigWithUserCredential() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(TEST_FQDN);
+        homeSp.setFriendlyName(TEST_FRIENDLY_NAME);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(TEST_REALM);
+        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername("username");
+        userCredential.setPassword("password");
+        userCredential.setEapType(EAPConstants.EAP_TTLS);
+        userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAP);
+        credential.setUserCredential(userCredential);
+        config.setCredential(credential);
+        return config;
+    }
+
+    /**
+     * Helper function for creating a test configuration with SIM credential.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private PasspointConfiguration createTestConfigWithSimCredential() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(TEST_FQDN);
+        homeSp.setFriendlyName(TEST_FRIENDLY_NAME);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(TEST_REALM);
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi(TEST_IMSI);
+        simCredential.setEapType(EAPConstants.EAP_SIM);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+        return config;
+    }
+
+    /**
+     * Helper function for adding a test provider to the manager.  Return the mock
+     * provider that's added to the manager.
+     *
+     * @return {@link PasspointProvider}
+     */
+    private PasspointProvider addTestProvider() {
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointProvider provider = createMockProvider(config);
+        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
+        assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+
+        return provider;
+    }
+
+    /**
+     * Helper function for creating a ScanResult for testing.
+     *
+     * @return {@link ScanResult}
+     */
+    private ScanResult createTestScanResult() {
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = TEST_SSID;
+        scanResult.BSSID = TEST_BSSID_STRING;
+        scanResult.hessid = TEST_HESSID;
+        return scanResult;
+    }
+
+    /**
+     * Verify that the ANQP elements will be added to the ANQP cache on receiving a successful
+     * response.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void anqpResponseSuccess() throws Exception {
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                new DomainNameElement(Arrays.asList(new String[] {"test.com"})));
+
+        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(TEST_ANQP_KEY);
+        mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
+        verify(mAnqpCache).addEntry(TEST_ANQP_KEY, anqpElementMap);
+        verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
+                any(String.class));
+    }
+
+    /**
+     * Verify that the ANQP elements will be added to the AQNP cache and an
+     * {@link WifiManager#ACTION_PASSPOINT_OSU_PROVIDER_LIST} intent will be broadcasted when
+     * receiving an ANQP response containing OSU Providers element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void anqpResponseWithOSUProviders() throws Exception {
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        byte[] testData = new byte[] {0x12, 0x34, 0x56, 0x78};
+        anqpElementMap.put(ANQPElementType.HSOSUProviders,
+                new RawByteElement(ANQPElementType.HSOSUProviders, testData));
+
+        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(TEST_ANQP_KEY);
+        mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
+        verify(mAnqpCache).addEntry(TEST_ANQP_KEY, anqpElementMap);
+
+        // Verify the broadcast intent for OSU providers.
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL),
+                eq(android.Manifest.permission.ACCESS_WIFI_STATE));
+        assertEquals(ACTION_PASSPOINT_OSU_PROVIDERS_LIST, intent.getValue().getAction());
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_BSSID_LONG));
+        assertEquals(TEST_BSSID, intent.getValue().getExtras().getLong(EXTRA_BSSID_LONG));
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_ANQP_ELEMENT_DATA));
+        assertTrue(Arrays.equals(testData,
+                intent.getValue().getExtras().getByteArray(EXTRA_ANQP_ELEMENT_DATA)));
+    }
+
+    /**
+     * Verify that no ANQP elements will be added to the ANQP cache on receiving a successful
+     * response for a request that's not sent by us.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void anqpResponseSuccessWithUnknownRequest() throws Exception {
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                new DomainNameElement(Arrays.asList(new String[] {"test.com"})));
+
+        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(null);
+        mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap);
+        verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap());
+    }
+
+    /**
+     * Verify that no ANQP elements will be added to the ANQP cache on receiving a failure response.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void anqpResponseFailure() throws Exception {
+        when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, false)).thenReturn(TEST_ANQP_KEY);
+        mCallbacks.onANQPResponse(TEST_BSSID, null);
+        verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap());
+
+    }
+
+    /**
+     * Validate the broadcast intent when icon file retrieval succeeded.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void iconResponseSuccess() throws Exception {
+        byte[] iconData = new byte[] {0x00, 0x11};
+        mCallbacks.onIconResponse(BSSID, ICON_FILENAME, iconData);
+        verifyIconIntent(BSSID, ICON_FILENAME, iconData);
+    }
+
+    /**
+     * Validate the broadcast intent when icon file retrieval failed.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void iconResponseFailure() throws Exception {
+        mCallbacks.onIconResponse(BSSID, ICON_FILENAME, null);
+        verifyIconIntent(BSSID, ICON_FILENAME, null);
+    }
+
+    /**
+     * Validate the broadcast intent {@link WifiManager#ACTION_PASSPOINT_DEAUTH_IMMINENT} when
+     * Deauth Imminent WNM frame is received.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void onDeauthImminentReceived() throws Exception {
+        String reasonUrl = "test.com";
+        int delay = 123;
+        boolean ess = true;
+
+        mCallbacks.onWnmFrameReceived(new WnmData(BSSID, reasonUrl, ess, delay));
+        // Verify the broadcast intent.
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL),
+                eq(android.Manifest.permission.ACCESS_WIFI_STATE));
+        assertEquals(ACTION_PASSPOINT_DEAUTH_IMMINENT, intent.getValue().getAction());
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_BSSID_LONG));
+        assertEquals(BSSID, intent.getValue().getExtras().getLong(EXTRA_BSSID_LONG));
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_ESS));
+        assertEquals(ess, intent.getValue().getExtras().getBoolean(EXTRA_ESS));
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_DELAY));
+        assertEquals(delay, intent.getValue().getExtras().getInt(EXTRA_DELAY));
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_URL));
+        assertEquals(reasonUrl, intent.getValue().getExtras().getString(EXTRA_URL));
+    }
+
+    /**
+     * Validate the broadcast intent {@link WifiManager#ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION}
+     * when Subscription Remediation WNM frame is received.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void onSubscriptionRemediationReceived() throws Exception {
+        int serverMethod = 1;
+        String serverUrl = "testUrl";
+
+        mCallbacks.onWnmFrameReceived(new WnmData(BSSID, serverUrl, serverMethod));
+        // Verify the broadcast intent.
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL),
+                eq(android.Manifest.permission.ACCESS_WIFI_STATE));
+        assertEquals(ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION, intent.getValue().getAction());
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_BSSID_LONG));
+        assertEquals(BSSID, intent.getValue().getExtras().getLong(EXTRA_BSSID_LONG));
+        assertTrue(intent.getValue().getExtras().containsKey(
+                EXTRA_SUBSCRIPTION_REMEDIATION_METHOD));
+        assertEquals(serverMethod, intent.getValue().getExtras().getInt(
+                EXTRA_SUBSCRIPTION_REMEDIATION_METHOD));
+        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_URL));
+        assertEquals(serverUrl, intent.getValue().getExtras().getString(EXTRA_URL));
+    }
+
+    /**
+     * Verify that adding a provider with a null configuration will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderWithNullConfig() throws Exception {
+        assertFalse(mManager.addOrUpdateProvider(null, TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verify that adding a provider with a empty configuration will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderWithEmptyConfig() throws Exception {
+        assertFalse(mManager.addOrUpdateProvider(new PasspointConfiguration(), TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verify taht adding a provider with an invalid credential will fail (using EAP-TLS
+     * for user credential).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderWithInvalidCredential() throws Exception {
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        // EAP-TLS not allowed for user credential.
+        config.getCredential().getUserCredential().setEapType(EAPConstants.EAP_TLS);
+        assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verify that adding a provider with a valid configuration and user credential will succeed.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addRemoveProviderWithValidUserCredential() throws Exception {
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointProvider provider = createMockProvider(config);
+        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
+        assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+        verifyInstalledConfig(config);
+        verify(mWifiConfigManager).saveToStore(true);
+        reset(mWifiConfigManager);
+
+        // Verify content in the data source.
+        List<PasspointProvider> providers = mDataSource.getProviders();
+        assertEquals(1, providers.size());
+        assertEquals(config, providers.get(0).getConfig());
+        // Provider index start with 0, should be 1 after adding a provider.
+        assertEquals(1, mDataSource.getProviderIndex());
+
+        // Remove the provider.
+        assertTrue(mManager.removeProvider(TEST_FQDN));
+        verify(provider).uninstallCertsAndKeys();
+        verify(mWifiConfigManager).saveToStore(true);
+        assertTrue(mManager.getProviderConfigs().isEmpty());
+
+        // Verify content in the data source.
+        assertTrue(mDataSource.getProviders().isEmpty());
+        // Removing a provider should not change the provider index.
+        assertEquals(1, mDataSource.getProviderIndex());
+    }
+
+    /**
+     * Verify that adding a provider with a valid configuration and SIM credential will succeed.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addRemoveProviderWithValidSimCredential() throws Exception {
+        PasspointConfiguration config = createTestConfigWithSimCredential();
+        PasspointProvider provider = createMockProvider(config);
+        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
+        assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+        verifyInstalledConfig(config);
+        verify(mWifiConfigManager).saveToStore(true);
+        reset(mWifiConfigManager);
+
+        // Verify content in the data source.
+        List<PasspointProvider> providers = mDataSource.getProviders();
+        assertEquals(1, providers.size());
+        assertEquals(config, providers.get(0).getConfig());
+        // Provider index start with 0, should be 1 after adding a provider.
+        assertEquals(1, mDataSource.getProviderIndex());
+
+        // Remove the provider.
+        assertTrue(mManager.removeProvider(TEST_FQDN));
+        verify(provider).uninstallCertsAndKeys();
+        verify(mWifiConfigManager).saveToStore(true);
+        assertTrue(mManager.getProviderConfigs().isEmpty());
+
+        // Verify content in the data source.
+        assertTrue(mDataSource.getProviders().isEmpty());
+        // Removing a provider should not change the provider index.
+        assertEquals(1, mDataSource.getProviderIndex());
+    }
+
+    /**
+     * Verify that adding a provider with the same base domain as the existing provider will
+     * succeed, and verify that the existing provider is replaced by the new provider with
+     * the new configuration.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderWithExistingConfig() throws Exception {
+        // Add a provider with the original configuration.
+        PasspointConfiguration origConfig = createTestConfigWithSimCredential();
+        PasspointProvider origProvider = createMockProvider(origConfig);
+        when(mObjectFactory.makePasspointProvider(eq(origConfig), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(origProvider);
+        assertTrue(mManager.addOrUpdateProvider(origConfig, TEST_CREATOR_UID));
+        verifyInstalledConfig(origConfig);
+        verify(mWifiConfigManager).saveToStore(true);
+        reset(mWifiConfigManager);
+
+        // Verify data source content.
+        List<PasspointProvider> origProviders = mDataSource.getProviders();
+        assertEquals(1, origProviders.size());
+        assertEquals(origConfig, origProviders.get(0).getConfig());
+        assertEquals(1, mDataSource.getProviderIndex());
+
+        // Add another provider with the same base domain as the existing provider.
+        // This should replace the existing provider with the new configuration.
+        PasspointConfiguration newConfig = createTestConfigWithUserCredential();
+        PasspointProvider newProvider = createMockProvider(newConfig);
+        when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(newProvider);
+        assertTrue(mManager.addOrUpdateProvider(newConfig, TEST_CREATOR_UID));
+        verifyInstalledConfig(newConfig);
+        verify(mWifiConfigManager).saveToStore(true);
+
+        // Verify data source content.
+        List<PasspointProvider> newProviders = mDataSource.getProviders();
+        assertEquals(1, newProviders.size());
+        assertEquals(newConfig, newProviders.get(0).getConfig());
+        assertEquals(2, mDataSource.getProviderIndex());
+    }
+
+    /**
+     * Verify that adding a provider will fail when failing to install certificates and
+     * key to the keystore.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderOnKeyInstallationFailiure() throws Exception {
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointProvider provider = mock(PasspointProvider.class);
+        when(provider.installCertsAndKeys()).thenReturn(false);
+        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
+        assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verify that adding a provider with an invalid CA certificate will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderWithInvalidCaCert() throws Exception {
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        doThrow(new GeneralSecurityException())
+                .when(mCertVerifier).verifyCaCert(any(X509Certificate.class));
+        assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+    }
+
+    /**
+     * Verify that adding a provider with R2 configuration will not perform CA certificate
+     * verification.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addProviderWithR2Config() throws Exception {
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        config.setUpdateIdentifier(1);
+        PasspointProvider provider = createMockProvider(config);
+        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
+        assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+        verify(mCertVerifier, never()).verifyCaCert(any(X509Certificate.class));
+        verifyInstalledConfig(config);
+    }
+
+    /**
+     * Verify that removing a non-existing provider will fail.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void removeNonExistingProvider() throws Exception {
+        assertFalse(mManager.removeProvider(TEST_FQDN));
+    }
+
+    /**
+     * Verify that a {code null} will be returned when no providers are installed.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchProviderWithNoProvidersInstalled() throws Exception {
+        assertNull(mManager.matchProvider(createTestScanResult()));
+    }
+
+    /**
+     * Verify that a {code null} be returned when ANQP entry doesn't exist in the cache.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchProviderWithAnqpCacheMissed() throws Exception {
+        addTestProvider();
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(null);
+        assertNull(mManager.matchProvider(createTestScanResult()));
+        // Verify that a request for ANQP elements is initiated.
+        verify(mAnqpRequestManager).requestANQPElements(eq(TEST_BSSID), any(ANQPNetworkKey.class),
+                anyBoolean(), anyBoolean());
+    }
+
+    /**
+     * Verify that the expected provider will be returned when a HomeProvider is matched.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchProviderAsHomeProvider() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        ANQPData entry = new ANQPData(mClock, null);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        when(provider.match(anyMap())).thenReturn(PasspointMatch.HomeProvider);
+        Pair<PasspointProvider, PasspointMatch> result =
+                mManager.matchProvider(createTestScanResult());
+        assertEquals(PasspointMatch.HomeProvider, result.second);
+        assertEquals(TEST_FQDN, result.first.getConfig().getHomeSp().getFqdn());
+    }
+
+    /**
+     * Verify that the expected provider will be returned when a RoamingProvider is matched.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchProviderAsRoamingProvider() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        ANQPData entry = new ANQPData(mClock, null);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        when(provider.match(anyMap())).thenReturn(PasspointMatch.RoamingProvider);
+        Pair<PasspointProvider, PasspointMatch> result =
+                mManager.matchProvider(createTestScanResult());
+        assertEquals(PasspointMatch.RoamingProvider, result.second);
+        assertEquals(TEST_FQDN, result.first.getConfig().getHomeSp().getFqdn());
+    }
+
+    /**
+     * Verify that a {code null} will be returned when there is no matching provider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchProviderWithNoMatch() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        ANQPData entry = new ANQPData(mClock, null);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        when(provider.match(anyMap())).thenReturn(PasspointMatch.None);
+        assertNull(mManager.matchProvider(createTestScanResult()));
+    }
+
+    /**
+     * Verify the expectations for sweepCache.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void sweepCache() throws Exception {
+        mManager.sweepCache();
+        verify(mAnqpCache).sweep();
+    }
+
+    /**
+     * Verify that an empty map will be returned if ANQP elements are not cached for the given AP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getANQPElementsWithNoMatchFound() throws Exception {
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(null);
+        assertTrue(mManager.getANQPElements(createTestScanResult()).isEmpty());
+    }
+
+    /**
+     * Verify that an expected ANQP elements will be returned if ANQP elements are cached for the
+     * given AP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getANQPElementsWithMatchFound() throws Exception {
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                new DomainNameElement(Arrays.asList(new String[] {"test.com"})));
+        ANQPData entry = new ANQPData(mClock, anqpElementMap);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        assertEquals(anqpElementMap, mManager.getANQPElements(createTestScanResult()));
+    }
+
+    /**
+     * Verify that an expected {@link WifiConfiguration} will be returned when a {@link ScanResult}
+     * is matched to a home provider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingWifiConfigForHomeProviderAP() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        ANQPData entry = new ANQPData(mClock, null);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        when(provider.match(anyMap())).thenReturn(PasspointMatch.HomeProvider);
+        when(provider.getWifiConfig()).thenReturn(new WifiConfiguration());
+        WifiConfiguration config = mManager.getMatchingWifiConfig(createTestScanResult());
+        assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID), config.SSID);
+        assertTrue(config.isHomeProviderNetwork);
+    }
+
+    /**
+     * Verify that an expected {@link WifiConfiguration} will be returned when a {@link ScanResult}
+     * is matched to a roaming provider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingWifiConfigForRoamingProviderAP() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        ANQPData entry = new ANQPData(mClock, null);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        when(provider.match(anyMap())).thenReturn(PasspointMatch.RoamingProvider);
+        when(provider.getWifiConfig()).thenReturn(new WifiConfiguration());
+        WifiConfiguration config = mManager.getMatchingWifiConfig(createTestScanResult());
+        assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID), config.SSID);
+        assertFalse(config.isHomeProviderNetwork);
+    }
+
+    /**
+     * Verify that a {code null} will be returned when a {@link ScanResult} doesn't match any
+     * provider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingWifiConfigWithNoMatchingProvider() throws Exception {
+        PasspointProvider provider = addTestProvider();
+        ANQPData entry = new ANQPData(mClock, null);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+        when(provider.match(anyMap())).thenReturn(PasspointMatch.None);
+        assertNull(mManager.getMatchingWifiConfig(createTestScanResult()));
+        verify(provider, never()).getWifiConfig();
+    }
+
+    /**
+     * Verify that a {@code null} will returned when trying to get a matching
+     * {@link WifiConfiguration} a {@code null} {@link ScanResult}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getMatchingWifiConfigWithNullScanResult() throws Exception {
+        assertNull(mManager.getMatchingWifiConfig(null));
+    }
+
+    /**
+     * Verify that the provider list maintained by the PasspointManager after the list is updated
+     * in the data source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyProvidersAfterDataSourceUpdate() throws Exception {
+        // Update the provider list in the data source.
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointProvider provider = createMockProvider(config);
+        List<PasspointProvider> providers = new ArrayList<>();
+        providers.add(provider);
+        mDataSource.setProviders(providers);
+
+        // Verify the providers maintained by PasspointManager.
+        assertEquals(1, mManager.getProviderConfigs().size());
+        assertEquals(config, mManager.getProviderConfigs().get(0));
+    }
+
+    /**
+     * Verify that the provider index used by PasspointManager is updated after it is updated in
+     * the data source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyProviderIndexAfterDataSourceUpdate() throws Exception {
+        long providerIndex = 9;
+        mDataSource.setProviderIndex(providerIndex);
+        assertEquals(providerIndex, mDataSource.getProviderIndex());
+
+        // Add a provider.
+        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointProvider provider = createMockProvider(config);
+        // Verify the provider ID used to create the new provider.
+        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+                eq(mSimAccessor), eq(providerIndex), eq(TEST_CREATOR_UID))).thenReturn(provider);
+        assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
+        verifyInstalledConfig(config);
+        verify(mWifiConfigManager).saveToStore(true);
+        reset(mWifiConfigManager);
+    }
+
+    /**
+     * Verify that a PasspointProvider with expected PasspointConfiguration will be installed when
+     * adding a legacy Passpoint configuration containing a valid user credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addLegacyPasspointConfigWithUserCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String username = "username";
+        String password = "password";
+        byte[] base64EncodedPw =
+                Base64.encode(password.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
+        String encodedPasswordStr = new String(base64EncodedPw, StandardCharsets.UTF_8);
+        String caCertificateAlias = "CaCert";
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setIdentity(username);
+        wifiConfig.enterpriseConfig.setPassword(password);
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+        wifiConfig.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.PAP);
+        wifiConfig.enterpriseConfig.setCaCertificateAlias(caCertificateAlias);
+
+        // Setup expected {@link PasspointConfiguration}
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        passpointConfig.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername(username);
+        userCredential.setPassword(encodedPasswordStr);
+        userCredential.setEapType(EAPConstants.EAP_TTLS);
+        userCredential.setNonEapInnerMethod("PAP");
+        credential.setUserCredential(userCredential);
+        credential.setRealm(realm);
+        passpointConfig.setCredential(credential);
+
+        assertTrue(PasspointManager.addLegacyPasspointConfig(wifiConfig));
+        verifyInstalledConfig(passpointConfig);
+    }
+
+    /**
+     * Verify that adding a legacy Passpoint configuration containing user credential will
+     * fail when client certificate is not provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addLegacyPasspointConfigWithUserCredentialWithoutCaCert() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String username = "username";
+        String password = "password";
+        byte[] base64EncodedPw =
+                Base64.encode(password.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
+        String encodedPasswordStr = new String(base64EncodedPw, StandardCharsets.UTF_8);
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setIdentity(username);
+        wifiConfig.enterpriseConfig.setPassword(password);
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+        wifiConfig.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.PAP);
+
+        assertFalse(PasspointManager.addLegacyPasspointConfig(wifiConfig));
+    }
+
+    /**
+     * Verify that a PasspointProvider with expected PasspointConfiguration will be installed when
+     * adding a legacy Passpoint configuration containing a valid SIM credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addLegacyPasspointConfigWithSimCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String imsi = "1234";
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        wifiConfig.enterpriseConfig.setPlmn(imsi);
+
+        // Setup expected {@link PasspointConfiguration}
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        passpointConfig.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setEapType(EAPConstants.EAP_SIM);
+        simCredential.setImsi(imsi);
+        credential.setSimCredential(simCredential);
+        credential.setRealm(realm);
+        passpointConfig.setCredential(credential);
+
+        assertTrue(PasspointManager.addLegacyPasspointConfig(wifiConfig));
+        verifyInstalledConfig(passpointConfig);
+    }
+
+    /**
+     * Verify that a PasspointProvider with expected PasspointConfiguration will be installed when
+     * adding a legacy Passpoint configuration containing a valid certificate credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addLegacyPasspointConfigWithCertCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String caCertificateAlias = "CaCert";
+        String clientCertificateAlias = "ClientCert";
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        wifiConfig.enterpriseConfig.setCaCertificateAlias(caCertificateAlias);
+        wifiConfig.enterpriseConfig.setClientCertificateAlias(clientCertificateAlias);
+
+        // Setup expected {@link PasspointConfiguration}
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        passpointConfig.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
+        credential.setCertCredential(certCredential);
+        credential.setRealm(realm);
+        passpointConfig.setCredential(credential);
+
+        assertTrue(PasspointManager.addLegacyPasspointConfig(wifiConfig));
+        verifyInstalledConfig(passpointConfig);
+    }
+
+    /**
+     * Verify that adding a legacy Passpoint configuration containing certificate credential will
+     * fail when CA certificate is not provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addLegacyPasspointConfigWithCertCredentialWithoutCaCert() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String clientCertificateAlias = "ClientCert";
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        wifiConfig.enterpriseConfig.setClientCertificateAlias(clientCertificateAlias);
+
+        assertFalse(PasspointManager.addLegacyPasspointConfig(wifiConfig));
+    }
+
+    /**
+     * Verify that adding a legacy Passpoint configuration containing certificate credential will
+     * fail when client certificate is not provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void addLegacyPasspointConfigWithCertCredentialWithoutClientCert() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String caCertificateAlias = "CaCert";
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+        wifiConfig.enterpriseConfig.setCaCertificateAlias(caCertificateAlias);
+
+        assertFalse(PasspointManager.addLegacyPasspointConfig(wifiConfig));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java
new file mode 100644
index 0000000..2224486
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkEvaluatorTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LocalLog;
+import android.util.Pair;
+
+import com.android.server.wifi.NetworkUpdateResult;
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.WifiConfigManager;
+import com.android.server.wifi.util.ScanResultUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointNetworkEvaluator}.
+ */
+@SmallTest
+public class PasspointNetworkEvaluatorTest {
+    private static final int TEST_NETWORK_ID = 1;
+    private static final String TEST_SSID1 = "ssid1";
+    private static final String TEST_SSID2 = "ssid2";
+    private static final String TEST_FQDN1 = "test1.com";
+    private static final String TEST_FQDN2 = "test2.com";
+    private static final WifiConfiguration TEST_CONFIG1 = generateWifiConfig(TEST_FQDN1);
+    private static final WifiConfiguration TEST_CONFIG2 = generateWifiConfig(TEST_FQDN2);
+    private static final PasspointProvider TEST_PROVIDER1 = generateProvider(TEST_CONFIG1);
+    private static final PasspointProvider TEST_PROVIDER2 = generateProvider(TEST_CONFIG2);
+
+    @Mock PasspointManager mPasspointManager;
+    @Mock WifiConfigManager mWifiConfigManager;
+    LocalLog mLocalLog;
+    PasspointNetworkEvaluator mEvaluator;
+
+    /**
+     * Helper function for generating {@link WifiConfiguration} for testing.
+     *
+     * @param fqdn The FQDN associated with the configuration
+     * @return {@link WifiConfiguration}
+     */
+    private static WifiConfiguration generateWifiConfig(String fqdn) {
+        WifiConfiguration config = new WifiConfiguration();
+        config.FQDN = fqdn;
+        return config;
+    }
+
+    /**
+     * Helper function for generating {@link PasspointProvider} for testing.
+     *
+     * @param config The WifiConfiguration associated with the provider
+     * @return {@link PasspointProvider}
+     */
+    private static PasspointProvider generateProvider(WifiConfiguration config) {
+        PasspointProvider provider = mock(PasspointProvider.class);
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(config.FQDN);
+        passpointConfig.setHomeSp(homeSp);
+        when(provider.getConfig()).thenReturn(passpointConfig);
+        when(provider.getWifiConfig()).thenReturn(config);
+        return provider;
+    }
+
+    /**
+     * Helper function for generating {@link ScanDetail} for testing.
+     *
+     * @param ssid The SSID associated with the scan
+     * @param rssiLevel The RSSI level associated with the scan
+     * @return {@link ScanDetail}
+     */
+    private static ScanDetail generateScanDetail(String ssid) {
+        NetworkDetail networkDetail = mock(NetworkDetail.class);
+        when(networkDetail.isInterworking()).thenReturn(true);
+        when(networkDetail.getAnt()).thenReturn(NetworkDetail.Ant.FreePublic);
+
+        ScanDetail scanDetail = mock(ScanDetail.class);
+        when(scanDetail.getSSID()).thenReturn(ssid);
+        when(scanDetail.getScanResult()).thenReturn(new ScanResult());
+        when(scanDetail.getNetworkDetail()).thenReturn(networkDetail);
+        return scanDetail;
+    }
+
+    /**
+     * Test setup.
+     */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        mLocalLog = new LocalLog(512);
+        mEvaluator = new PasspointNetworkEvaluator(mPasspointManager, mWifiConfigManager,
+                mLocalLog);
+    }
+
+    /**
+     * Verify that null will be returned when evaluating scans without any matching providers.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScansWithNoMatch() throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
+                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(null);
+        assertEquals(null, mEvaluator.evaluateNetworks(
+                scanDetails, null, null, false, false, connectableNetworks));
+        assertTrue(connectableNetworks.isEmpty());
+    }
+
+    /**
+     * Verify that provider matching will not be performed when evaluating scans with no
+     * interworking support, and null will be returned.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaulateScansWithNoInterworkingAP() throws Exception {
+        NetworkDetail networkDetail = mock(NetworkDetail.class);
+        when(networkDetail.isInterworking()).thenReturn(false);
+        ScanDetail scanDetail = mock(ScanDetail.class);
+        when(scanDetail.getNetworkDetail()).thenReturn(networkDetail);
+
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {scanDetail});
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        assertEquals(null, mEvaluator.evaluateNetworks(
+                scanDetails, null, null, false, false, connectableNetworks));
+        assertTrue(connectableNetworks.isEmpty());
+        // Verify that no provider matching is performed.
+        verify(mPasspointManager, never()).matchProvider(any(ScanResult.class));
+    }
+
+    /**
+     * Verify that when a network matches a home provider is found, the correct network
+     * information (WifiConfiguration) is setup and returned.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScansWithNetworkMatchingHomeProvider() throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
+                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+
+        // Setup matching providers for ScanDetail with TEST_SSID1.
+        Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
+                TEST_PROVIDER1, PasspointMatch.HomeProvider);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+
+        // Return homeProvider for the first ScanDetail (TEST_SSID1) and a null (no match) for
+        // for the second (TEST_SSID2);
+        when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(homeProvider)
+                .thenReturn(null);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(TEST_CONFIG1);
+        assertNotNull(mEvaluator.evaluateNetworks(scanDetails, null, null, false,
+                false, connectableNetworks));
+        assertEquals(1, connectableNetworks.size());
+
+        // Verify the content of the WifiConfiguration that was added to WifiConfigManager.
+        ArgumentCaptor<WifiConfiguration> addedConfig =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mWifiConfigManager).addOrUpdateNetwork(addedConfig.capture(), anyInt());
+        assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID1), addedConfig.getValue().SSID);
+        assertEquals(TEST_FQDN1, addedConfig.getValue().FQDN);
+        assertTrue(addedConfig.getValue().isHomeProviderNetwork);
+        verify(mWifiConfigManager).enableNetwork(eq(TEST_NETWORK_ID), eq(false), anyInt());
+        verify(mWifiConfigManager).setNetworkCandidateScanResult(
+                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt());
+        verify(mWifiConfigManager).updateScanDetailForNetwork(
+                eq(TEST_NETWORK_ID), any(ScanDetail.class));
+    }
+
+    /**
+     * Verify that when a network matches a roaming provider is found, the correct network
+     * information (WifiConfiguration) is setup and returned.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScansWithNetworkMatchingRoamingProvider() throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
+                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+
+        // Setup matching providers for ScanDetail with TEST_SSID1.
+        Pair<PasspointProvider, PasspointMatch> roamingProvider = Pair.create(
+                TEST_PROVIDER1, PasspointMatch.RoamingProvider);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+
+        // Return roamingProvider for the first ScanDetail (TEST_SSID1) and a null (no match) for
+        // for the second (TEST_SSID2);
+        when(mPasspointManager.matchProvider(any(ScanResult.class))).thenReturn(roamingProvider)
+                .thenReturn(null);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(TEST_CONFIG1);
+        assertNotNull(mEvaluator.evaluateNetworks(scanDetails, null, null, false,
+                false, connectableNetworks));
+        assertEquals(1, connectableNetworks.size());
+
+        // Verify the content of the WifiConfiguration that was added to WifiConfigManager.
+        ArgumentCaptor<WifiConfiguration> addedConfig =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mWifiConfigManager).addOrUpdateNetwork(addedConfig.capture(), anyInt());
+        assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID1), addedConfig.getValue().SSID);
+        assertEquals(TEST_FQDN1, addedConfig.getValue().FQDN);
+        assertFalse(addedConfig.getValue().isHomeProviderNetwork);
+        verify(mWifiConfigManager).enableNetwork(eq(TEST_NETWORK_ID), eq(false), anyInt());
+        verify(mWifiConfigManager).setNetworkCandidateScanResult(
+                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt());
+        verify(mWifiConfigManager).updateScanDetailForNetwork(
+                eq(TEST_NETWORK_ID), any(ScanDetail.class));
+    }
+
+    /**
+     * Verify that when a network matches a home provider and another network matches a roaming
+     * provider are found, the network that matched to a home provider is preferred.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScansWithHomeProviderNewtorkAndRoamingProviderNetwork() throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
+                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+
+        // Setup matching providers for ScanDetail with TEST_SSID1.
+        Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
+                TEST_PROVIDER1, PasspointMatch.HomeProvider);
+        Pair<PasspointProvider, PasspointMatch> roamingProvider = Pair.create(
+                TEST_PROVIDER2, PasspointMatch.RoamingProvider);
+
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+
+        // Return homeProvider for the first ScanDetail (TEST_SSID1) and
+        // roamingProvider for the second (TEST_SSID2);
+        when(mPasspointManager.matchProvider(any(ScanResult.class)))
+                .thenReturn(homeProvider).thenReturn(roamingProvider);
+        when(mWifiConfigManager.addOrUpdateNetwork(any(WifiConfiguration.class), anyInt()))
+                .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID));
+        when(mWifiConfigManager.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(TEST_CONFIG1);
+        assertNotNull(mEvaluator.evaluateNetworks(scanDetails, null, null, false,
+                false, connectableNetworks));
+        assertEquals(1, connectableNetworks.size());
+
+        // Verify the content of the WifiConfiguration that was added to WifiConfigManager.
+        ArgumentCaptor<WifiConfiguration> addedConfig =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mWifiConfigManager).addOrUpdateNetwork(addedConfig.capture(), anyInt());
+        assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID1), addedConfig.getValue().SSID);
+        assertEquals(TEST_FQDN1, addedConfig.getValue().FQDN);
+        assertTrue(addedConfig.getValue().isHomeProviderNetwork);
+        verify(mWifiConfigManager).enableNetwork(eq(TEST_NETWORK_ID), eq(false), anyInt());
+        verify(mWifiConfigManager).setNetworkCandidateScanResult(
+                eq(TEST_NETWORK_ID), any(ScanResult.class), anyInt());
+        verify(mWifiConfigManager).updateScanDetailForNetwork(
+                eq(TEST_NETWORK_ID), any(ScanDetail.class));
+    }
+
+    /**
+     * Verify that when two networks both matches a home provider, with one of them being the
+     * active network, the active network is preferred.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void evaluateScansWithActiveNetworkMatchingHomeProvider() throws Exception {
+        List<ScanDetail> scanDetails = Arrays.asList(new ScanDetail[] {
+                generateScanDetail(TEST_SSID1), generateScanDetail(TEST_SSID2)});
+
+        // Setup matching providers for both ScanDetail.
+        Pair<PasspointProvider, PasspointMatch> homeProvider = Pair.create(
+                TEST_PROVIDER1, PasspointMatch.HomeProvider);
+
+        // Setup currently connected network
+        WifiConfiguration currentNetwork = new WifiConfiguration();
+        currentNetwork.networkId = TEST_NETWORK_ID;
+        currentNetwork.SSID = ScanResultUtil.createQuotedSSID(TEST_SSID2);
+        String currentBssid = "12:23:34:45:12:0F";
+
+        // Returning the same matching provider for both ScanDetail.
+        List<Pair<ScanDetail, WifiConfiguration>> connectableNetworks = new ArrayList<>();
+        when(mPasspointManager.matchProvider(any(ScanResult.class)))
+                .thenReturn(homeProvider).thenReturn(homeProvider);
+        WifiConfiguration config = mEvaluator.evaluateNetworks(scanDetails, currentNetwork,
+                currentBssid, true, false, connectableNetworks);
+        assertEquals(1, connectableNetworks.size());
+
+        // Verify no new network is added to WifiConfigManager.
+        verify(mWifiConfigManager, never()).addOrUpdateNetwork(
+                any(WifiConfiguration.class), anyInt());
+
+        // Verify current active network is returned.
+        assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID2), config.SSID);
+        assertEquals(TEST_NETWORK_ID, config.networkId);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkScoreTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkScoreTest.java
new file mode 100644
index 0000000..10c50f5
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointNetworkScoreTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.ScanResult;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.ScanDetail;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.Constants;
+import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
+import com.android.server.wifi.hotspot2.anqp.HSWanMetricsElement;
+import com.android.server.wifi.hotspot2.anqp.IPAddressTypeAvailabilityElement;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointNetworkScore}.
+ */
+@SmallTest
+public class PasspointNetworkScoreTest {
+    private static class TestData {
+        public final boolean isHomeProvider;
+        public final boolean isActiveNetwork;
+        public final int rssiLevel;
+        public final boolean internetAccess;
+        public final NetworkDetail.Ant networkType;
+        public final Map<ANQPElementType, ANQPElement> anqpElements;
+        public int expectedScore;
+
+        TestData(boolean homeProvider, boolean activeNetwork, int rssi, boolean internet,
+                NetworkDetail.Ant type, Map<ANQPElementType, ANQPElement> elements,
+                int score) {
+            isHomeProvider = homeProvider;
+            isActiveNetwork = activeNetwork;
+            rssiLevel = rssi;
+            internetAccess = internet;
+            networkType = type;
+            anqpElements = elements;
+            expectedScore = score;
+        }
+    }
+
+    private static final HSWanMetricsElement WAN_PORT_DOWN_ELEMENT = new HSWanMetricsElement(
+            HSWanMetricsElement.LINK_STATUS_DOWN /* status */, true /* symmetric */,
+            false /* capped */, 1233 /* downlinkSpeed */, 1233 /* uplinkSpeed */,
+            10 /* downlinkLoad */, 10 /* uplinkLoad */, 12 /* lmd */);
+
+    private static final HSWanMetricsElement WAN_PORT_UP_ELEMENT = new HSWanMetricsElement(
+            HSWanMetricsElement.LINK_STATUS_UP /* status */, true /* symmetric */,
+            false /* capped */, 1233 /* downlinkSpeed */, 1233 /* uplinkSpeed */,
+            10 /* downlinkLoad */, 10 /* uplinkLoad */, 12 /* lmd */);
+
+    private static final HSWanMetricsElement WAN_PORT_CAPPED_ELEMENT = new HSWanMetricsElement(
+            HSWanMetricsElement.LINK_STATUS_UP /* status */, true /* symmetric */,
+            true /* capped */, 1233 /* downlinkSpeed */, 1233 /* uplinkSpeed */,
+            10 /* downlinkLoad */, 10 /* uplinkLoad */, 12 /* lmd */);
+
+    private static final IPAddressTypeAvailabilityElement UNRESTRICTED_IP_ADDRESS_ELEMENT =
+            new IPAddressTypeAvailabilityElement(IPAddressTypeAvailabilityElement.IPV4_PUBLIC,
+                    IPAddressTypeAvailabilityElement.IPV6_AVAILABLE);
+
+    private static final IPAddressTypeAvailabilityElement UNAVAILABLE_IP_ADDRESS_ELEMENT =
+            new IPAddressTypeAvailabilityElement(
+                    IPAddressTypeAvailabilityElement.IPV4_NOT_AVAILABLE,
+                    IPAddressTypeAvailabilityElement.IPV6_NOT_AVAILABLE);
+
+    private static final IPAddressTypeAvailabilityElement UNKNOWN_IP_ADDRESS_ELEMENT =
+            new IPAddressTypeAvailabilityElement(
+                    IPAddressTypeAvailabilityElement.IPV4_UNKNOWN,
+                    IPAddressTypeAvailabilityElement.IPV6_UNKNOWN);
+
+    private static final Map<ANQPElementType, ANQPElement> TEST_ANQP_WITH_WAN_PORT_DOWN =
+            new HashMap<>();
+
+    private static final Map<ANQPElementType, ANQPElement> TEST_ANQP_WITH_WAN_PORT_UP =
+            new HashMap<>();
+
+    private static final Map<ANQPElementType, ANQPElement> TEST_ANQP_WITH_WAN_PORT_CAPPED =
+            new HashMap<>();
+
+    private static final Map<ANQPElementType, ANQPElement> TEST_ANQP_WITH_UNRESTRICTED_IP =
+            new HashMap<>();
+
+    private static final Map<ANQPElementType, ANQPElement> TEST_ANQP_WITH_UNAVAILABLE_IP =
+            new HashMap<>();
+
+    private static final Map<ANQPElementType, ANQPElement> TEST_ANQP_WITH_UNKNOWN_IP =
+            new HashMap<>();
+
+    // List of test data.
+    private static final List<TestData> TEST_DATA_LIST = new ArrayList<>();
+    static {
+        // Setup ANQP elements map for testing.
+        TEST_ANQP_WITH_WAN_PORT_DOWN.put(Constants.ANQPElementType.HSWANMetrics,
+                WAN_PORT_DOWN_ELEMENT);
+        TEST_ANQP_WITH_WAN_PORT_UP.put(Constants.ANQPElementType.HSWANMetrics,
+                WAN_PORT_UP_ELEMENT);
+        TEST_ANQP_WITH_WAN_PORT_CAPPED.put(Constants.ANQPElementType.HSWANMetrics,
+                WAN_PORT_CAPPED_ELEMENT);
+        TEST_ANQP_WITH_UNRESTRICTED_IP.put(Constants.ANQPElementType.ANQPIPAddrAvailability,
+                UNRESTRICTED_IP_ADDRESS_ELEMENT);
+        TEST_ANQP_WITH_UNAVAILABLE_IP.put(Constants.ANQPElementType.ANQPIPAddrAvailability,
+                UNAVAILABLE_IP_ADDRESS_ELEMENT);
+        TEST_ANQP_WITH_UNKNOWN_IP.put(Constants.ANQPElementType.ANQPIPAddrAvailability,
+                UNKNOWN_IP_ADDRESS_ELEMENT);
+
+        // Home provider public network with Internet access that's not the current
+        // active network.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+
+        // Home provider public network with Internet access that's the current active network.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, true /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, true)));
+
+        // Home provider public network without Internet access that's not the current
+        // active network.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, false /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                - PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+
+        // Home provider personal network with Internet access that's not the current active
+        // network.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.Personal /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PERSONAL_OR_EMERGENCY_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+
+        // Home provider public network with Internet access that's not the current
+        // active network, and ANPQ element indicating WAN port is up.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */,
+                TEST_ANQP_WITH_WAN_PORT_UP /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+
+        // Home provider public network with Internet access that's not the current
+        // active network, and ANPQ element indicating WAN port is down.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */,
+                TEST_ANQP_WITH_WAN_PORT_DOWN /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)
+                - PasspointNetworkScore.WAN_PORT_DOWN_OR_CAPPED_PENALTY));
+
+        // Home provider public network with Internet access that's not the current
+        // active network, and ANPQ element indicating WAN port is capped (max load reached).
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */,
+                TEST_ANQP_WITH_WAN_PORT_CAPPED /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)
+                - PasspointNetworkScore.WAN_PORT_DOWN_OR_CAPPED_PENALTY));
+
+        // Home provider public network with Internet access that's not the current
+        // active network, and ANPQ element indicating both IPv4 and IPv6 addresses are available.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */,
+                TEST_ANQP_WITH_UNRESTRICTED_IP /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)
+                + PasspointNetworkScore.UNRESTRICTED_IP_AWARDS * 2 /* one for IPv4 and IPv6 */));
+
+        // Home provider public network with Internet access that's not the current
+        // active network, and ANPQ element indicating both IPv4 and IPv6 addresses are available.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */,
+                TEST_ANQP_WITH_UNRESTRICTED_IP /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)
+                /* one each for IPv4 and IPv6. */
+                + PasspointNetworkScore.UNRESTRICTED_IP_AWARDS * 2));
+
+        // Home provider public network with Internet access that's not the current
+        // active network, and ANPQ element indicating both IPv4 and IPv6 addresses are
+        // unavailable.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */,
+                TEST_ANQP_WITH_UNAVAILABLE_IP /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+
+        // Home provider public network with Internet access that's not the current
+        // active network, and ANPQ element indicating both IPv4 and IPv6 addresses are unknown.
+        TEST_DATA_LIST.add(new TestData(true /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */,
+                TEST_ANQP_WITH_UNKNOWN_IP /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.HOME_PROVIDER_AWARD
+                + PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)
+                /* one each for IPv4 and IPv6. */
+                + PasspointNetworkScore.RESTRICTED_OR_UNKNOWN_IP_AWARDS * 2));
+
+        // Roaming provider public network with Internet access that's not the current active
+        // network.
+        TEST_DATA_LIST.add(new TestData(false /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+
+        // Roaming provider public network with Internet access that's the current active network.
+        TEST_DATA_LIST.add(new TestData(false /* isHomeProvider */, true /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, true)));
+
+        // Roaming provider public network without Internet access that's not the current active
+        // network.
+        TEST_DATA_LIST.add(new TestData(false /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, false /* internetAccess */,
+                NetworkDetail.Ant.FreePublic /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.PUBLIC_OR_PRIVATE_NETWORK_AWARDS
+                - PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+
+        // Roaming provider personal network with Internet access that's not the current active
+        // network.
+        TEST_DATA_LIST.add(new TestData(false /* isHomeProvider */, false /* isActiveNetwork */,
+                -60 /* rssiLevel */, true /* internetAccess */,
+                NetworkDetail.Ant.Personal /* networkType */, null /* anqpElements */,
+                /* expectedScore */
+                PasspointNetworkScore.INTERNET_ACCESS_AWARD
+                + PasspointNetworkScore.PERSONAL_OR_EMERGENCY_NETWORK_AWARDS
+                + PasspointNetworkScore.RSSI_SCORE.lookupScore(-60, false)));
+    }
+
+    /**
+     * Helper function for generating a {@link ScanDetail} for testing.
+     *
+     * @param rssiLevel RSSI level of the network
+     * @param internetAccess Flag indicating if the network provides Internet access
+     * @param networkType The type of the network
+     * @return {@link ScanDetail}
+     */
+    private static ScanDetail generateScanDetail(int rssiLevel, boolean internetAccess,
+            NetworkDetail.Ant networkType) {
+        // Setup ScanResult.
+        ScanResult scanResult = new ScanResult();
+        scanResult.level = -60;
+
+        // Setup NetworkDetail.
+        NetworkDetail networkDetail = mock(NetworkDetail.class);
+        when(networkDetail.isInternet()).thenReturn(internetAccess);
+        when(networkDetail.getAnt()).thenReturn(networkType);
+
+        // Setup ScanDetail.
+        ScanDetail scanDetail = mock(ScanDetail.class);
+        when(scanDetail.getScanResult()).thenReturn(scanResult);
+        when(scanDetail.getNetworkDetail()).thenReturn(networkDetail);
+
+        return scanDetail;
+    }
+
+    /**
+     * Go through the list of the test data {@link #TEST_DATA_LIST} and verify the score for each.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void calculateScore() throws Exception {
+        for (TestData data : TEST_DATA_LIST) {
+            ScanDetail scanDetail = generateScanDetail(data.rssiLevel, data.internetAccess,
+                    data.networkType);
+            assertEquals(data.expectedScore, PasspointNetworkScore.calculateScore(
+                    data.isHomeProvider, scanDetail, data.anqpElements, data.isActiveNetwork));
+        }
+    }
+
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
new file mode 100644
index 0000000..c416a96
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
@@ -0,0 +1,901 @@
+/*
+ * 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.hotspot2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.net.wifi.EAPConstants;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import com.android.server.wifi.FakeKeys;
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.SIMAccessor;
+import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.hotspot2.anqp.ANQPElement;
+import com.android.server.wifi.hotspot2.anqp.CellularNetwork;
+import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType;
+import com.android.server.wifi.hotspot2.anqp.DomainNameElement;
+import com.android.server.wifi.hotspot2.anqp.NAIRealmData;
+import com.android.server.wifi.hotspot2.anqp.NAIRealmElement;
+import com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement;
+import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
+import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
+import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
+import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointProvider}.
+ */
+@SmallTest
+public class PasspointProviderTest {
+    private static final long PROVIDER_ID = 12L;
+    private static final int CREATOR_UID = 1234;
+    private static final String CA_CERTIFICATE_NAME = "CACERT_HS2_12";
+    private static final String CLIENT_CERTIFICATE_NAME = "USRCERT_HS2_12";
+    private static final String CLIENT_PRIVATE_KEY_NAME = "USRPKEY_HS2_12";
+    private static final String CA_CERTIFICATE_ALIAS = "HS2_12";
+    private static final String CLIENT_CERTIFICATE_ALIAS = "HS2_12";
+    private static final String CLIENT_PRIVATE_KEY_ALIAS = "HS2_12";
+
+    @Mock WifiKeyStore mKeyStore;
+    @Mock SIMAccessor mSimAccessor;
+    PasspointProvider mProvider;
+
+    /** Sets up test. */
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+    }
+
+    /**
+     * Helper function for creating a provider instance for testing.
+     *
+     * @param config The configuration associated with the provider
+     * @return {@link com.android.server.wifi.hotspot2.PasspointProvider}
+     */
+    private PasspointProvider createProvider(PasspointConfiguration config) {
+        return new PasspointProvider(config, mKeyStore, mSimAccessor, PROVIDER_ID, CREATOR_UID);
+    }
+
+    /**
+     * Verify that the configuration associated with the provider is the same or not the same
+     * as the expected configuration.
+     *
+     * @param expectedConfig The expected configuration
+     * @param equals Flag indicating equality or inequality check
+     */
+    private void verifyInstalledConfig(PasspointConfiguration expectedConfig, boolean equals) {
+        PasspointConfiguration actualConfig = mProvider.getConfig();
+        if (equals) {
+            assertTrue(actualConfig.equals(expectedConfig));
+        } else {
+            assertFalse(actualConfig.equals(expectedConfig));
+        }
+    }
+
+    /**
+     * Helper function for creating a Domain Name ANQP element.
+     *
+     * @param domains List of domain names
+     * @return {@link DomainNameElement}
+     */
+    private DomainNameElement createDomainNameElement(String[] domains) {
+        return new DomainNameElement(Arrays.asList(domains));
+    }
+
+    /**
+     * Helper function for creating a NAI Realm ANQP element.
+     *
+     * @param realm The realm of the network
+     * @param eapMethodID EAP Method ID
+     * @param authParam Authentication parameter
+     * @return {@link NAIRealmElement}
+     */
+    private NAIRealmElement createNAIRealmElement(String realm, int eapMethodID,
+            AuthParam authParam) {
+        Map<Integer, Set<AuthParam>> authParamMap = new HashMap<>();
+        if (authParam != null) {
+            Set<AuthParam> authSet = new HashSet<>();
+            authSet.add(authParam);
+            authParamMap.put(authParam.getAuthTypeID(), authSet);
+        }
+        EAPMethod eapMethod = new EAPMethod(eapMethodID, authParamMap);
+        NAIRealmData realmData = new NAIRealmData(Arrays.asList(new String[] {realm}),
+                Arrays.asList(new EAPMethod[] {eapMethod}));
+        return new NAIRealmElement(Arrays.asList(new NAIRealmData[] {realmData}));
+    }
+
+    /**
+     * Helper function for creating a Roaming Consortium ANQP element.
+     *
+     * @param rcOIs Roaming consortium OIs
+     * @return {@link RoamingConsortiumElement}
+     */
+    private RoamingConsortiumElement createRoamingConsortiumElement(Long[] rcOIs) {
+        return new RoamingConsortiumElement(Arrays.asList(rcOIs));
+    }
+
+    /**
+     * Helper function for creating a 3GPP Network ANQP element.
+     *
+     * @param imsiList List of IMSI to be included in a 3GPP Network
+     * @return {@link ThreeGPPNetworkElement}
+     */
+    private ThreeGPPNetworkElement createThreeGPPNetworkElement(String[] imsiList) {
+        CellularNetwork network = new CellularNetwork(Arrays.asList(imsiList));
+        return new ThreeGPPNetworkElement(Arrays.asList(new CellularNetwork[] {network}));
+    }
+
+    /**
+     * Verify that modification to the configuration used for creating PasspointProvider
+     * will not change the configuration stored inside the PasspointProvider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyModifyOriginalConfig() throws Exception {
+        // Create a dummy PasspointConfiguration.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1");
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+
+        // Modify the original configuration, the configuration maintained by the provider
+        // should be unchanged.
+        config.getHomeSp().setFqdn("test2");
+        verifyInstalledConfig(config, false);
+    }
+
+    /**
+     * Verify that modification to the configuration retrieved from the PasspointProvider
+     * will not change the configuration stored inside the PasspointProvider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyModifyRetrievedConfig() throws Exception {
+        // Create a dummy PasspointConfiguration.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1");
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+
+        // Modify the retrieved configuration, verify the configuration maintained by the
+        // provider should be unchanged.
+        PasspointConfiguration retrievedConfig = mProvider.getConfig();
+        retrievedConfig.getHomeSp().setFqdn("test2");
+        verifyInstalledConfig(retrievedConfig, false);
+    }
+
+    /**
+     * Verify a successful installation of certificates and key.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void installCertsAndKeysSuccess() throws Exception {
+        // Create a dummy configuration with certificate credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        Credential credential = new Credential();
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertSha256Fingerprint(
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        credential.setCertCredential(certCredential);
+        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
+        credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Install client certificate and key to the keystore successfully.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_NAME, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        when(mKeyStore.putKeyInKeyStore(CLIENT_PRIVATE_KEY_NAME, FakeKeys.RSA_KEY1))
+                .thenReturn(true);
+        when(mKeyStore.putCertInKeyStore(CLIENT_CERTIFICATE_NAME, FakeKeys.CLIENT_CERT))
+                .thenReturn(true);
+        assertTrue(mProvider.installCertsAndKeys());
+
+        // Verify client certificate and key in the configuration gets cleared and aliases
+        // are set correctly.
+        PasspointConfiguration curConfig = mProvider.getConfig();
+        assertTrue(curConfig.getCredential().getCaCertificate() == null);
+        assertTrue(curConfig.getCredential().getClientPrivateKey() == null);
+        assertTrue(curConfig.getCredential().getClientCertificateChain() == null);
+        assertTrue(mProvider.getCaCertificateAlias().equals(CA_CERTIFICATE_ALIAS));
+        assertTrue(mProvider.getClientPrivateKeyAlias().equals(CLIENT_PRIVATE_KEY_ALIAS));
+        assertTrue(mProvider.getClientCertificateAlias().equals(CLIENT_CERTIFICATE_ALIAS));
+    }
+
+    /**
+     * Verify a failure installation of certificates and key.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void installCertsAndKeysFailure() throws Exception {
+        // Create a dummy configuration with certificate credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        Credential credential = new Credential();
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertSha256Fingerprint(
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        credential.setCertCredential(certCredential);
+        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
+        credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Failed to install client certificate to the keystore.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_NAME, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        when(mKeyStore.putKeyInKeyStore(CLIENT_PRIVATE_KEY_NAME, FakeKeys.RSA_KEY1))
+                .thenReturn(true);
+        when(mKeyStore.putCertInKeyStore(CLIENT_CERTIFICATE_NAME, FakeKeys.CLIENT_CERT))
+                .thenReturn(false);
+        assertFalse(mProvider.installCertsAndKeys());
+
+        // Verify certificates and key in the configuration are not cleared and aliases
+        // are not set.
+        PasspointConfiguration curConfig = mProvider.getConfig();
+        assertTrue(curConfig.getCredential().getCaCertificate() != null);
+        assertTrue(curConfig.getCredential().getClientCertificateChain() != null);
+        assertTrue(curConfig.getCredential().getClientPrivateKey() != null);
+        assertTrue(mProvider.getCaCertificateAlias() == null);
+        assertTrue(mProvider.getClientPrivateKeyAlias() == null);
+        assertTrue(mProvider.getClientCertificateAlias() == null);
+    }
+
+    /**
+     * Verify a successful uninstallation of certificates and key.
+     */
+    @Test
+    public void uninstallCertsAndKeys() throws Exception {
+        // Create a dummy configuration with certificate credential.
+        PasspointConfiguration config = new PasspointConfiguration();
+        Credential credential = new Credential();
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertSha256Fingerprint(
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        credential.setCertCredential(certCredential);
+        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
+        credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Install client certificate and key to the keystore successfully.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_NAME, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        when(mKeyStore.putKeyInKeyStore(CLIENT_PRIVATE_KEY_NAME, FakeKeys.RSA_KEY1))
+                .thenReturn(true);
+        when(mKeyStore.putCertInKeyStore(CLIENT_CERTIFICATE_NAME, FakeKeys.CLIENT_CERT))
+                .thenReturn(true);
+        assertTrue(mProvider.installCertsAndKeys());
+        assertTrue(mProvider.getCaCertificateAlias().equals(CA_CERTIFICATE_ALIAS));
+        assertTrue(mProvider.getClientPrivateKeyAlias().equals(CLIENT_PRIVATE_KEY_ALIAS));
+        assertTrue(mProvider.getClientCertificateAlias().equals(CLIENT_CERTIFICATE_ALIAS));
+
+        // Uninstall certificates and key from the keystore.
+        mProvider.uninstallCertsAndKeys();
+        verify(mKeyStore).removeEntryFromKeyStore(CA_CERTIFICATE_NAME);
+        verify(mKeyStore).removeEntryFromKeyStore(CLIENT_CERTIFICATE_NAME);
+        verify(mKeyStore).removeEntryFromKeyStore(CLIENT_PRIVATE_KEY_NAME);
+        assertTrue(mProvider.getCaCertificateAlias() == null);
+        assertTrue(mProvider.getClientPrivateKeyAlias() == null);
+        assertTrue(mProvider.getClientCertificateAlias() == null);
+    }
+
+    /**
+     * Verify that a provider is a home provider when its FQDN matches a domain name in the
+     * Domain Name ANQP element and no NAI realm is provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchFQDNWithoutNAIRealm() throws Exception {
+        String testDomain = "test.com";
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(testDomain);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
+        credential.setUserCredential(userCredential);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Setup ANQP elements.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {testDomain}));
+
+        assertEquals(PasspointMatch.HomeProvider, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that a provider is a home provider when its FQDN matches a domain name in the
+     * Domain Name ANQP element and the provider's credential matches the NAI realm provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchFQDNWithNAIRealmMatch() throws Exception {
+        String testDomain = "test.com";
+        String testRealm = "realm.com";
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(testDomain);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(testRealm);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
+        credential.setUserCredential(userCredential);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {testDomain}));
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(testRealm, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+
+        assertEquals(PasspointMatch.HomeProvider, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that there is no match when the provider's FQDN matches a domain name in the
+     * Domain Name ANQP element but the provider's credential doesn't match the authentication
+     * method provided in the NAI realm.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchFQDNWithNAIRealmMismatch() throws Exception {
+        String testDomain = "test.com";
+        String testRealm = "realm.com";
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(testDomain);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(testRealm);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
+        credential.setUserCredential(userCredential);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {testDomain}));
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(testRealm, EAPConstants.EAP_TLS, null));
+
+        assertEquals(PasspointMatch.None, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that a provider is a home provider when its SIM credential matches an 3GPP network
+     * domain name in the Domain Name ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void match3GPPNetworkDomainName() throws Exception {
+        String testImsi = "1234567890";
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setHomeSp(new HomeSp());
+        Credential credential = new Credential();
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi(testImsi);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+        when(mSimAccessor.getMatchingImsis(new IMSIParameter(testImsi, false)))
+                .thenReturn(Arrays.asList(new String[] {testImsi}));
+        mProvider = createProvider(config);
+
+        // Setup Domain Name ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {"wlan.mnc456.mcc123.3gppnetwork.org"}));
+
+        assertEquals(PasspointMatch.HomeProvider, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that a provider is a roaming provider when a roaming consortium OI matches an OI
+     * in the roaming consortium ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchRoamingConsortium() throws Exception {
+        long[] providerRCOIs = new long[] {0x1234L, 0x2345L};
+        Long[] anqpRCOIs = new Long[] {0x1234L, 0x2133L};
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setRoamingConsortiumOis(providerRCOIs);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
+        credential.setUserCredential(userCredential);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Setup Roaming Consortium ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
+                createRoamingConsortiumElement(anqpRCOIs));
+
+        assertEquals(PasspointMatch.RoamingProvider, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that a provider is a roaming provider when the provider's IMSI parameter and an
+     * IMSI from the SIM card matches a MCC-MNC in the 3GPP Network ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchThreeGPPNetwork() throws Exception {
+        String testImsi = "1234567890";
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setHomeSp(new HomeSp());
+        Credential credential = new Credential();
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi(testImsi);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+        when(mSimAccessor.getMatchingImsis(new IMSIParameter(testImsi, false)))
+                .thenReturn(Arrays.asList(new String[] {testImsi}));
+        mProvider = createProvider(config);
+
+        // Setup 3GPP Network ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQP3GPPNetwork,
+                createThreeGPPNetworkElement(new String[] {"123456"}));
+
+        assertEquals(PasspointMatch.RoamingProvider, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that a provider is a roaming provider when its credential matches a NAI realm in
+     * the NAI Realm ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchNAIRealm() throws Exception {
+        String testRealm = "realm.com";
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setHomeSp(new HomeSp());
+        Credential credential = new Credential();
+        credential.setRealm(testRealm);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
+        credential.setUserCredential(userCredential);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Setup NAI Realm ANQP element.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
+                createNAIRealmElement(testRealm, EAPConstants.EAP_TTLS,
+                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAPV2)));
+
+        assertEquals(PasspointMatch.RoamingProvider, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that a provider is a home provider when its FQDN, roaming consortium OI, and
+     * IMSI all matched against the ANQP elements, since we prefer matching home provider over
+     * roaming provider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchHomeOverRoamingProvider() throws Exception {
+        // Setup test data.
+        String testDomain = "test.com";
+        String testImsi = "1234567890";
+        long[] providerRCOIs = new long[] {0x1234L, 0x2345L};
+        Long[] anqpRCOIs = new Long[] {0x1234L, 0x2133L};
+
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(testDomain);
+        homeSp.setRoamingConsortiumOis(providerRCOIs);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi(testImsi);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+        when(mSimAccessor.getMatchingImsis(new IMSIParameter(testImsi, false)))
+                .thenReturn(Arrays.asList(new String[] {testImsi}));
+        mProvider = createProvider(config);
+
+        // Setup ANQP elements.
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {testDomain}));
+        anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
+                createRoamingConsortiumElement(anqpRCOIs));
+        anqpElementMap.put(ANQPElementType.ANQP3GPPNetwork,
+                createThreeGPPNetworkElement(new String[] {"123456"}));
+
+        assertEquals(PasspointMatch.HomeProvider, mProvider.match(anqpElementMap));
+    }
+
+    /**
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
+     * with an user credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getWifiConfigWithUserCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String username = "username";
+        String password = "password";
+        byte[] base64EncodedPw =
+                Base64.encode(password.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
+        String encodedPasswordStr = new String(base64EncodedPw, StandardCharsets.UTF_8);
+
+        // Create provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(realm);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername(username);
+        userCredential.setPassword(encodedPasswordStr);
+        userCredential.setNonEapInnerMethod(Credential.UserCredential.AUTH_METHOD_MSCHAPV2);
+        credential.setUserCredential(userCredential);
+        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Install certificate.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_NAME, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        assertTrue(mProvider.installCertsAndKeys());
+
+        // Retrieve the WifiConfiguration associated with the provider, and verify the content of
+        // the configuration.  Need to verify field by field since WifiConfiguration doesn't
+        // override equals() function.
+        WifiConfiguration wifiConfig = mProvider.getWifiConfig();
+        WifiEnterpriseConfig wifiEnterpriseConfig = wifiConfig.enterpriseConfig;
+        assertEquals(fqdn, wifiConfig.FQDN);
+        assertEquals(friendlyName, wifiConfig.providerFriendlyName);
+        assertTrue(Arrays.equals(rcOIs, wifiConfig.roamingConsortiumIds));
+        assertTrue(wifiConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertTrue(wifiConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertEquals(realm, wifiEnterpriseConfig.getRealm());
+        assertEquals(fqdn, wifiEnterpriseConfig.getDomainSuffixMatch());
+        assertEquals("anonymous@" + realm, wifiEnterpriseConfig.getAnonymousIdentity());
+        assertEquals(WifiEnterpriseConfig.Eap.TTLS, wifiEnterpriseConfig.getEapMethod());
+        assertEquals(WifiEnterpriseConfig.Phase2.MSCHAPV2, wifiEnterpriseConfig.getPhase2Method());
+        assertEquals(username, wifiEnterpriseConfig.getIdentity());
+        assertEquals(password, wifiEnterpriseConfig.getPassword());
+        assertEquals(CA_CERTIFICATE_ALIAS, wifiEnterpriseConfig.getCaCertificateAlias());
+    }
+
+    /**
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
+     * with a certificate credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getWifiConfigWithCertCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+
+        // Create provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(realm);
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertSha256Fingerprint(
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
+        credential.setCertCredential(certCredential);
+        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
+        credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Install certificate.
+        when(mKeyStore.putCertInKeyStore(CA_CERTIFICATE_NAME, FakeKeys.CA_CERT0))
+                .thenReturn(true);
+        when(mKeyStore.putKeyInKeyStore(CLIENT_PRIVATE_KEY_NAME, FakeKeys.RSA_KEY1))
+                .thenReturn(true);
+        when(mKeyStore.putCertInKeyStore(CLIENT_CERTIFICATE_NAME, FakeKeys.CLIENT_CERT))
+                .thenReturn(true);
+        assertTrue(mProvider.installCertsAndKeys());
+
+        // Retrieve the WifiConfiguration associated with the provider, and verify the content of
+        // the configuration.  Need to verify field by field since WifiConfiguration doesn't
+        // override equals() function.
+        WifiConfiguration wifiConfig = mProvider.getWifiConfig();
+        WifiEnterpriseConfig wifiEnterpriseConfig = wifiConfig.enterpriseConfig;
+        assertEquals(fqdn, wifiConfig.FQDN);
+        assertEquals(friendlyName, wifiConfig.providerFriendlyName);
+        assertTrue(Arrays.equals(rcOIs, wifiConfig.roamingConsortiumIds));
+        assertTrue(wifiConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertTrue(wifiConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertEquals(realm, wifiEnterpriseConfig.getRealm());
+        assertEquals(fqdn, wifiEnterpriseConfig.getDomainSuffixMatch());
+        assertEquals("anonymous@" + realm, wifiEnterpriseConfig.getAnonymousIdentity());
+        assertEquals(WifiEnterpriseConfig.Eap.TLS, wifiEnterpriseConfig.getEapMethod());
+        assertEquals(CLIENT_CERTIFICATE_ALIAS, wifiEnterpriseConfig.getClientCertificateAlias());
+        assertEquals(CA_CERTIFICATE_ALIAS, wifiEnterpriseConfig.getCaCertificateAlias());
+    }
+
+    /**
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
+     * with a SIM credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getWifiConfigWithSimCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String imsi = "1234*";
+
+        // Create provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setRealm(realm);
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi(imsi);
+        simCredential.setEapType(EAPConstants.EAP_SIM);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+
+        // Retrieve the WifiConfiguration associated with the provider, and verify the content of
+        // the configuration.  Need to verify field by field since WifiConfiguration doesn't
+        // override equals() function.
+        WifiConfiguration wifiConfig = mProvider.getWifiConfig();
+        WifiEnterpriseConfig wifiEnterpriseConfig = wifiConfig.enterpriseConfig;
+        assertEquals(fqdn, wifiConfig.FQDN);
+        assertEquals(friendlyName, wifiConfig.providerFriendlyName);
+        assertTrue(Arrays.equals(rcOIs, wifiConfig.roamingConsortiumIds));
+        assertTrue(wifiConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertTrue(wifiConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertEquals(realm, wifiEnterpriseConfig.getRealm());
+        assertEquals(fqdn, wifiEnterpriseConfig.getDomainSuffixMatch());
+        assertEquals(WifiEnterpriseConfig.Eap.SIM, wifiEnterpriseConfig.getEapMethod());
+        assertEquals(imsi, wifiEnterpriseConfig.getPlmn());
+    }
+
+    /**
+     * Verify that an expected {@link PasspointConfiguration} will be returned when converting
+     * from a {@link WifiConfiguration} containing an user credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void convertFromWifiConfigWithUserCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String username = "username";
+        String password = "password";
+        byte[] base64EncodedPw =
+                Base64.encode(password.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
+        String encodedPasswordStr = new String(base64EncodedPw, StandardCharsets.UTF_8);
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setIdentity(username);
+        wifiConfig.enterpriseConfig.setPassword(password);
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+        wifiConfig.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.PAP);
+
+        // Setup expected {@link PasspointConfiguration}
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        passpointConfig.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername(username);
+        userCredential.setPassword(encodedPasswordStr);
+        userCredential.setEapType(EAPConstants.EAP_TTLS);
+        userCredential.setNonEapInnerMethod("PAP");
+        credential.setUserCredential(userCredential);
+        credential.setRealm(realm);
+        passpointConfig.setCredential(credential);
+
+        assertEquals(passpointConfig, PasspointProvider.convertFromWifiConfig(wifiConfig));
+    }
+
+    /**
+     * Verify that an expected {@link PasspointConfiguration} will be returned when converting
+     * from a {@link WifiConfiguration} containing a SIM credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void convertFromWifiConfigWithSimCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+        String imsi = "1234";
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.SIM);
+        wifiConfig.enterpriseConfig.setPlmn(imsi);
+
+        // Setup expected {@link PasspointConfiguration}
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        passpointConfig.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setEapType(EAPConstants.EAP_SIM);
+        simCredential.setImsi(imsi);
+        credential.setSimCredential(simCredential);
+        credential.setRealm(realm);
+        passpointConfig.setCredential(credential);
+
+        assertEquals(passpointConfig, PasspointProvider.convertFromWifiConfig(wifiConfig));
+    }
+
+    /**
+     * Verify that an expected {@link PasspointConfiguration} will be returned when converting
+     * from a {@link WifiConfiguration} containing a certificate credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void convertFromWifiConfigWithCertCredential() throws Exception {
+        // Test data.
+        String fqdn = "test.com";
+        String friendlyName = "Friendly Name";
+        long[] rcOIs = new long[] {0x1234L, 0x2345L};
+        String realm = "realm.com";
+
+        // Setup WifiConfiguration for legacy Passpoint configuraiton.
+        WifiConfiguration wifiConfig = new WifiConfiguration();
+        wifiConfig.FQDN = fqdn;
+        wifiConfig.providerFriendlyName = friendlyName;
+        wifiConfig.roamingConsortiumIds = rcOIs;
+        wifiConfig.enterpriseConfig.setRealm(realm);
+        wifiConfig.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TLS);
+
+        // Setup expected {@link PasspointConfiguration}
+        PasspointConfiguration passpointConfig = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn(fqdn);
+        homeSp.setFriendlyName(friendlyName);
+        homeSp.setRoamingConsortiumOis(rcOIs);
+        passpointConfig.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertType(Credential.CertificateCredential.CERT_TYPE_X509V3);
+        credential.setCertCredential(certCredential);
+        credential.setRealm(realm);
+        passpointConfig.setCredential(credential);
+
+        assertEquals(passpointConfig, PasspointProvider.convertFromWifiConfig(wifiConfig));
+    }
+
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointXmlUtilsTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointXmlUtilsTest.java
new file mode 100644
index 0000000..67f2dcd
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointXmlUtilsTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.hotspot2;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+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.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointXmlUtilsTest}.
+ */
+@SmallTest
+public class PasspointXmlUtilsTest {
+
+    /**
+     * Helper function for generating a {@link PasspointConfiguration} for testing the XML
+     * serialization/deserialization logic.
+     *
+     * @return {@link PasspointConfiguration}
+     * @throws Exception
+     */
+    private PasspointConfiguration createFullPasspointConfiguration() throws Exception {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        byte[] certFingerprint = new byte[32];
+        Arrays.fill(certFingerprint, (byte) 0x1f);
+
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setUpdateIdentifier(12);
+        config.setCredentialPriority(99);
+
+        // AAA Server trust root.
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        trustRootCertList.put("server1.trust.root.com", certFingerprint);
+        config.setTrustRootCertList(trustRootCertList);
+
+        // Subscription update.
+        UpdateParameter subscriptionUpdate = new UpdateParameter();
+        subscriptionUpdate.setUpdateIntervalInMinutes(120);
+        subscriptionUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_SSP);
+        subscriptionUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER);
+        subscriptionUpdate.setServerUri("subscription.update.com");
+        subscriptionUpdate.setUsername("subscriptionUser");
+        subscriptionUpdate.setBase64EncodedPassword("subscriptionPass");
+        subscriptionUpdate.setTrustRootCertUrl("subscription.update.cert.com");
+        subscriptionUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+        config.setSubscriptionUpdate(subscriptionUpdate);
+
+        // Subscription parameters.
+        config.setSubscriptionCreationTimeInMillis(format.parse("2016-02-01T10:00:00Z").getTime());
+        config.setSubscriptionExpirationTimeInMillis(
+                format.parse("2016-03-01T10:00:00Z").getTime());
+        config.setSubscriptionType("Gold");
+        config.setUsageLimitDataLimit(921890);
+        config.setUsageLimitStartTimeInMillis(format.parse("2016-12-01T10:00:00Z").getTime());
+        config.setUsageLimitTimeLimitInMinutes(120);
+        config.setUsageLimitUsageTimePeriodInMinutes(99910);
+
+        // HomeSP configuration.
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFriendlyName("Century House");
+        homeSp.setFqdn("mi6.co.uk");
+        homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+        homeSp.setIconUrl("icon.test.com");
+        Map<String, Long> homeNetworkIds = new HashMap<>();
+        homeNetworkIds.put("TestSSID", 0x12345678L);
+        homeNetworkIds.put("NullHESSID", null);
+        homeSp.setHomeNetworkIds(homeNetworkIds);
+        homeSp.setMatchAllOis(new long[] {0x11223344});
+        homeSp.setMatchAnyOis(new long[] {0x55667788});
+        homeSp.setOtherHomePartners(new String[] {"other.fqdn.com"});
+        config.setHomeSp(homeSp);
+
+        // Credential configuration.
+        Credential credential = new Credential();
+        credential.setCreationTimeInMillis(format.parse("2016-01-01T10:00:00Z").getTime());
+        credential.setExpirationTimeInMillis(format.parse("2016-02-01T10:00:00Z").getTime());
+        credential.setRealm("shaken.stirred.com");
+        credential.setCheckAaaServerCertStatus(true);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername("james");
+        userCredential.setPassword("Ym9uZDAwNw==");
+        userCredential.setMachineManaged(true);
+        userCredential.setSoftTokenApp("TestApp");
+        userCredential.setAbleToShare(true);
+        userCredential.setEapType(21);
+        userCredential.setNonEapInnerMethod("MS-CHAP-V2");
+        credential.setUserCredential(userCredential);
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertType("x509v3");
+        certCredential.setCertSha256Fingerprint(certFingerprint);
+        credential.setCertCredential(certCredential);
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi("imsi");
+        simCredential.setEapType(24);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+
+        // Policy configuration.
+        Policy policy = new Policy();
+        List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.setFqdn("test1.fqdn.com");
+        partner1.setFqdnExactMatch(true);
+        partner1.setPriority(127);
+        partner1.setCountries("us,fr");
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.setFqdn("test2.fqdn.com");
+        partner2.setFqdnExactMatch(false);
+        partner2.setPriority(200);
+        partner2.setCountries("*");
+        preferredRoamingPartnerList.add(partner1);
+        preferredRoamingPartnerList.add(partner2);
+        policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
+        policy.setMinHomeDownlinkBandwidth(23412);
+        policy.setMinHomeUplinkBandwidth(9823);
+        policy.setMinRoamingDownlinkBandwidth(9271);
+        policy.setMinRoamingUplinkBandwidth(2315);
+        policy.setExcludedSsidList(new String[] {"excludeSSID"});
+        Map<Integer, String> requiredProtoPortMap = new HashMap<>();
+        requiredProtoPortMap.put(12, "34,92,234");
+        policy.setRequiredProtoPortMap(requiredProtoPortMap);
+        policy.setMaximumBssLoadValue(23);
+        UpdateParameter policyUpdate = new UpdateParameter();
+        policyUpdate.setUpdateIntervalInMinutes(120);
+        policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+        policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+        policyUpdate.setServerUri("policy.update.com");
+        policyUpdate.setUsername("updateUser");
+        policyUpdate.setBase64EncodedPassword("updatePass");
+        policyUpdate.setTrustRootCertUrl("update.cert.com");
+        policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+        policy.setPolicyUpdate(policyUpdate);
+        config.setPolicy(policy);
+        return config;
+    }
+
+    /**
+     * Verify the serialization and deserialization logic of a {@link PasspointConfiguration}.
+     *
+     * 1. Serialize the test config to a XML block
+     * 2. Deserialize the XML block to a {@link PasspointConfiguration}
+     * 3. Verify that the deserialized config is the same as the test config
+     *
+     * @param testConfig The configuration to used for testing
+     * @throws Exception
+     */
+    private void serializeAndDeserializePasspointConfiguration(PasspointConfiguration testConfig)
+            throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        PasspointXmlUtils.serializePasspointConfiguration(out, testConfig);
+        out.flush();
+
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream =
+                new ByteArrayInputStream(outputStream.toByteArray());
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+        PasspointConfiguration deserializedConfig =
+                PasspointXmlUtils.deserializePasspointConfiguration(in, in.getDepth());
+        assertEquals(testConfig, deserializedConfig);
+    }
+
+    /**
+     * Verify that the serialization and deserialization logic for a full
+     * {@link PasspointConfiguration} (all fields are set) works as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeAndDeserializeFullPasspointConfiguration() throws Exception {
+        serializeAndDeserializePasspointConfiguration(createFullPasspointConfiguration());
+    }
+
+    /**
+     * Verify that the serialization and deserialization logic for an empty
+     * {@link PasspointConfiguration} works as expected.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeAndDeserializeEmptyPasspointConfiguration() throws Exception {
+        serializeAndDeserializePasspointConfiguration(new PasspointConfiguration());
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when deserialize a XML block
+     * for a PasspointConfiguraiton containing an unknown tag.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void deserializePasspointConfigurationWithUnknownTag() throws Exception {
+        String xmlStr = "<UnknownTag>\n"
+                + "</UnknownTag>\n";
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream =
+                new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        PasspointXmlUtils.deserializePasspointConfiguration(in, in.getDepth());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ANQPParserTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ANQPParserTest.java
new file mode 100644
index 0000000..8f019e0
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ANQPParserTest.java
@@ -0,0 +1,485 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.ANQPParser}.
+ */
+@SmallTest
+public class ANQPParserTest {
+    /**
+     * Helper function for generating payload for a Venue Name ANQP element.
+     *
+     * @param language Array of languages
+     * @param text Array of text
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getVenueNamePayload(String[] language, String[] text)
+            throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        stream.write(new byte[VenueNameElement.VENUE_INFO_LENGTH]);
+        stream.write(getI18NameListPayload(language, text));
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating payload for a Domain Name ANQP element.
+     *
+     * @param names Array of domain names
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getDomainNamePayload(String[] names) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (String name : names) {
+            byte[] nameBytes = name.getBytes(StandardCharsets.ISO_8859_1);
+            stream.write((byte) nameBytes.length);
+            stream.write(nameBytes);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating payload for a Roaming Consortium ANQP element.
+     *
+     * @param ois Array of OIs
+     * @param oisLength Array of length of each corresponding OI
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getRoamingConsortiumPayload(Long[] ois, int[] oisLength)
+            throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (int i = 0; i < ois.length; i++) {
+            stream.write((byte) oisLength[i]);
+            // Write the OI data in big-endian.
+            for (int l = oisLength[i] - 1; l >= 0; l--) {
+                stream.write((byte) ((ois[i].longValue() >> l * Byte.SIZE) & 0xFF));
+            }
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating payload for a NAI Realm ANQP element.
+     *
+     * @param realmDataList Array of realm data.
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getNAIRealmPayload(byte[][] realmDataList) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        // Data count in little-endian
+        stream.write((byte) (realmDataList.length & 0xFF));
+        stream.write((byte) ((realmDataList.length >> 8) & 0xFF));
+        for (byte[] realmData : realmDataList) {
+            stream.write(realmData);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating payload for 3GPP Network ANQP element.
+     *
+     * @param ieiList Array of IEI data
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getThreeGPPNetworkPayload(byte[][] ieiList) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        int totalIeiSize = CellularNetworkTestUtil.getDataSize(ieiList);
+        stream.write((byte) ThreeGPPNetworkElement.GUD_VERSION_1);
+        stream.write((byte) totalIeiSize);
+        for (byte[] iei : ieiList) {
+            stream.write(iei);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating payload for Vendor Specific ANQP element.
+     *
+     * @param oi The OI of the vendor
+     * @param type The type of the element
+     * @param subtype The subtype of the element
+     * @param payload The vendor specific data
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getVendorSpecificPayload(int oi, int type, int subtype, byte[] payload)
+            throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        stream.write((byte) ((oi >> 16) & 0xFF));
+        stream.write((byte) ((oi >> 8) & 0xFF));
+        stream.write((byte) (oi & 0xFF));
+        stream.write((byte) type);
+        stream.write((byte) subtype);
+        stream.write((byte) 0);    // Reserved
+        stream.write(payload);
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating payload for a Hotspot 2.0 Operator Friendly Name ANQP element.
+     *
+     * @param language Array of language
+     * @param text Array of text
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getHSFriendlyNamePayload(String[] language, String[] text)
+            throws IOException {
+        return getI18NameListPayload(language, text);
+    }
+
+    /**
+     * Helper function for generating payload for a Hotspot 2.0 WAN Metrics ANQP element.
+     *
+     * @param status Link status
+     * @param symmetric Flag indicating symmetric link speed
+     * @param capped Flag indicating link operating at max capacity
+     * @param downlinkSpeed Downlink speed
+     * @param uplinkSpeed Uplink speed
+     * @param downlinkLoad Downlink load
+     * @param uplinkLoad Uplink load
+     * @param lmd Load measurement duration
+     * @return byte[]
+     */
+    private static byte[] getHSWanMetricsPayload(int status, boolean symmetric, boolean capped,
+            long downlinkSpeed, long uplinkSpeed, int downlinkLoad, int uplinkLoad, int lmd) {
+        ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        int wanInfo = status & HSWanMetricsElement.LINK_STATUS_MASK;
+        if (symmetric) wanInfo |= HSWanMetricsElement.SYMMETRIC_LINK_MASK;
+        if (capped) wanInfo |= HSWanMetricsElement.AT_CAPACITY_MASK;
+        buffer.put((byte) wanInfo);
+        buffer.putInt((int) (downlinkSpeed & 0xFFFFFFFFL));
+        buffer.putInt((int) (uplinkSpeed & 0xFFFFFFFFL));
+        buffer.put((byte) (downlinkLoad & 0xFF));
+        buffer.put((byte) (uplinkLoad & 0xFF));
+        buffer.putShort((short) (lmd & 0xFFFF));
+        buffer.position(0);
+        byte[] data = new byte[HSWanMetricsElement.EXPECTED_BUFFER_SIZE];
+        buffer.get(data);
+        return data;
+    }
+
+    /**
+     * Helper function for generating payload for a Hotspot 2.0 Connection Capability ANQP
+     * element.
+     *
+     * @param protocol Network protocol
+     * @param port Network port
+     * @param status Status of the port
+     * @return byte[]
+     */
+    private static byte[] getHSConnectionCapabilityPayload(int protocol, int port, int status) {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        stream.write((byte) protocol);
+        // Write 2-byte port in little-endian.
+        stream.write((byte) (port & 0xFF));
+        stream.write((byte) ((port >> 8) & 0xFF));
+        stream.write((byte) status);
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating payload for a list of I18Name.
+     *
+     * @param language Array of language
+     * @param text Array of text
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getI18NameListPayload(String[] language, String[] text)
+            throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (int i = 0; i < language.length; i++) {
+            byte[] textBytes = text[i].getBytes(StandardCharsets.UTF_8);
+            int length = I18Name.LANGUAGE_CODE_LENGTH + text[i].length();
+            stream.write((byte) length);
+            stream.write(language[i].getBytes(StandardCharsets.US_ASCII));
+            // Add padding for two-character language code.
+            if (language[i].getBytes(StandardCharsets.US_ASCII).length
+                    < I18Name.LANGUAGE_CODE_LENGTH) {
+                stream.write(new byte[]{(byte) 0x0});
+            }
+            stream.write(textBytes);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Verify that an expected VenueNameElement will be returned when parsing a buffer that
+     * contained a Venue Name ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseVenueNameElement() throws Exception {
+        // Test data.
+        String[] language = new String[] {"en"};
+        String[] text = new String[] {"test"};
+
+        // Setup expectation.
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(language[0], Locale.forLanguageTag(language[0]), text[0]));
+        VenueNameElement expected = new VenueNameElement(nameList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(getVenueNamePayload(language, text));
+        assertEquals(expected,
+                ANQPParser.parseElement(Constants.ANQPElementType.ANQPVenueName, buffer));
+    }
+
+    /**
+     * Verify that an expected IPAddressTypeAvailabilityElement will be returned when parsing a
+     * buffer that contained an IP Address Type Availability ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseIPAddressTypeAvailabilityElement() throws Exception {
+        // Test data.
+        int ipAddressAvailability = IPAddressTypeAvailabilityElement.IPV4_PUBLIC << 2
+                | IPAddressTypeAvailabilityElement.IPV6_AVAILABLE;
+
+        // Setup expectation.
+        IPAddressTypeAvailabilityElement expected = new IPAddressTypeAvailabilityElement(
+                IPAddressTypeAvailabilityElement.IPV4_PUBLIC,
+                IPAddressTypeAvailabilityElement.IPV6_AVAILABLE);
+
+        ByteBuffer buffer = ByteBuffer.wrap(new byte[] {(byte) ipAddressAvailability});
+        assertEquals(expected,
+                ANQPParser.parseElement(Constants.ANQPElementType.ANQPIPAddrAvailability, buffer));
+    }
+
+    /**
+     * Verify that an expected DomainNameElement will be returned when parsing a buffer that
+     * contained a Domain Name ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseDomainNameElement() throws Exception {
+        String[] testNames = new String[] {"test.com", "abc.com"};
+        DomainNameElement expected = new DomainNameElement(Arrays.asList(testNames));
+
+        ByteBuffer buffer = ByteBuffer.wrap(getDomainNamePayload(testNames));
+        assertEquals(expected,
+                ANQPParser.parseElement(Constants.ANQPElementType.ANQPDomName, buffer));
+    }
+
+    /**
+     * Verify that an expected RoamingConsortiumElement will be returned when parsing a buffer that
+     * contained a Roaming Consortium ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseRoamingConsortium() throws Exception {
+        Long[] ois = new Long[] {0x12345678L, 0x5678L};
+        int[] oisLength = new int[] {4, 2};
+        RoamingConsortiumElement expected = new RoamingConsortiumElement(Arrays.asList(ois));
+
+        ByteBuffer buffer = ByteBuffer.wrap(getRoamingConsortiumPayload(ois, oisLength));
+        assertEquals(expected,
+                ANQPParser.parseElement(Constants.ANQPElementType.ANQPRoamingConsortium, buffer));
+    }
+
+    /**
+     * Verify that an expected NAIRealmElement will be returned when parsing a buffer that
+     * contained a NAI Realm ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseNAIRealmElement() throws Exception {
+        byte[][] testBytes = new byte[][] {NAIRealmDataTestUtil.TEST_REAML_WITH_UTF8_DATA_BYTES};
+        NAIRealmData[] realmDataList = new NAIRealmData[] {NAIRealmDataTestUtil.TEST_REALM_DATA};
+        NAIRealmElement expected = new NAIRealmElement(Arrays.asList(realmDataList));
+
+        ByteBuffer buffer = ByteBuffer.wrap(getNAIRealmPayload(testBytes));
+        assertEquals(expected,
+                ANQPParser.parseElement(Constants.ANQPElementType.ANQPNAIRealm, buffer));
+    }
+
+    /**
+     * Verify that an expected ThreeGPPNetworkElement will be returned when parsing a buffer that
+     * contained a 3GPP Network ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseThreeGPPNetworkElement() throws Exception {
+        byte[][] plmnBytes = new byte[][] {new byte[] {(byte) 0x87, 0x29, 0x10}};
+        String[] plmnList = new String[] {"789012"};
+
+        List<CellularNetwork> networkList = new ArrayList<>();
+        networkList.add(new CellularNetwork(Arrays.asList(plmnList)));
+        ThreeGPPNetworkElement expected = new ThreeGPPNetworkElement(networkList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(getThreeGPPNetworkPayload(
+                new byte[][] {CellularNetworkTestUtil.formatPLMNListIEI(plmnBytes)}));
+        assertEquals(expected,
+                ANQPParser.parseElement(Constants.ANQPElementType.ANQP3GPPNetwork, buffer));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a buffer that contained a
+     * vendor specific element that contained a non-Hotspot 2.0 ANQP-element.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseNonHS20VendorSpecificElement() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(
+                getVendorSpecificPayload(0x123456, 0x12, 1, new byte[0]));
+        ANQPParser.parseElement(Constants.ANQPElementType.ANQPVendorSpec, buffer);
+    }
+
+    /**
+     * Verify that an expected HSFriendlyNameElement will be returned when parsing a buffer that
+     * contained a vendor specific element that contained a Hotspot 2.0 friendly name
+     * ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseVendorSpecificElementWithHSFriendlyName() throws Exception {
+        String[] language = new String[] {"en"};
+        String[] text = new String[] {"test"};
+
+        // Setup expectation.
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(language[0], Locale.forLanguageTag(language[0]), text[0]));
+        HSFriendlyNameElement expected = new HSFriendlyNameElement(nameList);
+
+        byte[] hsFriendlyNameBytes = getHSFriendlyNamePayload(language, text);
+        byte[] data = getVendorSpecificPayload(
+                ANQPParser.VENDOR_SPECIFIC_HS20_OI, ANQPParser.VENDOR_SPECIFIC_HS20_TYPE,
+                Constants.HS_FRIENDLY_NAME, hsFriendlyNameBytes);
+        assertEquals(expected, ANQPParser.parseElement(
+                Constants.ANQPElementType.ANQPVendorSpec, ByteBuffer.wrap(data)));
+    }
+
+    /**
+     * Verify that an expected HSFriendlyNameElement will be returned when parsing a buffer that
+     * contained a Hotspot 2.0 Friendly Name ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseHSFrendlyNameElement() throws Exception {
+        // Test data.
+        String[] language = new String[] {"en"};
+        String[] text = new String[] {"test"};
+
+        // Setup expectation.
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(language[0], Locale.forLanguageTag(language[0]), text[0]));
+        HSFriendlyNameElement expected = new HSFriendlyNameElement(nameList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(getHSFriendlyNamePayload(language, text));
+        assertEquals(expected,
+                ANQPParser.parseHS20Element(Constants.ANQPElementType.HSFriendlyName, buffer));
+    }
+
+    /**
+     * Verify that an expected HSWanMetricsElement will be returned when parsing a buffer that
+     * contained a Hotspot 2.0 WAN Metrics ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseHSWANMetricsElement() throws Exception {
+        int status = HSWanMetricsElement.LINK_STATUS_UP;
+        boolean symmetric = false;
+        boolean capped = true;
+        long downlinkSpeed = 0x12453L;
+        long uplinkSpeed = 0x12423L;
+        int downlinkLoad = 0x12;
+        int uplinkLoad = 0x23;
+        int lmd = 0x2321;
+
+        HSWanMetricsElement expected = new HSWanMetricsElement(status, symmetric, capped,
+                downlinkSpeed, uplinkSpeed, downlinkLoad, uplinkLoad, lmd);
+
+        byte[] data = getHSWanMetricsPayload(status, symmetric, capped, downlinkSpeed,
+                uplinkSpeed, downlinkLoad, uplinkLoad, lmd);
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        assertEquals(expected,
+                ANQPParser.parseHS20Element(Constants.ANQPElementType.HSWANMetrics, buffer));
+    }
+
+    /**
+     * Verify that an expected HSConnectionCapabilityElement will be returned when parsing a
+     * buffer that contained a Hotspot 2.0 Connection Capability ANQP element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseHSConnectionCapabilityElement() throws Exception {
+        int protocol = 12;
+        int port = 23;
+        int status = ProtocolPortTuple.PROTO_STATUS_OPEN;
+
+        List<ProtocolPortTuple> statusList = new ArrayList<>();
+        statusList.add(new ProtocolPortTuple(protocol, port, status));
+        HSConnectionCapabilityElement expected = new HSConnectionCapabilityElement(statusList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(
+                getHSConnectionCapabilityPayload(protocol, port, status));
+        assertEquals(expected,
+                ANQPParser.parseHS20Element(Constants.ANQPElementType.HSConnCapability, buffer));
+    }
+
+    /**
+     * Verify that an expected RawByteElement will be returned when parsing a buffer that
+     * contained a Hotspot 2.0 OSU Providers element.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseHSOUSProvidersElement() throws Exception {
+        byte[] data = new byte[10];
+
+        RawByteElement expected =
+                new RawByteElement(Constants.ANQPElementType.HSOSUProviders, data);
+
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        assertEquals(expected,
+                ANQPParser.parseHS20Element(Constants.ANQPElementType.HSOSUProviders, buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/CellularNetworkTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/CellularNetworkTest.java
new file mode 100644
index 0000000..50ab189
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/CellularNetworkTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.CellularNetwork}.
+ */
+@SmallTest
+public class CellularNetworkTest {
+    private static final byte[] TEST_PLMN_BYTES_1 = new byte[] {0x12, 0x34, 0x56};
+    private static final String TEST_PLMN_STRING_1 = "214653";
+    private static final byte[] TEST_PLMN_BYTES_2 = new byte[] {0x13, (byte) 0xF9, 0x32};
+    private static final String TEST_PLMN_STRING_2 = "31923";
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithEmptyBuffer() throws Exception {
+        CellularNetwork.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that a null will be returned when parsing a buffer contained an unsupported IEI type.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithInvalidIEIType() throws Exception {
+        byte[][] plmnsData = new byte[][] {TEST_PLMN_BYTES_1, TEST_PLMN_BYTES_2};
+        byte[] testData = CellularNetworkTestUtil.formatPLMNListIEI(1, plmnsData);
+        assertNull(CellularNetwork.parse(ByteBuffer.wrap(testData)));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithIncompleteData() throws Exception {
+        byte[][] plmnsData = new byte[][] {TEST_PLMN_BYTES_1, TEST_PLMN_BYTES_2};
+        byte[] testData = CellularNetworkTestUtil.formatPLMNListIEI(plmnsData);
+        CellularNetwork.parse(ByteBuffer.wrap(testData, 0, testData.length - 1));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when IEI size and the PLMN count doesn't
+     * match.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithMismatchIEISizeAndPLMNCount() throws Exception {
+        byte[][] plmnsData = new byte[][] {TEST_PLMN_BYTES_1, TEST_PLMN_BYTES_2};
+        // Get test data with IEI size set to incorrect value.
+        byte[] testData = CellularNetworkTestUtil.formatPLMNListIEI(
+                CellularNetwork.IEI_TYPE_PLMN_LIST, plmnsData, true);
+        CellularNetwork.parse(ByteBuffer.wrap(testData));
+    }
+
+    /**
+     * Verify that the expected ProtocolPortTuple is returned when parsing a buffer contained
+     * the test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        byte[][] plmnsData = new byte[][] {TEST_PLMN_BYTES_1, TEST_PLMN_BYTES_2};
+        byte[] testData = CellularNetworkTestUtil.formatPLMNListIEI(plmnsData);
+
+        // Setup the expected CellularNetwork.
+        List<String> plmnList = new ArrayList<>();
+        plmnList.add(TEST_PLMN_STRING_1);
+        plmnList.add(TEST_PLMN_STRING_2);
+        CellularNetwork expected = new CellularNetwork(plmnList);
+
+        assertEquals(expected, CellularNetwork.parse(ByteBuffer.wrap(testData)));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/CellularNetworkTestUtil.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/CellularNetworkTestUtil.java
new file mode 100644
index 0000000..dad2919
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/CellularNetworkTestUtil.java
@@ -0,0 +1,94 @@
+/*
+ * 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.hotspot2.anqp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Utility class for formatting IEI (Information Element Identity) data for testing.
+ */
+public class CellularNetworkTestUtil {
+    /**
+     * Format and return PLMN List IEI with the given PLMN list data.
+     *
+     * @param plmnList The array of PLMN data
+     * @return byte[]
+     * @throws IOException
+     */
+    public static byte[] formatPLMNListIEI(byte[][] plmnList) throws IOException {
+        return formatPLMNListIEI(CellularNetwork.IEI_TYPE_PLMN_LIST, plmnList);
+    }
+
+    /**
+     * Format and return PLMN List IEI with the given IEI type and PLMN list data.  This
+     * allows the test to use an invalid IEI type for testing purpose.
+     *
+     * @param ieiType The IEI type
+     * @param plmnList The array of PLMN data
+     * @return byte[]
+     * @throws IOException
+     */
+    public static byte[] formatPLMNListIEI(int ieiType, byte[][] plmnList) throws IOException {
+        return formatPLMNListIEI(ieiType, plmnList, false);
+    }
+
+    /**
+     * Format and return PLMN List IEI with the given IEI type and PLMN list data.  This also
+     * allows the test to intentionally setting an incorrect size value.
+     *
+     * @param ieiType The IEI type
+     * @param plmnList The array of PLMN data
+     * @param setWrongSize Flag for setting incorrect IEI size
+     * @return byte[]
+     * @throws IOException
+     */
+    public static byte[] formatPLMNListIEI(int ieiType, byte[][] plmnList, boolean setWrongSize)
+            throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+
+        // Calculate the total bytes for all the PLMNs.
+        int plmnsSize = getDataSize(plmnList);
+
+        // Use incorrect size intentionally.
+        if (setWrongSize) plmnsSize -= 1;
+
+        stream.write((byte) ieiType);
+        // One extra byte for the PLMN count field.
+        stream.write((byte) ((plmnsSize + 1) & CellularNetwork.IEI_CONTENT_LENGTH_MASK));
+        stream.write((byte) plmnList.length);
+        for (byte[] plmn : plmnList) {
+            stream.write(plmn);
+        }
+
+        return stream.toByteArray();
+    }
+
+    /**
+     * Return the number of bytes in a 2D array.
+     *
+     * @param dataArray The 2D array
+     * @return The number of bytes in the 2D array
+     */
+    public static int getDataSize(byte[][] dataArray) {
+        int size = 0;
+        for (byte[] data : dataArray) {
+            size += data.length;
+        }
+        return size;
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/DomainNameElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/DomainNameElementTest.java
new file mode 100644
index 0000000..d17a7fa
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/DomainNameElementTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.DomainNameElement}.
+ */
+@SmallTest
+public class DomainNameElementTest {
+    private static final String TEST_DOMAIN_NAME1 = "test1.com";
+    private static final String TEST_DOMAIN_NAME2 = "test2.com";
+
+    /**
+     * Helper function for appending a Domain Name to an output stream.
+     *
+     * @param stream Stream to write to
+     * @param domain The domain name string
+     * @throws IOException
+     */
+    private void appendDomain(ByteArrayOutputStream stream, String domain) throws IOException {
+        byte[] domainBytes = domain.getBytes(StandardCharsets.ISO_8859_1);
+        stream.write((byte) domainBytes.length);
+        stream.write(domainBytes);
+    }
+
+    /**
+     * Helper function for generating test data.
+     *
+     * @return byte[] of data
+     * @throws IOException
+     */
+    private byte[] getTestData(String[] domains) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (String domain : domains) {
+            appendDomain(stream, domain);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Verify that a DomainNameElement with empty domain list will be returned when parsing an
+     * empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseEmptyBuffer() throws Exception {
+        assertTrue(DomainNameElement.parse(ByteBuffer.allocate(0)).getDomains().isEmpty());
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(new String[] {TEST_DOMAIN_NAME1}));
+        buffer.limit(buffer.remaining() - 1);
+        DomainNameElement.parse(buffer);
+    }
+
+    /**
+     * Verify that a DomainNameElement with expected domain list will be returned when parsing a
+     * buffer contained valid domain name list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithValidDomainNames() throws Exception {
+        byte[] testData = getTestData(new String[] {TEST_DOMAIN_NAME1, TEST_DOMAIN_NAME2});
+        ByteBuffer buffer = ByteBuffer.wrap(testData);
+
+        // Setup expected element.
+        List<String> domainList = new ArrayList<>();
+        domainList.add(TEST_DOMAIN_NAME1);
+        domainList.add(TEST_DOMAIN_NAME2);
+        DomainNameElement expectedElement = new DomainNameElement(domainList);
+
+        assertEquals(expectedElement, DomainNameElement.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSConnectionCapabilityElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSConnectionCapabilityElementTest.java
new file mode 100644
index 0000000..aef2b86
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSConnectionCapabilityElementTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.HSConnectionCapabilityElement}.
+ */
+@SmallTest
+public class HSConnectionCapabilityElementTest {
+    private static final ProtocolPortTuple TEST_TUPLE1 =
+            new ProtocolPortTuple(1, 2, ProtocolPortTuple.PROTO_STATUS_CLOSED);
+    private static final ProtocolPortTuple TEST_TUPLE2 =
+            new ProtocolPortTuple(3, 4, ProtocolPortTuple.PROTO_STATUS_OPEN);
+
+    /**
+     * Helper function for writing a ProtocolPortTuple into a buffer.
+     *
+     * @param buffer The buffer to write to
+     * @param tuple The tuple to write
+     */
+    private void appendProtocolPortTuple(ByteBuffer buffer, ProtocolPortTuple tuple) {
+        buffer.put((byte) tuple.getProtocol());
+        buffer.putShort((short) tuple.getPort());
+        buffer.put((byte) tuple.getStatus());
+    }
+
+    /**
+     * Helper function for generating a buffer with test data.
+     *
+     * @param tuples Tuples to put in the buffer
+     * @return {@link ByteBuffer}
+     */
+    private ByteBuffer getTestBuffer(ProtocolPortTuple[] tuples) {
+        ByteBuffer buffer = ByteBuffer.allocate(tuples.length * ProtocolPortTuple.RAW_BYTE_SIZE)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        for (ProtocolPortTuple tuple : tuples) {
+            appendProtocolPortTuple(buffer, tuple);
+        }
+        buffer.position(0);
+        return buffer;
+    }
+
+    /**
+     * Verify that a HSConnectionCapabilityElement with an empty status list will be returned
+     * when parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseEmptyBuffer() throws Exception {
+        HSConnectionCapabilityElement element =
+                HSConnectionCapabilityElement.parse(ByteBuffer.allocate(0));
+        assertTrue(element.getStatusList().isEmpty());
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a buffer without
+     * the complete tuple data (missing status field).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithLessThanMinimumSize() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(ProtocolPortTuple.RAW_BYTE_SIZE - 1);
+        buffer.put(new byte[ProtocolPortTuple.RAW_BYTE_SIZE - 1]);
+        buffer.position(0);
+        HSConnectionCapabilityElement.parse(buffer);
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a buffer that contained
+     * incomplete bytes for a tuple.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithIncompleteTupleBytes() throws Exception {
+        // Construct a buffer which will contained a tuple and an extra byte at the end.
+        ByteBuffer buffer = ByteBuffer.allocate(ProtocolPortTuple.RAW_BYTE_SIZE + 1);
+        appendProtocolPortTuple(buffer, TEST_TUPLE1);
+        buffer.put((byte) 0);
+        buffer.position(0);
+        HSConnectionCapabilityElement.parse(buffer);
+    }
+
+    /**
+     * Verify that the expected HSConnectionCapabilityElement is returned when parsing
+     * a buffer containing the test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        ByteBuffer buffer = getTestBuffer(new ProtocolPortTuple[] {TEST_TUPLE1, TEST_TUPLE2});
+
+        // Setup expected element.
+        List<ProtocolPortTuple> tupleList = new ArrayList<>();
+        tupleList.add(TEST_TUPLE1);
+        tupleList.add(TEST_TUPLE2);
+        HSConnectionCapabilityElement expected = new HSConnectionCapabilityElement(tupleList);
+
+        assertEquals(expected, HSConnectionCapabilityElement.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElementTest.java
new file mode 100644
index 0000000..e228e03
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSFriendlyNameElementTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.HSFriendlyNameElement}.
+ */
+@SmallTest
+public class HSFriendlyNameElementTest {
+    private static final String TEST_LANGUAGE = "en";
+    private static final Locale TEST_LOCALE = Locale.forLanguageTag(TEST_LANGUAGE);
+    private static final String TEST_OPERATOR_NAME1 = "Operator1";
+    private static final String TEST_OPERATOR_NAME2 = "Operator2";
+
+    /**
+     * Helper function for appending a Operator Name to an output stream.
+     *
+     * @param stream Stream to write to
+     * @param operator The name of the operator
+     * @throws IOException
+     */
+    private void appendOperatorName(ByteArrayOutputStream stream, String operator)
+            throws IOException {
+        byte[] nameBytes = operator.getBytes(StandardCharsets.UTF_8);
+        int length = I18Name.LANGUAGE_CODE_LENGTH + operator.length();
+        stream.write((byte) length);
+        stream.write(TEST_LANGUAGE.getBytes(StandardCharsets.US_ASCII));
+        stream.write(new byte[]{(byte) 0x0});  // Padding for language code.
+        stream.write(nameBytes);
+    }
+
+    /**
+     * Helper function for generating test data.
+     *
+     * @return byte[] of data
+     * @throws IOException
+     */
+    private byte[] getTestData(String[] names) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (String name : names) {
+            appendOperatorName(stream, name);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Verify that HSFriendlyNameElement with a empty operator name list will be returned when
+     * parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithEmptyBuffer() throws Exception {
+        assertTrue(HSFriendlyNameElement.parse(ByteBuffer.allocate(0)).getNames().isEmpty());
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithTruncatedByte() throws Exception {
+        byte[] testData = getTestData(new String[] {TEST_OPERATOR_NAME1});
+        // Truncate a byte at the end.
+        ByteBuffer buffer = ByteBuffer.allocate(testData.length - 1);
+        buffer.put(testData, 0, testData.length - 1);
+        buffer.position(0);
+        HSFriendlyNameElement.parse(buffer);
+    }
+
+    /**
+     * Verify that an expected HSFriendlyNameElement will be returned when parsing a buffer
+     * containing the default test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithDefaultTestData() throws Exception {
+        byte[] testData = getTestData(new String[] {TEST_OPERATOR_NAME1, TEST_OPERATOR_NAME2});
+        ByteBuffer buffer = ByteBuffer.allocate(testData.length);
+        buffer.put(testData);
+        buffer.position(0);
+
+        // Setup expected element.
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_OPERATOR_NAME1));
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_OPERATOR_NAME2));
+        HSFriendlyNameElement expectedElement = new HSFriendlyNameElement(nameList);
+
+        assertEquals(expectedElement, HSFriendlyNameElement.parse(buffer));
+    }
+
+    /**
+     * Verify that an expected HSFriendlyNameElement will be returned when parsing a buffer
+     * containing a operator name with the maximum length.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithMaxLengthOperatoreName() throws Exception {
+        // Operator name with the maximum length.
+        byte[] textData = new byte[HSFriendlyNameElement.MAXIMUM_OPERATOR_NAME_LENGTH];
+        Arrays.fill(textData, (byte) 'a');
+        String text = new String(textData);
+        byte[] testData = getTestData(new String[] {text});
+        ByteBuffer buffer = ByteBuffer.allocate(testData.length);
+        buffer.put(testData);
+        buffer.position(0);
+
+        // Setup expected element.
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, text));
+        HSFriendlyNameElement expectedElement = new HSFriendlyNameElement(nameList);
+
+        assertEquals(expectedElement, HSFriendlyNameElement.parse(buffer));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a buffer containing a
+     * operator name that exceeds the maximum length.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithOperatorNameLengthExceedMax() throws Exception {
+        byte[] textData = new byte[HSFriendlyNameElement.MAXIMUM_OPERATOR_NAME_LENGTH + 1];
+        Arrays.fill(textData, (byte) 'a');
+        String text = new String(textData);
+        byte[] testData = getTestData(new String[] {text});
+        ByteBuffer buffer = ByteBuffer.allocate(testData.length);
+        buffer.put(testData);
+        buffer.position(0);
+        HSFriendlyNameElement.parse(buffer);
+    }
+
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElementTest.java
new file mode 100644
index 0000000..8c53fe3
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/HSWanMetricsElementTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.HSWanMetricsElement}.
+ */
+@SmallTest
+public class HSWanMetricsElementTest {
+    private static final int TEST_LINK_STATUS = HSWanMetricsElement.LINK_STATUS_UP;
+    private static final boolean TEST_SYMMETRIC_LINK = true;
+    private static final boolean TEST_AT_CAPACITY = true;
+    private static final long TEST_DOWNLINK_SPEED = 0x1234556L;
+    private static final long TEST_UPLINK_SPEED = 0x342343L;
+    private static final int TEST_DOWNLINK_LOAD = 0x23;
+    private static final int TEST_UPLINK_LOAD = 0x45;
+    private static final int TEST_LMD = 0x2132;
+
+    /**
+     * Helper function for generating a ByteBuffer with the test data.
+     *
+     * @return {@link ByteBuffer}
+     */
+    private ByteBuffer getTestBuffer() {
+        ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        int wanInfo = TEST_LINK_STATUS & HSWanMetricsElement.LINK_STATUS_MASK;
+        if (TEST_SYMMETRIC_LINK) wanInfo |= HSWanMetricsElement.SYMMETRIC_LINK_MASK;
+        if (TEST_AT_CAPACITY) wanInfo |= HSWanMetricsElement.AT_CAPACITY_MASK;
+        buffer.put((byte) wanInfo);
+        buffer.putInt((int) (TEST_DOWNLINK_SPEED & 0xFFFFFFFFL));
+        buffer.putInt((int) (TEST_UPLINK_SPEED & 0xFFFFFFFFL));
+        buffer.put((byte) (TEST_DOWNLINK_LOAD & 0xFF));
+        buffer.put((byte) (TEST_UPLINK_LOAD & 0xFF));
+        buffer.putShort((short) (TEST_LMD & 0xFFFF));
+        buffer.position(0);
+        return buffer;
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseEmptyBuffer() throws Exception {
+        HSWanMetricsElement.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when a buffer with size less than the
+     * expected.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithLessThanExpectedSize() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE - 1);
+        buffer.put(new byte[HSWanMetricsElement.EXPECTED_BUFFER_SIZE - 1]);
+        buffer.position(0);
+        HSWanMetricsElement.parse(buffer);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when a buffer with size more than the
+     * expected.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithMoreThanExpectedSize() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(HSWanMetricsElement.EXPECTED_BUFFER_SIZE + 1);
+        buffer.put(new byte[HSWanMetricsElement.EXPECTED_BUFFER_SIZE + 1]);
+        buffer.position(0);
+        HSWanMetricsElement.parse(buffer);
+    }
+
+    /**
+     * Verify that the expected HSWanMetricsElement is returned when parsing
+     * a buffer containing the test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        ByteBuffer buffer = getTestBuffer();
+        HSWanMetricsElement expectedElement = new HSWanMetricsElement(
+                TEST_LINK_STATUS, TEST_SYMMETRIC_LINK, TEST_AT_CAPACITY,
+                TEST_DOWNLINK_SPEED, TEST_UPLINK_SPEED, TEST_DOWNLINK_LOAD,
+                TEST_UPLINK_LOAD, TEST_LMD);
+        assertEquals(expectedElement, HSWanMetricsElement.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/I18NameTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/I18NameTest.java
new file mode 100644
index 0000000..6920e58
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/I18NameTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.I18Name}.
+ */
+@SmallTest
+public class I18NameTest {
+    private static final String TEST_LANGUAGE = "en";
+    private static final Locale TEST_LOCALE = Locale.forLanguageTag(TEST_LANGUAGE);
+    private static final String TEST_TEXT = "Hello World";
+
+    /**
+     * Helper function for returning byte array containing test data.
+     *
+     * @param language The language code string
+     * @param text The text string
+     * @return byte[]
+     * @throws IOException
+     */
+    private byte[] getTestData(String language, String text) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        stream.write(language.getBytes(StandardCharsets.US_ASCII));
+        stream.write(new byte[]{(byte) 0x0});  // Padding for language code.
+        stream.write(text.getBytes(StandardCharsets.UTF_8));
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating default test data. The test data include the language code
+     * and text field.
+     *
+     * @return byte[] of data
+     * @throws IOException
+     */
+    private byte[] getDefaultTestData() throws IOException {
+        return getTestData(TEST_LANGUAGE, TEST_TEXT);
+    }
+
+    /**
+     * Helper function for returning a buffer containing a I18Name test data.
+     *
+     * @Param data The byte array of I18Name data
+     * @param length The length value to set in the I18Name header
+     * @return {@link ByteBuffer}
+     * @throws IOException
+     */
+    private ByteBuffer getTestBuffer(byte[] data, int length) throws IOException {
+        // Allocate extra byte for storing the length field.
+        ByteBuffer buffer = ByteBuffer.allocate(data.length + 1);
+        buffer.put((byte) length);
+        buffer.put(data);
+        buffer.position(0);
+        return buffer;
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        I18Name.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when the length field is set to more
+     * than the actual buffer size.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        byte[] data = getDefaultTestData();
+        ByteBuffer buffer = getTestBuffer(data, data.length);
+        buffer.limit(buffer.remaining() - 1);
+        I18Name.parse(buffer);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when the length field is set to less than
+     * the minimum.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithLengthLessThanMinimum() throws Exception {
+        byte[] data = getDefaultTestData();
+        I18Name.parse(getTestBuffer(data, I18Name.MINIMUM_LENGTH - 1));
+    }
+
+    /**
+     * Verify that the expected I18Name will be returned when parsing a buffer contained the
+     * predefined test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithDefaultTestData() throws Exception {
+        byte[] data = getDefaultTestData();
+        I18Name actualName = I18Name.parse(getTestBuffer(data, data.length));
+        I18Name expectedName = new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_TEXT);
+        assertEquals(expectedName, actualName);
+    }
+
+    /**
+     * Verify that the expected I18Name will be returned when parsing a buffer contained
+     * a non-English (French) language.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithFrenchData() throws Exception {
+        // Test data for French.
+        String language = "fr";
+        String text = "Hello World";
+        byte[] data = getTestData(language, text);
+        I18Name actualName = I18Name.parse(getTestBuffer(data, data.length));
+        I18Name expectedName = new I18Name(language, Locale.forLanguageTag(language), text);
+        assertEquals(expectedName, actualName);
+    }
+
+    /**
+     * Verify that an I18Name with an empty text will be returned when parsing a buffer contained
+     * an empty text field.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithEmptyText() throws Exception {
+        byte[] data = getTestData(TEST_LANGUAGE, "");
+        I18Name actualName = I18Name.parse(getTestBuffer(data, data.length));
+        I18Name expectedName = new I18Name(TEST_LANGUAGE, TEST_LOCALE, "");
+        assertEquals(expectedName, actualName);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElementTest.java
new file mode 100644
index 0000000..bbe8148
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/IPAddressTypeAvailabilityElementTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.IPAddressTypeAvailabilityElement}.
+ */
+@SmallTest
+public class IPAddressTypeAvailabilityElementTest {
+    private static final int TEST_IPV4_AVAILABILITY =
+            IPAddressTypeAvailabilityElement.IPV4_PUBLIC;
+    private static final int TEST_IPV6_AVAILABILITY =
+            IPAddressTypeAvailabilityElement.IPV6_AVAILABLE;
+
+    private static int getIPAvailability() {
+        return (TEST_IPV4_AVAILABILITY << 2) | TEST_IPV6_AVAILABILITY;
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferEmptyBuffer() throws Exception {
+        IPAddressTypeAvailabilityElement.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing an buffer containing excess
+     * data.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithExcessData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(
+                IPAddressTypeAvailabilityElement.EXPECTED_BUFFER_LENGTH + 1);
+        buffer.put((byte) getIPAvailability());
+        buffer.put((byte) 0);    // Excess data.
+        buffer.position(0);
+        IPAddressTypeAvailabilityElement.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that the expected IPAddressTypeAvailabilityElement is returned when parsing
+     * a buffer containing the test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(
+                IPAddressTypeAvailabilityElement.EXPECTED_BUFFER_LENGTH);
+        buffer.put((byte) getIPAvailability());
+        buffer.position(0);
+
+        IPAddressTypeAvailabilityElement expectedElement = new IPAddressTypeAvailabilityElement(
+                TEST_IPV4_AVAILABILITY, TEST_IPV6_AVAILABILITY);
+        assertEquals(expectedElement, IPAddressTypeAvailabilityElement.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmDataTest.java
new file mode 100644
index 0000000..e600e3b
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmDataTest.java
@@ -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.
+ */
+
+package com.android.server.wifi.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.NAIRealmData}.
+ */
+@SmallTest
+public class NAIRealmDataTest {
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        NAIRealmData.parse(ByteBuffer.wrap(new byte[0]));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(NAIRealmDataTestUtil.TEST_REAML_WITH_UTF8_DATA_BYTES);
+        buffer.limit(buffer.remaining() - 1);
+        NAIRealmData.parse(buffer);
+    }
+
+    /**
+     * Verify that an expected NAIRealmData will be returned when parsing a buffer contained
+     * the test data with realm string encoded using UTF8.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithUTF8EncodedNAIRealmData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(NAIRealmDataTestUtil.TEST_REAML_WITH_UTF8_DATA_BYTES);
+        assertEquals(NAIRealmDataTestUtil.TEST_REALM_DATA, NAIRealmData.parse(buffer));
+    }
+
+    /**
+     * Verify that the expected NAIRealmData will be returned when parsing a buffer contained
+     * the test data with realm string encoded using non-UTF8.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithNonUTF8EncodedNAIRealmData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(
+                NAIRealmDataTestUtil.TEST_REAML_WITH_NON_UTF8_DATA_BYTES);
+        assertEquals(NAIRealmDataTestUtil.TEST_REALM_DATA, NAIRealmData.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmDataTestUtil.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmDataTestUtil.java
new file mode 100644
index 0000000..1ce3044
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmDataTestUtil.java
@@ -0,0 +1,119 @@
+/*
+ * 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.hotspot2.anqp;
+
+import android.net.wifi.EAPConstants;
+
+import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
+import com.android.server.wifi.hotspot2.anqp.eap.CredentialType;
+import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class containing test data for NAI Realm Data.
+ */
+public class NAIRealmDataTestUtil {
+    /**
+     * Raw bytes for EAP Method.
+     */
+    private static final byte[] TEST_EAP_METHOD_BYTES =
+            new byte[] {0x05 /* length */, 0x0D /* EAP_TLS */, 0x01 /* Auth Param Count */,
+                        0x05 /* CredentialType */, 0x01, 0x02 /* USIM */};
+
+    /**
+     * NAI Realm strings.
+     */
+    private static final String[] TEST_REALMS = new String[] {"test1", "test2"};
+
+    /**
+     * Setup expected EAPMethod list.
+     */
+    private static final Map<Integer, Set<AuthParam>> TEST_EAP_METHOD_AUTH_PARAMS =
+            new HashMap<>();
+    private static final Set<AuthParam> TEST_EAP_METHOD_CREDENTIAL_TYPE_PARAMS = new HashSet<>();
+    private static final List<EAPMethod> TEST_EAP_METHOD_LIST = new ArrayList<>();
+    static {
+        TEST_EAP_METHOD_CREDENTIAL_TYPE_PARAMS.add(new CredentialType(
+                AuthParam.PARAM_TYPE_CREDENTIAL_TYPE, CredentialType.CREDENTIAL_TYPE_USIM));
+        TEST_EAP_METHOD_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_CREDENTIAL_TYPE,
+                TEST_EAP_METHOD_CREDENTIAL_TYPE_PARAMS);
+
+        TEST_EAP_METHOD_LIST.add(new EAPMethod(EAPConstants.EAP_TLS, TEST_EAP_METHOD_AUTH_PARAMS));
+    }
+
+    /**
+     * Setup expected NAIRealmData.
+     */
+    public static final NAIRealmData TEST_REALM_DATA =
+            new NAIRealmData(Arrays.asList(TEST_REALMS), TEST_EAP_METHOD_LIST);
+
+    public static byte[] TEST_REAML_WITH_UTF8_DATA_BYTES = formatNAIRealmData(true);
+    public static byte[] TEST_REAML_WITH_NON_UTF8_DATA_BYTES = formatNAIRealmData(false);
+
+    /**
+     * Helper function for returning raw bytes of NAI Realm Data (including the length field) for
+     * testing.
+     *
+     * @param utfEncoding Flag indicating the UTF encoding of the realm string
+     * @return byte[]
+     */
+    private static byte[] formatNAIRealmData(boolean utfEncoding) {
+        try {
+            byte[] realmData = getNAIRealmData(utfEncoding);
+            ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            // Realm Data length in Little-Endian.
+            stream.write((byte) realmData.length);
+            stream.write((byte) realmData.length >> 8);
+            stream.write(realmData);
+            return stream.toByteArray();
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Helper function for returning raw bytes of NAI Realm Data payload.
+     *
+     * @param utfEncoding Flag indicating the encoding of NAI Realm string
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getNAIRealmData(boolean utfEncoding) throws IOException {
+        String realmsStr = String.join(NAIRealmData.NAI_REALM_STRING_SEPARATOR, TEST_REALMS);
+        byte[] realmStrData = realmsStr.getBytes(
+                utfEncoding ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII);
+
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        // NAI Realm Encoding byte.
+        stream.write((byte) (utfEncoding ? NAIRealmData.NAI_ENCODING_UTF8_MASK : 0));
+        stream.write((byte) realmStrData.length);
+        stream.write(realmStrData);
+        stream.write((byte) 1);    // EAP Method count
+        stream.write(TEST_EAP_METHOD_BYTES);
+        return stream.toByteArray();
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmElementTest.java
new file mode 100644
index 0000000..6b7aa84
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/NAIRealmElementTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.NAIRealmElement}.
+ */
+@SmallTest
+public class NAIRealmElementTest {
+    /**
+     * Helper function for returning a ByteBuffer containing raw bytes for NAI Realm Element
+     * with specified number of NAI Realm Data.
+     *
+     * @param dataCount The number of NAI Realm Data to be added to the buffer
+     * @return {@link ByteBuffer}
+     */
+    private static ByteBuffer getTestBufferWithNAIRealmData(int dataCount) {
+        int dataLength = NAIRealmDataTestUtil.TEST_REAML_WITH_UTF8_DATA_BYTES.length * dataCount;
+       // 2-bytes for the NAI Realm Data count header.
+        ByteBuffer buffer = ByteBuffer.allocate(dataLength + 2).order(ByteOrder.LITTLE_ENDIAN);
+        buffer.putShort((short) dataCount);
+        for (int i = 0; i < dataCount; i++) {
+            buffer.put(NAIRealmDataTestUtil.TEST_REAML_WITH_UTF8_DATA_BYTES);
+        }
+        buffer.position(0);
+        return buffer;
+    }
+
+    /**
+     * Verify that a NAIRealmElement with an empty NAIRealmData list will be returned when parsing
+     * from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseEmptyBuffer() throws Exception {
+        assertTrue(NAIRealmElement.parse(
+                ByteBuffer.wrap(new byte[0])).getRealmDataList().isEmpty());
+    }
+
+    /**
+     * Verify that an expected NAIRealmElement will be returned when parsing a buffer containing
+     * a NAI Realm Element with single NAI Realm Data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithSingleNAIRealmData() throws Exception {
+        // Setup expected NAIRealmElement.
+        List<NAIRealmData> realmDataList = new ArrayList<>();
+        realmDataList.add(NAIRealmDataTestUtil.TEST_REALM_DATA);
+        NAIRealmElement expected = new NAIRealmElement(realmDataList);
+
+        assertEquals(expected, NAIRealmElement.parse(getTestBufferWithNAIRealmData(1)));
+    }
+
+    /**
+     * Verify that an expected NAIRealmElement will be returned when parsing a buffer containing
+     * a NAI Realm Element with multiple NAI Realm Data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithMultipleNAIRealmData() throws Exception {
+        // Setup expected NAIRealmElement.
+        List<NAIRealmData> realmDataList = new ArrayList<>();
+        realmDataList.add(NAIRealmDataTestUtil.TEST_REALM_DATA);
+        realmDataList.add(NAIRealmDataTestUtil.TEST_REALM_DATA);
+        NAIRealmElement expected = new NAIRealmElement(realmDataList);
+
+        assertEquals(expected, NAIRealmElement.parse(getTestBufferWithNAIRealmData(2)));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ProtocolPortTupleTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ProtocolPortTupleTest.java
new file mode 100644
index 0000000..b4bfaf1
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ProtocolPortTupleTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.ProtocolPortTuple}.
+ */
+@SmallTest
+public class ProtocolPortTupleTest {
+    private static final int TEST_PROTOCOL = 1;
+    private static final int TEST_PORT = 2;
+    private static final int TEST_STATUS = ProtocolPortTuple.PROTO_STATUS_CLOSED;
+
+    /**
+     * Helper function for generating a buffer with test data.
+     *
+     * @param protocol Protocol value
+     * @param port Port value
+     * @param status Status value
+     * @return {@link ByteBuffer}
+     */
+    private ByteBuffer getTestBuffer(int protocol, int port, int status) {
+        ByteBuffer buffer = ByteBuffer.allocate(ProtocolPortTuple.RAW_BYTE_SIZE)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put((byte) protocol);
+        buffer.putShort((short) port);
+        buffer.put((byte) status);
+        buffer.position(0);
+        return buffer;
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        ProtocolPortTuple.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a buffer without
+     * the complete tuple data (missing status field).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithIncompleteData() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(ProtocolPortTuple.RAW_BYTE_SIZE - 1);
+        buffer.put(new byte[ProtocolPortTuple.RAW_BYTE_SIZE - 1]);
+        buffer.position(0);
+        ProtocolPortTuple.parse(buffer);
+    }
+
+    /**
+     * Verify that the expected ProtocolPortTuple is returned when parsing a buffer contained
+     * the test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        ByteBuffer buffer = getTestBuffer(TEST_PROTOCOL, TEST_PORT, TEST_STATUS);
+        ProtocolPortTuple expected = new ProtocolPortTuple(TEST_PROTOCOL, TEST_PORT, TEST_STATUS);
+        assertEquals(expected, ProtocolPortTuple.parse(buffer));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/RawByteElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/RawByteElementTest.java
new file mode 100644
index 0000000..e61797a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/RawByteElementTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.RawByteElement}.
+ */
+@SmallTest
+public class RawByteElementTest {
+    private static final Constants.ANQPElementType TEST_ELEMENT_ID =
+            Constants.ANQPElementType.HSOSUProviders;
+
+    /**
+     * Verify that a RawByteElement with an empty payload will be returned when parsing
+     * an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseEmptyBuffer() throws Exception {
+        byte[] data = new byte[0];
+        RawByteElement actual = RawByteElement.parse(TEST_ELEMENT_ID, ByteBuffer.wrap(data));
+        RawByteElement expected = new RawByteElement(TEST_ELEMENT_ID, data);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Verify that the expected RawByteElement will be returned when parsing a non-empty
+     * buffer.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseNonEmptyBuffer() throws Exception {
+        byte[] data = new byte[10];
+        RawByteElement actual = RawByteElement.parse(TEST_ELEMENT_ID, ByteBuffer.wrap(data));
+        RawByteElement expected = new RawByteElement(TEST_ELEMENT_ID, data);
+        assertEquals(expected, actual);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElementTest.java
new file mode 100644
index 0000000..4e3bd1f
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/RoamingConsortiumElementTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.RoamingConsortiumElement}.
+ */
+@SmallTest
+public class RoamingConsortiumElementTest {
+    // Default test data.  Each test data contained a pair indicating the number of bytes for the
+    // OI and the value of the OI.
+    private static final Pair<Integer, Long> TEST_OI1 = new Pair<Integer, Long>(1, 0x12L);
+    private static final Pair<Integer, Long> TEST_OI2 = new Pair<Integer, Long>(2, 0x1234L);
+    private static final Pair<Integer, Long> TEST_OI3 = new Pair<Integer, Long>(4, 0x12345678L);
+    private static final Pair<Integer, Long> TEST_OI4 = new Pair<Integer, Long>(8, 0x1234567890L);
+
+    /**
+     * Helper function for appending an OI field to the given output stream.
+     *
+     * @param stream The output stream to write to
+     * @param OI The OI to write to the output stream
+     */
+    private void appendOI(ByteArrayOutputStream stream, Pair<Integer, Long> oi) {
+        stream.write(oi.first.byteValue());
+        // Write the OI data in big-endian.
+        for (int i = oi.first.intValue() - 1; i >= 0; i--) {
+            stream.write((byte) ((oi.second.longValue() >> i * Byte.SIZE) & 0xFF));
+        }
+    }
+    /**
+     * Helper function for generating test data with the provided OIs.
+     *
+     * @param OIs The OIs to generate the data with
+     * @return byte[] of data
+     * @throws IOException
+     */
+    private byte[] getTestData(List<Pair<Integer, Long>> ois) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        for (Pair<Integer, Long> oi : ois) {
+            appendOI(stream, oi);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Helper function for generating test data using the predefined OIs.
+     *
+     * @return byte[] of data
+     * @throws IOException
+     */
+    private byte[] getDefaultTestData() throws IOException {
+        List<Pair<Integer, Long>> oiList = new ArrayList<>();
+        oiList.add(TEST_OI1);
+        oiList.add(TEST_OI2);
+        oiList.add(TEST_OI3);
+        oiList.add(TEST_OI4);
+        return getTestData(oiList);
+    }
+
+    /**
+     * Helper function for creating a RoamingConsortiumElement using the predefined OIs.
+     *
+     * @return {@link RoamingConsortiumElement}
+     */
+    private RoamingConsortiumElement getDefaultElement() {
+        List<Long> oiList = new ArrayList<>();
+        oiList.add(TEST_OI1.second);
+        oiList.add(TEST_OI2.second);
+        oiList.add(TEST_OI3.second);
+        oiList.add(TEST_OI4.second);
+        return new RoamingConsortiumElement(oiList);
+    }
+
+    /**
+     * Verify that no exception will be thrown when parsing an empty buffer and the returned
+     * RoamingConsortiumElement will contained an empty list of OIs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseEmptyBuffer() throws Exception {
+        RoamingConsortiumElement element = RoamingConsortiumElement.parse(ByteBuffer.allocate(0));
+        assertTrue(element.getOIs().isEmpty());
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getDefaultTestData());
+        buffer.limit(buffer.remaining() - 1);
+        RoamingConsortiumElement.parse(buffer);
+    }
+
+    /**
+     * Verify that an expected RoamingConsortiumElement will be returned when parsing a buffer
+     * containing valid data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithDefaultTestData() throws Exception {
+        // Setup expected element.
+        RoamingConsortiumElement expectedElement = getDefaultElement();
+
+        ByteBuffer buffer = ByteBuffer.wrap(getDefaultTestData());
+        assertEquals(expectedElement, RoamingConsortiumElement.parse(buffer));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a buffer contained an OI length
+     * that's less than minimum allowed.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithOILengthLessThanMinimum() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(1);
+        buffer.put((byte) (RoamingConsortiumElement.MINIMUM_OI_LENGTH - 1));
+        buffer.position(0);
+        RoamingConsortiumElement.parse(buffer);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a buffer contained an OI length
+     * that's more than maximum allowed.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithOILengthMoreThanMaximum() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(1);
+        buffer.put((byte) (RoamingConsortiumElement.MAXIMUM_OI_LENGTH + 1));
+        buffer.position(0);
+        RoamingConsortiumElement.parse(buffer);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElementTest.java
new file mode 100644
index 0000000..02d45ef
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/ThreeGPPNetworkElementTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement}.
+ */
+@SmallTest
+public class ThreeGPPNetworkElementTest {
+    private static final byte[][] TEST_NETWORK1_PLMN_BYTES =
+            new byte[][] { new byte[] {0x21, 0x63, 0x54},
+                           new byte[] {0x43, (byte) 0x85, 0x76} };
+    private static final List<String> TEST_NETWORK1_PLMN_LIST = new ArrayList<>();
+    static {
+        TEST_NETWORK1_PLMN_LIST.add("123456");
+        TEST_NETWORK1_PLMN_LIST.add("345678");
+    }
+    private static final CellularNetwork TEST_NETWORK1 =
+            new CellularNetwork(TEST_NETWORK1_PLMN_LIST);
+
+    private static final byte[][] TEST_NETWORK2_PLMN_BYTES =
+            new byte[][] { new byte[] {(byte) 0x87, 0x29, 0x10},
+                           new byte[] {0x62, (byte) 0xF5, 0x73} };
+    private static final List<String> TEST_NETWORK2_PLMN_LIST = new ArrayList<>();
+    static {
+        TEST_NETWORK2_PLMN_LIST.add("789012");
+        TEST_NETWORK2_PLMN_LIST.add("26537");
+    }
+    private static final CellularNetwork TEST_NETWORK2 =
+            new CellularNetwork(TEST_NETWORK2_PLMN_LIST);
+
+    /**
+     * Helper function for generating test data.
+     *
+     * @param version The GUD version number
+     * @param ieiList The array containing IEI data
+     * @return byte[]
+     * @throws IOException
+     */
+    private static byte[] getTestData(int version, byte[][] ieiList)
+            throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        int totalIeiSize = CellularNetworkTestUtil.getDataSize(ieiList);
+        stream.write((byte) version);
+        stream.write((byte) totalIeiSize);
+        for (byte[] iei : ieiList) {
+            stream.write(iei);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithEmptyBuffer() throws Exception {
+        ThreeGPPNetworkElement.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing an buffer contained
+     * an unsupported version number.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithUnsupportedVersionNumber() throws Exception {
+        byte[][] testIeiList = new byte[][] {
+            CellularNetworkTestUtil.formatPLMNListIEI(TEST_NETWORK1_PLMN_BYTES) };
+        byte[] testData = getTestData(1, testIeiList);
+        ThreeGPPNetworkElement.parse(ByteBuffer.wrap(testData));
+    }
+
+    /**
+     * Verify that Protocol will be thrown when parsing a truncated buffer (missing a
+     * byte at the end), which will cause a inconsistency between the length value and
+     * the buffer size.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithIncompleteData() throws Exception {
+        byte[][] testIeiList = new byte[][] {
+            CellularNetworkTestUtil.formatPLMNListIEI(TEST_NETWORK1_PLMN_BYTES) };
+        byte[] testData = getTestData(ThreeGPPNetworkElement.GUD_VERSION_1, testIeiList);
+        ThreeGPPNetworkElement.parse(ByteBuffer.wrap(testData, 0, testData.length - 1));
+    }
+
+    /**
+     * Verify that the expected ThreeGPPNetworkElement is returned when parsing a buffer contained
+     * the test data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData() throws Exception {
+        byte[][] testIeiList = new byte[][] {
+            CellularNetworkTestUtil.formatPLMNListIEI(TEST_NETWORK1_PLMN_BYTES),
+            CellularNetworkTestUtil.formatPLMNListIEI(TEST_NETWORK2_PLMN_BYTES) };
+        byte[] testData = getTestData(ThreeGPPNetworkElement.GUD_VERSION_1, testIeiList);
+
+        // Setup the expected ThreeGPPNetworkElement.
+        List<CellularNetwork> networkList = new ArrayList<>();
+        networkList.add(TEST_NETWORK1);
+        networkList.add(TEST_NETWORK2);
+        ThreeGPPNetworkElement expected = new ThreeGPPNetworkElement(networkList);
+
+        assertEquals(expected, ThreeGPPNetworkElement.parse(ByteBuffer.wrap(testData)));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/VenueNameElementTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/VenueNameElementTest.java
new file mode 100644
index 0000000..407e7bf
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/VenueNameElementTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.hotspot2.anqp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.VenueNameElement}.
+ */
+@SmallTest
+public class VenueNameElementTest {
+    private static final String TEST_LANGUAGE = "en";
+    private static final Locale TEST_LOCALE = Locale.forLanguageTag(TEST_LANGUAGE);
+    private static final String TEST_VENUE_NAME1 = "Venue1";
+    private static final String TEST_VENUE_NAME2 = "Venue2";
+
+    /**
+     * Helper function for appending a Venue Name to an output stream.
+     *
+     * @param stream Stream to write to
+     * @param venue The venue name string
+     * @throws IOException
+     */
+    private void appendVenue(ByteArrayOutputStream stream, String venue) throws IOException {
+        byte[] venueBytes = venue.getBytes(StandardCharsets.UTF_8);
+        int length = I18Name.LANGUAGE_CODE_LENGTH + venue.length();
+        stream.write((byte) length);
+        stream.write(TEST_LANGUAGE.getBytes(StandardCharsets.US_ASCII));
+        stream.write(new byte[]{(byte) 0x0});  // Padding for language code.
+        stream.write(venueBytes);
+    }
+
+    /**
+     * Helper function for generating test data.
+     *
+     * @return byte[] of data
+     * @throws IOException
+     */
+    private byte[] getTestData(String[] names) throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        // Venue info data not currently used.
+        stream.write(new byte[VenueNameElement.VENUE_INFO_LENGTH]);
+        for (String name : names) {
+            appendVenue(stream, name);
+        }
+        return stream.toByteArray();
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        VenueNameElement.parse(ByteBuffer.allocate(0));
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(new String[] {TEST_VENUE_NAME1}));
+        // Truncate a byte at the end.
+        buffer.limit(buffer.remaining() - 1);
+        VenueNameElement.parse(buffer);
+    }
+
+    /**
+     * Verify that a VenueNameElement with empty name list will be returned when parsing a buffer
+     * contained no venue name (only contained the venue info data).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithEmptyVenueName() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(new String[0]));
+        assertTrue(VenueNameElement.parse(buffer).getNames().isEmpty());
+    }
+    /**
+     * Verify that an expected VenueNameElement will be returned when parsing a buffer contained
+     * valid Venue Name data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithValidVenueNames() throws Exception {
+        // Setup expected element.
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_VENUE_NAME1));
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, TEST_VENUE_NAME2));
+        VenueNameElement expectedElement = new VenueNameElement(nameList);
+
+        ByteBuffer buffer = ByteBuffer.wrap(
+                getTestData(new String[] {TEST_VENUE_NAME1, TEST_VENUE_NAME2}));
+        assertEquals(expectedElement, VenueNameElement.parse(buffer));
+    }
+
+    /**
+     * Verify that an expected VenueNameElement will be returned when parsing a buffer
+     * contained a venue name with the maximum length.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithMaxLengthVenueName() throws Exception {
+        // Venue name with maximum length.
+        byte[] textData = new byte[VenueNameElement.MAXIMUM_VENUE_NAME_LENGTH];
+        Arrays.fill(textData, (byte) 'a');
+        String text = new String(textData);
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(new String[] {text}));
+
+        // Setup expected element.
+        List<I18Name> nameList = new ArrayList<>();
+        nameList.add(new I18Name(TEST_LANGUAGE, TEST_LOCALE, text));
+        VenueNameElement expectedElement = new VenueNameElement(nameList);
+
+        assertEquals(expectedElement, VenueNameElement.parse(buffer));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a buffer contained a
+     * venue name that exceeds the maximum length.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithVenueNameLengthExceedMax() throws Exception {
+        byte[] textData = new byte[VenueNameElement.MAXIMUM_VENUE_NAME_LENGTH + 1];
+        Arrays.fill(textData, (byte) 'a');
+        String text = new String(textData);
+        ByteBuffer buffer = ByteBuffer.wrap(getTestData(new String[] {text}));
+        VenueNameElement.parse(buffer);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/CredentialTypeTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/CredentialTypeTest.java
new file mode 100644
index 0000000..14be73d
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/CredentialTypeTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.eap.CredentialType}.
+ */
+@SmallTest
+public class CredentialTypeTest {
+    private static final int TEST_TYPE = CredentialType.CREDENTIAL_TYPE_USIM;
+
+    /**
+     * Helper function for generating the test buffer.
+     *
+     * @return {@link ByteBuffer}
+     */
+    private ByteBuffer getTestBuffer() {
+        return ByteBuffer.wrap(new byte[] {(byte) TEST_TYPE});
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        CredentialType.parse(
+                ByteBuffer.wrap(new byte[0]), CredentialType.EXPECTED_LENGTH_VALUE, false);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when the data length value is not the same
+     * as the expected
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithInvalidLength() throws Exception {
+        CredentialType.parse(getTestBuffer(), CredentialType.EXPECTED_LENGTH_VALUE - 1, false);
+    }
+
+    /**
+     * Verify that an expected CredentialType is returned when parsing the buffer for a
+     * non-tunneled EAP method.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferForNonTunneledEAPMethod() throws Exception {
+        CredentialType expected =
+                new CredentialType(AuthParam.PARAM_TYPE_CREDENTIAL_TYPE, TEST_TYPE);
+        CredentialType actual = CredentialType.parse(
+                getTestBuffer(), CredentialType.EXPECTED_LENGTH_VALUE, false);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Verify that an expected CredentialType is returned when parsing the buffer for a
+     * tunneled EAP method.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferForTunneledEAPMethod() throws Exception {
+        CredentialType expected = new CredentialType(
+                AuthParam.PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE, TEST_TYPE);
+        CredentialType actual = CredentialType.parse(
+                getTestBuffer(), CredentialType.EXPECTED_LENGTH_VALUE, true);
+        assertEquals(expected, actual);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/EAPMethodTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/EAPMethodTest.java
new file mode 100644
index 0000000..51434c2
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/EAPMethodTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.EAPConstants;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.eap.EAPMethod}.
+ */
+@SmallTest
+public class EAPMethodTest {
+    /**
+     * Setup basic test data - contained multiple parameters of the same type.
+     */
+    private static final byte[] TEST_DATA1_BYTES =
+            new byte[] {0x0B /* length */, 0x0D /* EAP_TLS */, 0x03 /* Auth Param Count */,
+                        0x05 /* CredentialType */, 0x01, 0x02 /* USIM */,
+                        0x05 /* CredentialType */, 0x01, 0x06 /* Certificate */,
+                        0x06 /* Tunneled CredentialType */, 0x01, 0x03 /* NFC */};
+    private static final Map<Integer, Set<AuthParam>> TEST_DATA1_AUTH_PARAMS =
+            new HashMap<>();
+    private static final Set<AuthParam> TEST_DATA1_CREDENTIAL_TYPE_PARAMS = new HashSet<>();
+    private static final Set<AuthParam> TEST_DATA1_TUNNELED_CREDENTIAL_TYPE_PARAMS =
+            new HashSet<>();
+    static {
+        TEST_DATA1_CREDENTIAL_TYPE_PARAMS.add(new CredentialType(
+                AuthParam.PARAM_TYPE_CREDENTIAL_TYPE, CredentialType.CREDENTIAL_TYPE_USIM));
+        TEST_DATA1_CREDENTIAL_TYPE_PARAMS.add(new CredentialType(
+                AuthParam.PARAM_TYPE_CREDENTIAL_TYPE, CredentialType.CREDENTIAL_TYPE_CERTIFICATE));
+
+        TEST_DATA1_TUNNELED_CREDENTIAL_TYPE_PARAMS.add(new CredentialType(
+                AuthParam.PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE,
+                CredentialType.CREDENTIAL_TYPE_NFC));
+
+        TEST_DATA1_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_CREDENTIAL_TYPE,
+                TEST_DATA1_CREDENTIAL_TYPE_PARAMS);
+        TEST_DATA1_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE,
+                TEST_DATA1_TUNNELED_CREDENTIAL_TYPE_PARAMS);
+    }
+    private static final EAPMethod TEST_DATA1_EAP_METHOD = new EAPMethod(
+            EAPConstants.EAP_TLS, TEST_DATA1_AUTH_PARAMS);
+
+    /**
+     * Setup test data for testing an EAP Method containing all types of authentication parameters.
+     */
+    private static final byte[] TEST_DATA2_BYTES =
+            new byte[] {0x26 /* length */, 0x0D /* EAP_TLS */, 0x07,
+                        // Expanded EAP Method
+                        0x01, 0x07, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78,
+                        // Non-EAP Inner Auth Type
+                        0x02, 0x01, 0x03 /* AUTH_TYPE_MSCHAP */,
+                        // Inner Auth EAP Method
+                        0x03, 0x01, 0x1D /* EAP_PEAP */,
+                        // Expanded Inner EAP Method
+                        0x04, 0x07, 0x01, 0x23, 0x45, 0x56, 0x78, 0x56, 0x12,
+                        // Credential Type
+                        0x05, 0x01, 0x02 /* USIM */,
+                        // Tunneled Credential Type
+                        0x06, 0x01, 0x03 /* NFC */,
+                        // Vendor Specific
+                        (byte) 0xDD, 0x04, 0x12, 0x23, 0x45, 0x56};
+    private static final Map<Integer, Set<AuthParam>> TEST_DATA2_AUTH_PARAMS =
+            new HashMap<>();
+    private static final Set<AuthParam> TEST_DATA2_EXPANDED_EAP_METHOD_PARAMS = new HashSet<>();
+    private static final Set<AuthParam> TEST_DATA2_NON_EAP_INNER_AUTH_PARAMS = new HashSet<>();
+    private static final Set<AuthParam> TEST_DATA2_INNER_AUTH_EAP_PARAMS = new HashSet<>();
+    private static final Set<AuthParam> TEST_DATA2_EXPANDED_INNER_EAP_PARAMS = new HashSet<>();
+    private static final Set<AuthParam> TEST_DATA2_CREDENTIAL_TYPE_PARAMS = new HashSet<>();
+    private static final Set<AuthParam> TEST_DATA2_TUNNELED_CREDENTIAL_TYPE_PARAMS =
+            new HashSet<>();
+    private static final Set<AuthParam> TEST_DATA2_VENDOR_SPECIFIC_PARAMS = new HashSet<>();
+    static {
+        TEST_DATA2_EXPANDED_EAP_METHOD_PARAMS.add(new ExpandedEAPMethod(
+                AuthParam.PARAM_TYPE_EXPANDED_EAP_METHOD, 0x122334, 0x45566778L));
+        TEST_DATA2_NON_EAP_INNER_AUTH_PARAMS.add(new NonEAPInnerAuth(
+                NonEAPInnerAuth.AUTH_TYPE_MSCHAP));
+        TEST_DATA2_INNER_AUTH_EAP_PARAMS.add(new InnerAuthEAP(EAPConstants.EAP_PEAP));
+        TEST_DATA2_EXPANDED_INNER_EAP_PARAMS.add(new ExpandedEAPMethod(
+                AuthParam.PARAM_TYPE_EXPANDED_INNER_EAP_METHOD, 0x012345, 0x56785612L));
+        TEST_DATA2_CREDENTIAL_TYPE_PARAMS.add(new CredentialType(
+                AuthParam.PARAM_TYPE_CREDENTIAL_TYPE, CredentialType.CREDENTIAL_TYPE_USIM));
+        TEST_DATA2_TUNNELED_CREDENTIAL_TYPE_PARAMS.add(new CredentialType(
+                AuthParam.PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE,
+                CredentialType.CREDENTIAL_TYPE_NFC));
+        TEST_DATA2_VENDOR_SPECIFIC_PARAMS.add(new VendorSpecificAuth(
+                new byte[] {0x12, 0x23, 0x45, 0x56}));
+
+        TEST_DATA2_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_EXPANDED_EAP_METHOD,
+                TEST_DATA2_EXPANDED_EAP_METHOD_PARAMS);
+        TEST_DATA2_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_NON_EAP_INNER_AUTH_TYPE,
+                TEST_DATA2_NON_EAP_INNER_AUTH_PARAMS);
+        TEST_DATA2_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_INNER_AUTH_EAP_METHOD_TYPE,
+                TEST_DATA2_INNER_AUTH_EAP_PARAMS);
+        TEST_DATA2_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_EXPANDED_INNER_EAP_METHOD,
+                TEST_DATA2_EXPANDED_INNER_EAP_PARAMS);
+        TEST_DATA2_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_CREDENTIAL_TYPE,
+                TEST_DATA2_CREDENTIAL_TYPE_PARAMS);
+        TEST_DATA2_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_TUNNELED_EAP_METHOD_CREDENTIAL_TYPE,
+                TEST_DATA2_TUNNELED_CREDENTIAL_TYPE_PARAMS);
+        TEST_DATA2_AUTH_PARAMS.put(AuthParam.PARAM_TYPE_VENDOR_SPECIFIC,
+                TEST_DATA2_VENDOR_SPECIFIC_PARAMS);
+    }
+    private static final EAPMethod TEST_DATA2_EAP_METHOD = new EAPMethod(
+            EAPConstants.EAP_TLS, TEST_DATA2_AUTH_PARAMS);
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        EAPMethod.parse(ByteBuffer.wrap(new byte[0]));
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when parsing a truncated buffer
+     * (missing a byte at the end).
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.wrap(TEST_DATA1_BYTES, 0, TEST_DATA1_BYTES.length - 1);
+        EAPMethod.parse(buffer);
+    }
+
+    /**
+     * Verify that the expected EAPMethod is return when parsing a buffer contained
+     * {@link #TEST_DATA1_BYTES}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData1() throws Exception {
+        assertEquals(TEST_DATA1_EAP_METHOD, EAPMethod.parse(ByteBuffer.wrap(TEST_DATA1_BYTES)));
+    }
+
+    /**
+     * Verify that the expected EAPMethod is return when parsing a buffer contained
+     * {@link #TEST_DATA2_BYTES}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithTestData2() throws Exception {
+        assertEquals(TEST_DATA2_EAP_METHOD, EAPMethod.parse(ByteBuffer.wrap(TEST_DATA2_BYTES)));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/ExpandedEAPMethodTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/ExpandedEAPMethodTest.java
new file mode 100644
index 0000000..8f114b2
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/ExpandedEAPMethodTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.eap.ExpandedEAPMethod}.
+ */
+@SmallTest
+public class ExpandedEAPMethodTest {
+    private static final int TEST_VENDOR_ID = 0x123456;
+    private static final long TEST_VENDOR_TYPE = 0x23456523;
+    private static final byte[] TEST_DATA_BYTES =
+            new byte[] {0x12, 0x34, 0x56, 0x23, 0x45, 0x65, 0x23};
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        ExpandedEAPMethod.parse(
+                ByteBuffer.wrap(new byte[0]), ExpandedEAPMethod.EXPECTED_LENGTH_VALUE, false);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when the data length is not the expected
+     * length.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithInvalidLength() throws Exception {
+        ExpandedEAPMethod.parse(ByteBuffer.wrap(TEST_DATA_BYTES),
+                ExpandedEAPMethod.EXPECTED_LENGTH_VALUE - 1, false);
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing a truncated buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseBufferWithTruncatedBuffer() throws Exception {
+        ExpandedEAPMethod.parse(ByteBuffer.wrap(TEST_DATA_BYTES, 0, TEST_DATA_BYTES.length - 1),
+                ExpandedEAPMethod.EXPECTED_LENGTH_VALUE, false);
+    }
+
+    /**
+     * Verify that an expected ExpandedEAPMethod is returned when parsing the buffer for a
+     * non-inner EAP method.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferForNonInnerEAPMethod() throws Exception {
+        ExpandedEAPMethod expected = new ExpandedEAPMethod(
+                AuthParam.PARAM_TYPE_EXPANDED_EAP_METHOD, TEST_VENDOR_ID, TEST_VENDOR_TYPE);
+        ExpandedEAPMethod actual = ExpandedEAPMethod.parse(
+                ByteBuffer.wrap(TEST_DATA_BYTES), ExpandedEAPMethod.EXPECTED_LENGTH_VALUE, false);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Verify that an expected CredentialType is returned when parsing the buffer for a
+     * inner EAP method.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferForTunneledEAPMethod() throws Exception {
+        ExpandedEAPMethod expected = new ExpandedEAPMethod(
+                AuthParam.PARAM_TYPE_EXPANDED_INNER_EAP_METHOD, TEST_VENDOR_ID, TEST_VENDOR_TYPE);
+        ExpandedEAPMethod actual = ExpandedEAPMethod.parse(
+                ByteBuffer.wrap(TEST_DATA_BYTES), ExpandedEAPMethod.EXPECTED_LENGTH_VALUE, true);
+        assertEquals(expected, actual);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/InnerAuthEAPTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/InnerAuthEAPTest.java
new file mode 100644
index 0000000..a4a813b
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/InnerAuthEAPTest.java
@@ -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.
+ */
+
+package com.android.server.wifi.hotspot2.anqp.eap;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.EAPConstants;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.eap.InnerAuthEAP}.
+ */
+@SmallTest
+public class InnerAuthEAPTest {
+    private static final int TEST_EAP_METHOD_ID = EAPConstants.EAP_TTLS;
+
+    /**
+     * Helper function for generating the test buffer.
+     *
+     * @return {@link ByteBuffer}
+     */
+    private ByteBuffer getTestBuffer() {
+        return ByteBuffer.wrap(new byte[] {(byte) TEST_EAP_METHOD_ID});
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        InnerAuthEAP.parse(ByteBuffer.wrap(new byte[0]), InnerAuthEAP.EXPECTED_LENGTH_VALUE);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when the data length is not the expected
+     * length.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithInvalidLength() throws Exception {
+        InnerAuthEAP.parse(getTestBuffer(), InnerAuthEAP.EXPECTED_LENGTH_VALUE - 1);
+    }
+
+    /**
+     * Verify that an expected InnerAuthEAP is returned when parsing a buffer contained
+     * the expected EAP method ID.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBuffer() throws Exception {
+        InnerAuthEAP expected = new InnerAuthEAP(TEST_EAP_METHOD_ID);
+        InnerAuthEAP actual =
+                InnerAuthEAP.parse(getTestBuffer(), InnerAuthEAP.EXPECTED_LENGTH_VALUE);
+        assertEquals(expected, actual);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/NonEAPInnerAuthTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/NonEAPInnerAuthTest.java
new file mode 100644
index 0000000..9770321
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/NonEAPInnerAuthTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.hotspot2.anqp.eap;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.net.ProtocolException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth}.
+ */
+@SmallTest
+public class NonEAPInnerAuthTest {
+    private static final int TEST_AUTH_TYPE = NonEAPInnerAuth.AUTH_TYPE_MSCHAP;
+
+    /**
+     * Helper function for generating the test buffer.
+     *
+     * @return {@link ByteBuffer}
+     */
+    private ByteBuffer getTestBuffer() {
+        return ByteBuffer.wrap(new byte[] {(byte) TEST_AUTH_TYPE});
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        NonEAPInnerAuth.parse(ByteBuffer.wrap(new byte[0]), NonEAPInnerAuth.EXPECTED_LENGTH_VALUE);
+    }
+
+    /**
+     * Verify that ProtocolException will be thrown when the data length is not the expected
+     * length.
+     *
+     * @throws Exception
+     */
+    @Test(expected = ProtocolException.class)
+    public void parseBufferWithInvalidLength() throws Exception {
+        NonEAPInnerAuth.parse(getTestBuffer(), NonEAPInnerAuth.EXPECTED_LENGTH_VALUE - 1);
+    }
+
+    /**
+     * Verify that an expected NonEAPInnerAuth is returned when parsing a buffer contained
+     * the expected auth type.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBuffer() throws Exception {
+        NonEAPInnerAuth expected = new NonEAPInnerAuth(TEST_AUTH_TYPE);
+        NonEAPInnerAuth actual =
+                NonEAPInnerAuth.parse(getTestBuffer(), NonEAPInnerAuth.EXPECTED_LENGTH_VALUE);
+        assertEquals(expected, actual);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/VendorSpecificAuthTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/VendorSpecificAuthTest.java
new file mode 100644
index 0000000..dbb2d8f
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/eap/VendorSpecificAuthTest.java
@@ -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.
+ */
+
+package com.android.server.wifi.hotspot2.anqp.eap;
+
+import static org.junit.Assert.assertEquals;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.eap.VendorSpecificAuth}.
+ */
+@SmallTest
+public class VendorSpecificAuthTest {
+    private static final byte[] TEST_DATA = new byte[] {0x12, 0x34, 0x45, 0x56};
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from an empty buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseEmptyBuffer() throws Exception {
+        VendorSpecificAuth.parse(ByteBuffer.wrap(new byte[0]), 1);
+    }
+
+    /**
+     * Verify that BufferUnderflowException will be thrown when parsing from a truncated buffer.
+     *
+     * @throws Exception
+     */
+    @Test(expected = BufferUnderflowException.class)
+    public void parseTruncatedBuffer() throws Exception {
+        VendorSpecificAuth.parse(
+                ByteBuffer.wrap(TEST_DATA, 0, TEST_DATA.length - 1), TEST_DATA.length);
+    }
+
+    /**
+     * Verify that a VendorSpecificAuth with a empty data array is returned when parsing
+     * a zero byte from a buffer.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBufferWithZeroLength() throws Exception {
+        VendorSpecificAuth expected = new VendorSpecificAuth(new byte[0]);
+        assertEquals(expected, VendorSpecificAuth.parse(ByteBuffer.wrap(TEST_DATA), 0));
+    }
+
+    /**
+     * Verify that an expected VendorSpecificAuth is returned when parsing a buffer contained
+     * the expected data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseBuffer() throws Exception {
+        VendorSpecificAuth expected = new VendorSpecificAuth(TEST_DATA);
+        VendorSpecificAuth actual =
+                VendorSpecificAuth.parse(ByteBuffer.wrap(TEST_DATA), TEST_DATA.length);
+        assertEquals(expected, actual);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java b/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
deleted file mode 100644
index 5c83185..0000000
--- a/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
+++ /dev/null
@@ -1,234 +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.nan;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-
-import android.net.wifi.nan.TlvBufferUtils;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ErrorCollector;
-import org.junit.rules.ExpectedException;
-
-/**
- * Unit test harness for WifiNanManager class.
- */
-@SmallTest
-public class TlvBufferUtilsTest {
-    @Rule
-    public ErrorCollector collector = new ErrorCollector();
-
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
-    /*
-     * TlvBufferUtils Tests
-     */
-
-    @Test
-    public void testTlvBuild() {
-        TlvBufferUtils.TlvConstructor tlv11 = new TlvBufferUtils.TlvConstructor(1, 1);
-        tlv11.allocate(15);
-        tlv11.putByte(0, (byte) 2);
-        tlv11.putByteArray(2, new byte[] {
-                0, 1, 2 });
-
-        collector.checkThat("tlv11-correct-construction",
-                utilAreArraysEqual(tlv11.getArray(), tlv11.getActualLength(), new byte[] {
-                        0, 1, 2, 2, 3, 0, 1, 2 }, 8),
-                equalTo(true));
-
-        TlvBufferUtils.TlvConstructor tlv01 = new TlvBufferUtils.TlvConstructor(0, 1);
-        tlv01.allocate(15);
-        tlv01.putByte(0, (byte) 2);
-        tlv01.putByteArray(2, new byte[] {
-                0, 1, 2 });
-
-        collector.checkThat("tlv01-correct-construction",
-                utilAreArraysEqual(tlv01.getArray(), tlv01.getActualLength(), new byte[] {
-                        1, 2, 3, 0, 1, 2 }, 6),
-                equalTo(true));
-    }
-
-    @Test
-    public void testTlvIterate() {
-        TlvBufferUtils.TlvConstructor tlv22 = new TlvBufferUtils.TlvConstructor(2, 2);
-        tlv22.allocate(18);
-        tlv22.putInt(0, 2);
-        tlv22.putShort(2, (short) 3);
-        tlv22.putZeroLengthElement(55);
-
-        TlvBufferUtils.TlvIterable tlv22It = new TlvBufferUtils.TlvIterable(2, 2, tlv22.getArray(),
-                tlv22.getActualLength());
-        int count = 0;
-        for (TlvBufferUtils.TlvElement tlv : tlv22It) {
-            if (count == 0) {
-                collector.checkThat("tlv22-correct-iteration-mType", tlv.mType, equalTo(0));
-                collector.checkThat("tlv22-correct-iteration-mLength", tlv.mLength, equalTo(4));
-                collector.checkThat("tlv22-correct-iteration-DATA", tlv.getInt(), equalTo(2));
-            } else if (count == 1) {
-                collector.checkThat("tlv22-correct-iteration-mType", tlv.mType, equalTo(2));
-                collector.checkThat("tlv22-correct-iteration-mLength", tlv.mLength, equalTo(2));
-                collector.checkThat("tlv22-correct-iteration-DATA", (int) tlv.getShort(),
-                        equalTo(3));
-            } else if (count == 2) {
-                collector.checkThat("tlv22-correct-iteration-mType", tlv.mType, equalTo(55));
-                collector.checkThat("tlv22-correct-iteration-mLength", tlv.mLength, equalTo(0));
-            } else {
-                collector.checkThat("Invalid number of iterations in loop - tlv22", true,
-                        equalTo(false));
-            }
-            ++count;
-        }
-        if (count != 3) {
-            collector.checkThat("Invalid number of iterations outside loop - tlv22", true,
-                    equalTo(false));
-        }
-
-        TlvBufferUtils.TlvConstructor tlv02 = new TlvBufferUtils.TlvConstructor(0, 2);
-        tlv02.allocate(15);
-        tlv02.putByte(0, (byte) 2);
-        tlv02.putString(0, "ABC");
-
-        TlvBufferUtils.TlvIterable tlv02It = new TlvBufferUtils.TlvIterable(0, 2, tlv02.getArray(),
-                tlv02.getActualLength());
-        count = 0;
-        for (TlvBufferUtils.TlvElement tlv : tlv02It) {
-            if (count == 0) {
-                collector.checkThat("tlv02-correct-iteration-mLength", tlv.mLength, equalTo(1));
-                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"),
-                        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));
-        }
-    }
-
-    @Test
-    public void testTlvInvalidSizeT1L0() {
-        thrown.expect(IllegalArgumentException.class);
-        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 0);
-    }
-
-    @Test
-    public void testTlvInvalidSizeTm3L2() {
-        thrown.expect(IllegalArgumentException.class);
-        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(-3, 2);
-    }
-
-    @Test
-    public void testTlvInvalidSizeT1Lm2() {
-        thrown.expect(IllegalArgumentException.class);
-        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, -2);
-    }
-
-    @Test
-    public void testTlvInvalidSizeT1L3() {
-        thrown.expect(IllegalArgumentException.class);
-        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 3);
-    }
-
-    @Test
-    public void testTlvInvalidSizeT3L1() {
-        thrown.expect(IllegalArgumentException.class);
-        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(3, 1);
-    }
-
-    @Test
-    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
-    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
-    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
-    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
-    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);
-    }
-
-    /*
-     * Utilities
-     */
-
-    private static boolean utilAreArraysEqual(byte[] x, int xLength, byte[] y, int yLength) {
-        if (xLength != yLength) {
-            return false;
-        }
-
-        if (x != null && y != null) {
-            for (int i = 0; i < xLength; ++i) {
-                if (x[i] != y[i]) {
-                    return false;
-                }
-            }
-        } else if (xLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        return true;
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
deleted file mode 100644
index 4f3ba4d..0000000
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
+++ /dev/null
@@ -1,97 +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.nan;
-
-import java.lang.reflect.Field;
-
-/**
- * Mock class for NAN HAL. Provides access to HAL API and to callbacks. To
- * extend:
- * <ul>
- * <li>HAL API: create a {@code public void} method which takes any fixed
- * arguments (e.g. a {@code short transactionId} and a second argument to
- * provide the rest of the argument as a JSON string: {@code String jsonArgs}.
- * <li>Callbacks from HAL: create a {@code public static native} function which
- * is used to trigger the callback from the test harness. The arguments are
- * similar to the HAL API arguments.
- * </ul>
- */
-public class WifiNanHalMock {
-    public void getCapabilitiesHalMockNative(short transactionId) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    public void enableHalMockNative(short transactionId, String jsonArgs) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    public void disableHalMockNative(short transactionId) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    public void publishHalMockNative(short transactionId, String jsonArgs) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    public void publishCancelHalMockNative(short transactionId, String jsonArgs) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    public void subscribeHalMockNative(short transactionId, String jsonArgs) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    public void subscribeCancelHalMockNative(short transactionId, String jsonArgs) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    public void transmitFollowupHalMockNative(short transactionId, String jsonArgs) {
-        throw new IllegalStateException("Please mock this class!");
-    }
-
-    /*
-     * trigger callbacks - called by test harness with arguments passed by JSON
-     * string.
-     */
-
-    public static native void callNotifyResponse(short transactionId, String jsonArgs);
-
-    public static native void callPublishTerminated(String jsonArgs);
-
-    public static native void callSubscribeTerminated(String jsonArgs);
-
-    public static native void callFollowup(String jsonArgs);
-
-    public static native void callMatch(String jsonArgs);
-
-    public static native void callDiscEngEvent(String jsonArgs);
-
-    public static native void callDisabled(String jsonArgs);
-
-    /**
-     * initialize NAN mock
-     */
-    private static native int initNanHalMock();
-
-    public static void initNanHalMockLibrary() throws Exception {
-        Field field = WifiNanNative.class.getDeclaredField("sNanNativeInit");
-        field.setAccessible(true);
-        field.setBoolean(null, 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
deleted file mode 100644
index 536c924..0000000
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java
+++ /dev/null
@@ -1,762 +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.nan;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-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.TlvBufferUtils;
-import android.net.wifi.nan.WifiNanSessionListener;
-import android.os.Bundle;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.wifi.HalMockUtils;
-import com.android.server.wifi.WifiNative;
-
-import libcore.util.HexEncoding;
-
-import org.json.JSONException;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ErrorCollector;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.lang.reflect.Field;
-import java.util.Arrays;
-
-/**
- * Unit test harness for WifiNanNative + JNI code interfacing to the HAL.
- */
-@SmallTest
-public class WifiNanHalTest {
-    private WifiNanNative mDut = WifiNanNative.getInstance();
-    private ArgumentCaptor<String> mArgs = ArgumentCaptor.forClass(String.class);
-
-    @Mock
-    private WifiNanHalMock mNanHalMock;
-    @Mock private WifiNanStateManager mNanStateManager;
-
-    @Rule
-    public ErrorCollector collector = new ErrorCollector();
-
-    @Before
-    public void setup() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        HalMockUtils.initHalMockLibrary();
-        WifiNanHalMock.initNanHalMockLibrary();
-        WifiNanNative.initNanHandlersNative(WifiNative.class, WifiNative.sWlan0Index);
-        HalMockUtils.setHalMockObject(mNanHalMock);
-        installMockNanStateManager(mNanStateManager);
-    }
-
-    @Test
-    public void testEnableWith5g() throws JSONException {
-        final short transactionId = 2346;
-        final int clusterLow = 23;
-        final int clusterHigh = 126;
-        final int masterPref = 234;
-        final boolean enable5g = true;
-
-        testEnable(transactionId, clusterLow, clusterHigh, masterPref, enable5g);
-    }
-
-    @Test
-    public void testEnableWithout5g() throws JSONException {
-        final short transactionId = 1296;
-        final int clusterLow = 17;
-        final int clusterHigh = 197;
-        final int masterPref = 33;
-        final boolean enable5g = false;
-
-        testEnable(transactionId, clusterLow, clusterHigh, masterPref, enable5g);
-    }
-
-    @Test
-    public void testDisable() {
-        final short transactionId = 5478;
-
-        mDut.disable(transactionId);
-
-        verify(mNanHalMock).disableHalMockNative(transactionId);
-    }
-
-    @Test
-    public void testPublishUnsolicited() throws JSONException {
-        final short transactionId = 55;
-        final int publishId = 23;
-        final String serviceName = "some-service-name";
-        final String ssi = "some much longer and more arbitrary data";
-        final int publishCount = 7;
-        final int publishTtl = 66;
-
-        TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
-        tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
-                .putZeroLengthElement(0);
-
-        TlvBufferUtils.TlvConstructor tlvRx = new TlvBufferUtils.TlvConstructor(0, 1);
-        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);
-    }
-
-    @Test
-    public void testPublishSolicited() throws JSONException {
-        final short transactionId = 45;
-        final int publishId = 17;
-        final String serviceName = "some-service-name-or-another";
-        final String ssi = "some much longer arbitrary data";
-        final int publishCount = 32;
-        final int publishTtl = 33;
-
-        TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
-        tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
-                .putZeroLengthElement(0);
-
-        TlvBufferUtils.TlvConstructor tlvRx = new TlvBufferUtils.TlvConstructor(0, 1);
-        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);
-    }
-
-    @Test
-    public void testPublishCancel() throws JSONException {
-        final short transactionId = 12;
-        final int publishId = 15;
-
-        mDut.stopPublish(transactionId, publishId);
-
-        verify(mNanHalMock).publishCancelHalMockNative(eq(transactionId), mArgs.capture());
-
-        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
-
-        collector.checkThat("publish_id", argsData.getInt("publish_id"), equalTo(publishId));
-    }
-
-    @Test
-    public void testSubscribePassive() throws JSONException {
-        final short transactionId = 45;
-        final int subscribeId = 17;
-        final String serviceName = "some-service-name-or-another";
-        final String ssi = "some much longer arbitrary data";
-        final int subscribeCount = 32;
-        final int subscribeTtl = 33;
-
-        TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
-        tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
-                .putZeroLengthElement(0);
-
-        TlvBufferUtils.TlvConstructor tlvRx = new TlvBufferUtils.TlvConstructor(0, 1);
-        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);
-    }
-
-    @Test
-    public void testSubscribeActive() throws JSONException {
-        final short transactionId = 45;
-        final int subscribeId = 17;
-        final String serviceName = "some-service-name-or-another";
-        final String ssi = "some much longer arbitrary data";
-        final int subscribeCount = 32;
-        final int subscribeTtl = 33;
-
-        TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
-        tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
-                .putZeroLengthElement(0);
-
-        TlvBufferUtils.TlvConstructor tlvRx = new TlvBufferUtils.TlvConstructor(0, 1);
-        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);
-    }
-
-    @Test
-    public void testSubscribeCancel() throws JSONException {
-        final short transactionId = 12;
-        final int subscribeId = 15;
-
-        mDut.stopSubscribe(transactionId, subscribeId);
-
-        verify(mNanHalMock).subscribeCancelHalMockNative(eq(transactionId), mArgs.capture());
-
-        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
-
-        collector.checkThat("subscribe_id", argsData.getInt("subscribe_id"), equalTo(subscribeId));
-    }
-
-    @Test
-    public void testSendMessage() throws JSONException {
-        final short transactionId = 45;
-        final int pubSubId = 22;
-        final int reqInstanceId = 11;
-        final byte[] peer = HexEncoding.decode("000102030405".toCharArray(), false);
-        final String msg = "Hello there - how are you doing?";
-
-        mDut.sendMessage(transactionId, pubSubId, reqInstanceId, peer, msg.getBytes(),
-                msg.length());
-
-        verify(mNanHalMock).transmitFollowupHalMockNative(eq(transactionId), mArgs.capture());
-
-        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
-
-        collector.checkThat("publish_subscribe_id", argsData.getInt("publish_subscribe_id"),
-                equalTo(pubSubId));
-        collector.checkThat("requestor_instance_id", argsData.getInt("requestor_instance_id"),
-                equalTo(reqInstanceId));
-        collector.checkThat("addr", argsData.getByteArray("addr"), equalTo(peer));
-        collector.checkThat("priority", argsData.getInt("priority"), equalTo(0));
-        collector.checkThat("dw_or_faw", argsData.getInt("dw_or_faw"), equalTo(0));
-        collector.checkThat("service_specific_info_len",
-                argsData.getInt("service_specific_info_len"), equalTo(msg.length()));
-        collector.checkThat("service_specific_info", argsData.getByteArray("service_specific_info"),
-                equalTo(msg.getBytes()));
-    }
-
-    @Test
-    public void testNotifyCapabilities() throws JSONException {
-        final short transactionId = 23;
-        final int max_concurrent_nan_clusters = 1;
-        final int max_publishes = 2;
-        final int max_subscribes = 3;
-        final int max_service_name_len = 4;
-        final int max_match_filter_len = 5;
-        final int max_total_match_filter_len = 6;
-        final int max_service_specific_info_len = 7;
-        final int max_vsa_data_len = 8;
-        final int max_mesh_data_len = 9;
-        final int max_ndi_interfaces = 10;
-        final int max_ndp_sessions = 11;
-        final int max_app_info_len = 12;
-
-        ArgumentCaptor<WifiNanNative.Capabilities> capabilitiesCapture = ArgumentCaptor
-                .forClass(WifiNanNative.Capabilities.class);
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_GET_CAPABILITIES);
-
-        args.putInt("body.nan_capabilities.max_concurrent_nan_clusters",
-                max_concurrent_nan_clusters);
-        args.putInt("body.nan_capabilities.max_publishes", max_publishes);
-        args.putInt("body.nan_capabilities.max_subscribes", max_subscribes);
-        args.putInt("body.nan_capabilities.max_service_name_len", max_service_name_len);
-        args.putInt("body.nan_capabilities.max_match_filter_len", max_match_filter_len);
-        args.putInt("body.nan_capabilities.max_total_match_filter_len", max_total_match_filter_len);
-        args.putInt("body.nan_capabilities.max_service_specific_info_len",
-                max_service_specific_info_len);
-        args.putInt("body.nan_capabilities.max_vsa_data_len", max_vsa_data_len);
-        args.putInt("body.nan_capabilities.max_mesh_data_len", max_mesh_data_len);
-        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);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onCapabilitiesUpdate(eq(transactionId),
-                capabilitiesCapture.capture());
-        WifiNanNative.Capabilities capabilities = capabilitiesCapture.getValue();
-        collector.checkThat("max_concurrent_nan_clusters", capabilities.maxConcurrentNanClusters,
-                equalTo(max_concurrent_nan_clusters));
-        collector.checkThat("max_publishes", capabilities.maxPublishes, equalTo(max_publishes));
-        collector.checkThat("max_subscribes", capabilities.maxSubscribes, equalTo(max_subscribes));
-        collector.checkThat("max_service_name_len", capabilities.maxServiceNameLen,
-                equalTo(max_service_name_len));
-        collector.checkThat("max_match_filter_len", capabilities.maxMatchFilterLen,
-                equalTo(max_match_filter_len));
-        collector.checkThat("max_total_match_filter_len", capabilities.maxTotalMatchFilterLen,
-                equalTo(max_total_match_filter_len));
-        collector.checkThat("max_service_specific_info_len", capabilities.maxServiceSpecificInfoLen,
-                equalTo(max_service_specific_info_len));
-        collector.checkThat("max_vsa_data_len", capabilities.maxVsaDataLen,
-                equalTo(max_vsa_data_len));
-        collector.checkThat("max_mesh_data_len", capabilities.maxMeshDataLen,
-                equalTo(max_mesh_data_len));
-        collector.checkThat("max_ndi_interfaces", capabilities.maxNdiInterfaces,
-                equalTo(max_ndi_interfaces));
-        collector.checkThat("max_ndp_sessions", capabilities.maxNdpSessions,
-                equalTo(max_ndp_sessions));
-        collector.checkThat("max_app_info_len", capabilities.maxAppInfoLen,
-                equalTo(max_app_info_len));
-    }
-
-    @Test
-    public void testNotifyResponseConfigSuccess() throws JSONException {
-        final short transactionId = 23;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_ENABLED);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onConfigCompleted(transactionId);
-    }
-
-    @Test
-    public void testNotifyResponseConfigFail() throws JSONException {
-        final short transactionId = 23;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_INVALID_BAND_CONFIG_FLAGS);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_ENABLED);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onConfigFailed(transactionId,
-                WifiNanSessionListener.FAIL_REASON_INVALID_ARGS);
-    }
-
-    @Test
-    public void testNotifyResponsePublishSuccess() throws JSONException {
-        final short transactionId = 23;
-        final int publishId = 127;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_PUBLISH);
-        args.putInt("body.publish_response.publish_id", publishId);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onPublishSuccess(transactionId, publishId);
-    }
-
-    @Test
-    public void testNotifyResponsePublishFail() throws JSONException {
-        final short transactionId = 23;
-        final int publishId = 127;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_NO_SPACE_AVAILABLE);
-        args.putInt("value", 57);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_PUBLISH);
-        args.putInt("body.publish_response.publish_id", publishId);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onPublishFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_NO_RESOURCES);
-    }
-
-    @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);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_PUBLISH_CANCEL);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verifyZeroInteractions(mNanStateManager);
-    }
-
-    @Test
-    public void testNotifyResponseSubscribeSuccess() throws JSONException {
-        final short transactionId = 17;
-        final int subscribeId = 198;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_SUBSCRIBE);
-        args.putInt("body.subscribe_response.subscribe_id", subscribeId);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onSubscribeSuccess(transactionId, subscribeId);
-    }
-
-    @Test
-    public void testNotifyResponseSubscribeFail() throws JSONException {
-        final short transactionId = 17;
-        final int subscribeId = 198;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_DE_FAILURE);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_SUBSCRIBE);
-        args.putInt("body.subscribe_response.subscribe_id", subscribeId);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onSubscribeFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_OTHER);
-    }
-
-    @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("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_SUBSCRIBE_CANCEL);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verifyZeroInteractions(mNanStateManager);
-    }
-
-    @Test
-    public void testNotifyResponseTransmitFollowupSuccess() throws JSONException {
-        final short transactionId = 23;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_TRANSMIT_FOLLOWUP);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onMessageSendSuccess(transactionId);
-    }
-
-    @Test
-    public void testNotifyResponseTransmitFollowupFail() throws JSONException {
-        final short transactionId = 45;
-
-        Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_TIMEOUT);
-        args.putInt("value", 0);
-        args.putInt("response_type", WifiNanNative.NAN_RESPONSE_TRANSMIT_FOLLOWUP);
-
-        WifiNanHalMock.callNotifyResponse(transactionId,
-                HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onMessageSendFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_OTHER);
-    }
-
-    @Test
-    public void testPublishTerminatedDone() throws JSONException {
-        final int publishId = 167;
-
-        Bundle args = new Bundle();
-        args.putInt("publish_id", publishId);
-        args.putInt("reason", WifiNanNative.NAN_TERMINATED_REASON_COUNT_REACHED);
-
-        WifiNanHalMock.callPublishTerminated(HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onPublishTerminated(publishId,
-                WifiNanSessionListener.TERMINATE_REASON_DONE);
-    }
-
-    @Test
-    public void testSubscribeTerminatedFail() throws JSONException {
-        final int subscribeId = 167;
-
-        Bundle args = new Bundle();
-        args.putInt("subscribe_id", subscribeId);
-        args.putInt("reason", WifiNanNative.NAN_TERMINATED_REASON_FAILURE);
-
-        WifiNanHalMock.callSubscribeTerminated(HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onSubscribeTerminated(subscribeId,
-                WifiNanSessionListener.TERMINATE_REASON_FAIL);
-    }
-
-    @Test
-    public void testFollowup() throws JSONException {
-        final int pubSubId = 236;
-        final int reqInstanceId = 57;
-        final byte[] peer = HexEncoding.decode("0A0B0C0D0E0F".toCharArray(), false);
-        final String message = "this is some message received from some peer - hello!";
-
-        Bundle args = new Bundle();
-        args.putInt("publish_subscribe_id", pubSubId);
-        args.putInt("requestor_instance_id", reqInstanceId);
-        args.putByteArray("addr", peer);
-        args.putInt("dw_or_faw", 0);
-        args.putInt("service_specific_info_len", message.length());
-        args.putByteArray("service_specific_info", message.getBytes());
-
-        WifiNanHalMock.callFollowup(HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onMessageReceived(pubSubId, reqInstanceId, peer,
-                message.getBytes(), message.length());
-    }
-
-    @Test
-    public void testMatch() throws JSONException {
-        final int pubSubId = 287;
-        final int reqInstanceId = 98;
-        final byte[] peer = HexEncoding.decode("010203040506".toCharArray(), false);
-        final String ssi = "some service specific info - really arbitrary";
-        final String filter = "most likely binary - but faking here with some string data";
-
-        Bundle args = new Bundle();
-        args.putInt("publish_subscribe_id", pubSubId);
-        args.putInt("requestor_instance_id", reqInstanceId);
-        args.putByteArray("addr", peer);
-        args.putInt("service_specific_info_len", ssi.length());
-        args.putByteArray("service_specific_info", ssi.getBytes());
-        args.putInt("sdf_match_filter_len", filter.length());
-        args.putByteArray("sdf_match_filter", filter.getBytes());
-
-        WifiNanHalMock.callMatch(HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onMatch(pubSubId, reqInstanceId, peer, ssi.getBytes(),
-                ssi.length(), filter.getBytes(), filter.length());
-    }
-
-    @Test
-    public void testDiscoveryInterfaceChange() throws JSONException {
-        final byte[] mac = HexEncoding.decode("060504030201".toCharArray(), false);
-
-        Bundle args = new Bundle();
-        args.putInt("event_type", WifiNanNative.NAN_EVENT_ID_DISC_MAC_ADDR);
-        args.putByteArray("data", mac);
-
-        WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onInterfaceAddressChange(mac);
-    }
-
-    @Test
-    public void testClusterChange() throws JSONException {
-        final byte[] mac = HexEncoding.decode("060504030201".toCharArray(), false);
-
-        Bundle args = new Bundle();
-        args.putInt("event_type", WifiNanNative.NAN_EVENT_ID_JOINED_CLUSTER);
-        args.putByteArray("data", mac);
-
-        WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED,
-                mac);
-    }
-
-    @Test
-    public void testDisabled() throws JSONException {
-        Bundle args = new Bundle();
-        args.putInt("reason", WifiNanNative.NAN_STATUS_DE_FAILURE);
-
-        WifiNanHalMock.callDisabled(HalMockUtils.convertBundleToJson(args).toString());
-
-        verify(mNanStateManager).onNanDown(WifiNanSessionListener.FAIL_REASON_OTHER);
-    }
-
-    /*
-     * Utilities
-     */
-
-    private void testEnable(short transactionId, int clusterLow, int clusterHigh, int masterPref,
-            boolean enable5g) throws JSONException {
-        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(clusterLow)
-                .setClusterHigh(clusterHigh).setMasterPreference(masterPref)
-                .setSupport5gBand(enable5g).build();
-
-        mDut.enableAndConfigure(transactionId, configRequest);
-
-        verify(mNanHalMock).enableHalMockNative(eq(transactionId), mArgs.capture());
-
-        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
-
-        collector.checkThat("master_pref", argsData.getInt("master_pref"), equalTo(masterPref));
-        collector.checkThat("cluster_low", argsData.getInt("cluster_low"), equalTo(clusterLow));
-        collector.checkThat("cluster_high", argsData.getInt("cluster_high"), equalTo(clusterHigh));
-        collector.checkThat("config_support_5g", argsData.getInt("config_support_5g"), equalTo(1));
-        collector.checkThat("support_5g_val", argsData.getInt("support_5g_val"),
-                equalTo(enable5g ? 1 : 0));
-
-        collector.checkThat("config_sid_beacon", argsData.getInt("config_sid_beacon"), equalTo(0));
-        collector.checkThat("config_2dot4g_rssi_close", argsData.getInt("config_2dot4g_rssi_close"),
-                equalTo(0));
-        collector.checkThat("config_2dot4g_rssi_middle",
-                argsData.getInt("config_2dot4g_rssi_middle"), equalTo(0));
-        collector.checkThat("config_2dot4g_rssi_proximity",
-                argsData.getInt("config_2dot4g_rssi_proximity"), equalTo(0));
-        collector.checkThat("config_hop_count_limit", argsData.getInt("config_hop_count_limit"),
-                equalTo(0));
-        collector.checkThat("config_2dot4g_support", argsData.getInt("config_2dot4g_support"),
-                equalTo(0));
-        collector.checkThat("config_2dot4g_beacons", argsData.getInt("config_2dot4g_beacons"),
-                equalTo(0));
-        collector.checkThat("config_2dot4g_sdf", argsData.getInt("config_2dot4g_sdf"), equalTo(0));
-        collector.checkThat("config_5g_beacons", argsData.getInt("config_5g_beacons"), equalTo(0));
-        collector.checkThat("config_5g_sdf", argsData.getInt("config_5g_sdf"), equalTo(0));
-        collector.checkThat("config_5g_rssi_close", argsData.getInt("config_5g_rssi_close"),
-                equalTo(0));
-        collector.checkThat("config_5g_rssi_middle", argsData.getInt("config_5g_rssi_middle"),
-                equalTo(0));
-        collector.checkThat("config_5g_rssi_close_proximity",
-                argsData.getInt("config_5g_rssi_close_proximity"), equalTo(0));
-        collector.checkThat("config_rssi_window_size", argsData.getInt("config_rssi_window_size"),
-                equalTo(0));
-        collector.checkThat("config_oui", argsData.getInt("config_oui"), equalTo(0));
-        collector.checkThat("config_intf_addr", argsData.getInt("config_intf_addr"), 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("config_hop_count_force", argsData.getInt("config_hop_count_force"),
-                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();
-
-        PublishSettings publishSettings = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
-
-        mDut.publish(transactionId, publishId, publishData, publishSettings);
-
-        verify(mNanHalMock).publishHalMockNative(eq(transactionId), mArgs.capture());
-
-        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
-
-        collector.checkThat("publish_id", argsData.getInt("publish_id"), equalTo(publishId));
-        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));
-        collector.checkThat("publish_count", argsData.getInt("publish_count"),
-                equalTo(publishCount));
-        collector.checkThat("service_name_len", argsData.getInt("service_name_len"),
-                equalTo(serviceName.length()));
-        collector.checkThat("service_name", argsData.getByteArray("service_name"),
-                equalTo(serviceName.getBytes()));
-        collector.checkThat("service_specific_info_len",
-                argsData.getInt("service_specific_info_len"), equalTo(ssi.length()));
-        collector.checkThat("service_specific_info", argsData.getByteArray("service_specific_info"),
-                equalTo(ssi.getBytes()));
-        collector.checkThat("publish_match_indicator", argsData.getInt("publish_match_indicator"),
-                equalTo(0));
-        collector.checkThat("rx_match_filter_len", argsData.getInt("rx_match_filter_len"),
-                equalTo(tlvRx.getActualLength()));
-        collector.checkThat("rx_match_filter", argsData.getByteArray("rx_match_filter"),
-                equalTo(Arrays.copyOf(tlvRx.getArray(), tlvRx.getActualLength())));
-        collector.checkThat("tx_match_filter_len", argsData.getInt("tx_match_filter_len"),
-                equalTo(tlvTx.getActualLength()));
-        collector.checkThat("tx_match_filter", argsData.getByteArray("tx_match_filter"),
-                equalTo(Arrays.copyOf(tlvTx.getArray(), tlvTx.getActualLength())));
-        collector.checkThat("rssi_threshold_flag", argsData.getInt("rssi_threshold_flag"),
-                equalTo(0));
-        collector.checkThat("connmap", argsData.getInt("connmap"), equalTo(0));
-    }
-
-    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)
-                .setServiceSpecificInfo(ssi)
-                .setTxFilter(tlvTx.getArray(), tlvTx.getActualLength())
-                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength()).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
-
-        mDut.subscribe(transactionId, subscribeId, subscribeData, subscribeSettings);
-
-        verify(mNanHalMock).subscribeHalMockNative(eq(transactionId), mArgs.capture());
-
-        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
-
-        collector.checkThat("subscribe_id", argsData.getInt("subscribe_id"), equalTo(subscribeId));
-        collector.checkThat("ttl", argsData.getInt("ttl"), equalTo(subscribeTtl));
-        collector.checkThat("period", argsData.getInt("period"), equalTo(500));
-        collector.checkThat("subscribe_type", argsData.getInt("subscribe_type"),
-                equalTo(subscribeType));
-        collector.checkThat("serviceResponseFilter", argsData.getInt("serviceResponseFilter"),
-                equalTo(1));
-        collector.checkThat("serviceResponseInclude", argsData.getInt("serviceResponseInclude"),
-                equalTo(1));
-        collector.checkThat("useServiceResponseFilter", argsData.getInt("useServiceResponseFilter"),
-                equalTo(1));
-        collector.checkThat("ssiRequiredForMatchIndication",
-                argsData.getInt("ssiRequiredForMatchIndication"), equalTo(0));
-        collector.checkThat("subscribe_match_indicator",
-                argsData.getInt("subscribe_match_indicator"), equalTo(0));
-        collector.checkThat("subscribe_count", argsData.getInt("subscribe_count"),
-                equalTo(subscribeCount));
-        collector.checkThat("service_name_len", argsData.getInt("service_name_len"),
-                equalTo(serviceName.length()));
-        collector.checkThat("service_name", argsData.getByteArray("service_name"),
-                equalTo(serviceName.getBytes()));
-        collector.checkThat("service_specific_info_len",
-                argsData.getInt("service_specific_info_len"), equalTo(serviceName.length()));
-        collector.checkThat("service_specific_info", argsData.getByteArray("service_specific_info"),
-                equalTo(ssi.getBytes()));
-        collector.checkThat("rx_match_filter_len", argsData.getInt("rx_match_filter_len"),
-                equalTo(tlvRx.getActualLength()));
-        collector.checkThat("rx_match_filter", argsData.getByteArray("rx_match_filter"),
-                equalTo(Arrays.copyOf(tlvRx.getArray(), tlvRx.getActualLength())));
-        collector.checkThat("tx_match_filter_len", argsData.getInt("tx_match_filter_len"),
-                equalTo(tlvTx.getActualLength()));
-        collector.checkThat("tx_match_filter", argsData.getByteArray("tx_match_filter"),
-                equalTo(Arrays.copyOf(tlvTx.getArray(), tlvTx.getActualLength())));
-        collector.checkThat("rssi_threshold_flag", argsData.getInt("rssi_threshold_flag"),
-                equalTo(0));
-        collector.checkThat("connmap", argsData.getInt("connmap"), equalTo(0));
-        collector.checkThat("num_intf_addr_present", argsData.getInt("num_intf_addr_present"),
-                equalTo(0));
-    }
-
-    private static void installMockNanStateManager(WifiNanStateManager nanStateManager)
-            throws Exception {
-        Field field = WifiNanStateManager.class.getDeclaredField("sNanStateManagerSingleton");
-        field.setAccessible(true);
-        field.set(null, nanStateManager);
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
deleted file mode 100644
index bd4d43b..0000000
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
+++ /dev/null
@@ -1,403 +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.nan;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.assertEquals;
-
-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.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ErrorCollector;
-import org.junit.rules.ExpectedException;
-
-/**
- * Unit test harness for WifiNanManager class.
- */
-@SmallTest
-public class WifiNanManagerTest {
-    @Rule
-    public ErrorCollector collector = new ErrorCollector();
-
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
-    /*
-     * ConfigRequest Tests
-     */
-
-    @Test
-    public void testConfigRequestBuilder() {
-        final int clusterHigh = 100;
-        final int clusterLow = 5;
-        final int masterPreference = 55;
-        final boolean supportBand5g = true;
-
-        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
-                .setClusterLow(clusterLow).setMasterPreference(masterPreference)
-                .setSupport5gBand(supportBand5g).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));
-    }
-
-    @Test
-    public void testConfigRequestBuilderMasterPrefNegative() {
-        thrown.expect(IllegalArgumentException.class);
-        ConfigRequest.Builder builder = new ConfigRequest.Builder();
-        builder.setMasterPreference(-1);
-    }
-
-    @Test
-    public void testConfigRequestBuilderMasterPrefReserved1() {
-        thrown.expect(IllegalArgumentException.class);
-        new ConfigRequest.Builder().setMasterPreference(1);
-    }
-
-    @Test
-    public void testConfigRequestBuilderMasterPrefReserved255() {
-        thrown.expect(IllegalArgumentException.class);
-        new ConfigRequest.Builder().setMasterPreference(255);
-    }
-
-    @Test
-    public void testConfigRequestBuilderMasterPrefTooLarge() {
-        thrown.expect(IllegalArgumentException.class);
-        new ConfigRequest.Builder().setMasterPreference(256);
-    }
-
-    @Test
-    public void testConfigRequestBuilderClusterLowNegative() {
-        thrown.expect(IllegalArgumentException.class);
-        new ConfigRequest.Builder().setClusterLow(-1);
-    }
-
-    @Test
-    public void testConfigRequestBuilderClusterHighNegative() {
-        thrown.expect(IllegalArgumentException.class);
-        new ConfigRequest.Builder().setClusterHigh(-1);
-    }
-
-    @Test
-    public void testConfigRequestBuilderClusterLowAboveMax() {
-        thrown.expect(IllegalArgumentException.class);
-        new ConfigRequest.Builder().setClusterLow(ConfigRequest.CLUSTER_ID_MAX + 1);
-    }
-
-    @Test
-    public void testConfigRequestBuilderClusterHighAboveMax() {
-        thrown.expect(IllegalArgumentException.class);
-        new ConfigRequest.Builder().setClusterHigh(ConfigRequest.CLUSTER_ID_MAX + 1);
-    }
-
-    @Test
-    public void testConfigRequestBuilderClusterLowLargerThanHigh() {
-        thrown.expect(IllegalArgumentException.class);
-        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(100)
-                .setClusterHigh(5).build();
-    }
-
-    @Test
-    public void testConfigRequestParcel() {
-        final int clusterHigh = 189;
-        final int clusterLow = 25;
-        final int masterPreference = 177;
-        final boolean supportBand5g = true;
-
-        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
-                .setClusterLow(clusterLow).setMasterPreference(masterPreference)
-                .setSupport5gBand(supportBand5g).build();
-
-        Parcel parcelW = Parcel.obtain();
-        configRequest.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
-
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
-        ConfigRequest rereadConfigRequest = ConfigRequest.CREATOR.createFromParcel(parcelR);
-
-        assertEquals(configRequest, rereadConfigRequest);
-    }
-
-    /*
-     * SubscribeData Tests
-     */
-
-    @Test
-    public void testSubscribeDataBuilder() {
-        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 subscribeCount = 10;
-        final int subscribeTtl = 15;
-
-        SubscribeSettings subscribeSetting = new SubscribeSettings.Builder()
-                .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
-
-        collector.checkThat("mSubscribeType", subscribeType,
-                equalTo(subscribeSetting.mSubscribeType));
-        collector.checkThat("mSubscribeCount", subscribeCount,
-                equalTo(subscribeSetting.mSubscribeCount));
-        collector.checkThat("mTtlSec", subscribeTtl, equalTo(subscribeSetting.mTtlSec));
-    }
-
-    @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;
-        final int subscribeCount = 10;
-        final int subscribeTtl = 15;
-
-        SubscribeSettings subscribeSetting = new SubscribeSettings.Builder()
-                .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
-
-        Parcel parcelW = Parcel.obtain();
-        subscribeSetting.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);
-
-        assertEquals(subscribeSetting, rereadSubscribeSettings);
-    }
-
-    /*
-     * PublishData Tests
-     */
-
-    @Test
-    public void testPublishDataBuilder() {
-        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 };
-
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setRxFilter(rxFilter, rxFilter.length).build();
-
-        collector.checkThat("mServiceName", serviceName, equalTo(publishData.mServiceName));
-        String mServiceSpecificInfo = new String(publishData.mServiceSpecificInfo, 0,
-                publishData.mServiceSpecificInfoLength);
-        collector.checkThat("mServiceSpecificInfo",
-                utilAreArraysEqual(serviceSpecificInfo.getBytes(), serviceSpecificInfo.length(),
-                        publishData.mServiceSpecificInfo, publishData.mServiceSpecificInfoLength),
-                equalTo(true));
-        collector.checkThat("mTxFilter", utilAreArraysEqual(txFilter, txFilter.length,
-                publishData.mTxFilter, publishData.mTxFilterLength), equalTo(true));
-        collector.checkThat("mRxFilter", utilAreArraysEqual(rxFilter, rxFilter.length,
-                publishData.mRxFilter, publishData.mRxFilterLength), equalTo(true));
-    }
-
-    @Test
-    public void testPublishDataParcel() {
-        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 };
-
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setTxFilter(rxFilter, rxFilter.length).build();
-
-        Parcel parcelW = Parcel.obtain();
-        publishData.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);
-
-        assertEquals(publishData, rereadPublishData);
-    }
-
-    /*
-     * PublishSettings Tests
-     */
-
-    @Test
-    public void testPublishSettingsBuilder() {
-        final int publishType = PublishSettings.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
-        final int publishTtl = 15;
-
-        PublishSettings publishSetting = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
-
-        collector.checkThat("mPublishType", publishType, equalTo(publishSetting.mPublishType));
-        collector.checkThat("mPublishCount", publishCount, equalTo(publishSetting.mPublishCount));
-        collector.checkThat("mTtlSec", publishTtl, equalTo(publishSetting.mTtlSec));
-    }
-
-    @Test
-    public void testPublishSettingsBuilderBadPublishType() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setPublishType(5);
-    }
-
-    @Test
-    public void testPublishSettingsBuilderNegativeCount() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setPublishCount(-4);
-    }
-
-    @Test
-    public void testPublishSettingsBuilderNegativeTtl() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setTtlSec(-10);
-    }
-
-    @Test
-    public void testPublishSettingsParcel() {
-        final int publishType = PublishSettings.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
-        final int publishTtl = 15;
-
-        PublishSettings configSetting = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
-
-        Parcel parcelW = Parcel.obtain();
-        configSetting.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
-
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
-        PublishSettings rereadPublishSettings = PublishSettings.CREATOR.createFromParcel(parcelR);
-
-        assertEquals(configSetting, rereadPublishSettings);
-    }
-
-    /*
-     * Utilities
-     */
-
-    private static boolean utilAreArraysEqual(byte[] x, int xLength, byte[] y, int yLength) {
-        if (xLength != yLength) {
-            return false;
-        }
-
-        if (x != null && y != null) {
-            for (int i = 0; i < xLength; ++i) {
-                if (x[i] != y[i]) {
-                    return false;
-                }
-            }
-        } else if (xLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        return true;
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanStateManagerTest.java
deleted file mode 100644
index 49abd27..0000000
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanStateManagerTest.java
+++ /dev/null
@@ -1,1147 +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.nan;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.core.IsNull.nullValue;
-import static org.junit.Assert.assertTrue;
-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.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
-
-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.test.suitebuilder.annotation.SmallTest;
-import android.util.SparseArray;
-
-import com.android.server.wifi.MockLooper;
-
-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;
-
-/**
- * Unit test harness for WifiNanStateManager.
- */
-@SmallTest
-public class WifiNanStateManagerTest {
-    private MockLooper mMockLooper;
-    private WifiNanStateManager mDut;
-    @Mock private WifiNanNative mMockNative;
-
-    @Rule
-    public ErrorCollector collector = new ErrorCollector();
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mMockLooper = new MockLooper();
-
-        mDut = installNewNanStateManagerAndResetState();
-        mDut.start(mMockLooper.getLooper());
-
-        installMockWifiNanNative(mMockNative);
-    }
-
-    @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;
-        final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
-
-        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);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, 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);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest1));
-        short transactionId1 = transactionId.getValue();
-
-        mDut.requestConfig(uid, configRequest2);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest2));
-        short transactionId2 = transactionId.getValue();
-
-        mDut.onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, someMac);
-        mDut.onConfigCompleted(transactionId1);
-        mDut.onConfigFailed(transactionId2, reason);
-        mDut.onInterfaceAddressChange(someMac);
-        mDut.onNanDown(reason);
-        mMockLooper.dispatchAll();
-
-        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);
-    }
-
-    @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);
-
-        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);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, mMockNative);
-
-        mDut.connect(uid, mockListener, 0);
-        mDut.requestConfig(uid, configRequest1);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest1));
-        short transactionId1 = transactionId.getValue();
-
-        mDut.requestConfig(uid, configRequest2);
-        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);
-        mMockLooper.dispatchAll();
-
-        verifyZeroInteractions(mockListener);
-
-        validateInternalTransactionInfoCleanedUp(transactionId1);
-        validateInternalTransactionInfoCleanedUp(transactionId2);
-    }
-
-    @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;
-
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED)
-                .setPublishCount(publishCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, 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);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishFail(transactionId.getValue(), reasonFail);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mockListener).onPublishFail(reasonFail);
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-
-        // publish - success/terminate
-        mDut.publish(uid, sessionId, publishData, publishSettings);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishSuccess(transactionId.getValue(), publishId1);
-        mMockLooper.dispatchAll();
-
-        mDut.onPublishTerminated(publishId1, reasonTerminate);
-        mMockLooper.dispatchAll();
-
-        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);
-    }
-
-    @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;
-        final int publishId = 15;
-
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        PublishSettings publishSettings = new PublishSettings.Builder()
-                .setPublishType(PublishSettings.PUBLISH_TYPE_UNSOLICITED)
-                .setPublishCount(publishCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, 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);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishFail(transactionId.getValue(), reasonFail);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-
-        // publish - success/terminate
-        mDut.publish(uid, sessionId, publishData, publishSettings);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).publish(transactionId.capture(), eq(0), eq(publishData),
-                eq(publishSettings));
-
-        mDut.onPublishSuccess(transactionId.getValue(), publishId);
-        mMockLooper.dispatchAll();
-
-        mDut.onPublishTerminated(publishId, reasonTerminate);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        verifyNoMoreInteractions(mockListener, mMockNative);
-    }
-
-    @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;
-
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE)
-                .setSubscribeCount(subscribeCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, 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);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeFail(transactionId.getValue(), reasonFail);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onSubscribeFail(reasonFail);
-
-        // subscribe - success/terminate
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeSuccess(transactionId.getValue(), subscribeId1);
-        mMockLooper.dispatchAll();
-
-        mDut.onSubscribeTerminated(subscribeId1, reasonTerminate);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onSubscribeTerminated(reasonTerminate);
-
-        // re-subscribe
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeSuccess(transactionId.getValue(), subscribeId2);
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(subscribeId2),
-                eq(subscribeData), eq(subscribeSettings));
-        verifyNoMoreInteractions(mockListener, mMockNative);
-    }
-
-    @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;
-        final int subscribeId = 15;
-
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE)
-                .setSubscribeCount(subscribeCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, 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);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeFail(transactionId.getValue(), reasonFail);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-
-        // subscribe - success/terminate
-        mDut.subscribe(uid, sessionId, subscribeData, subscribeSettings);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        mDut.onSubscribeSuccess(transactionId.getValue(), subscribeId);
-        mMockLooper.dispatchAll();
-
-        mDut.onSubscribeTerminated(subscribeId, reasonTerminate);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        verifyNoMoreInteractions(mockListener, mMockNative);
-    }
-
-    @Test
-    public void testMatchAndMessages() 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 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 String peerMsg = "some message from peer";
-        final int messageId = 6948;
-
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
-                .setSubscribeType(SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE)
-                .setSubscribeCount(subscribeCount).build();
-
-        IWifiNanSessionListener mockListener = mock(IWifiNanSessionListener.class);
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        InOrder inOrder = inOrder(mockListener, 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);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq(0), eq(subscribeData),
-                eq(subscribeSettings));
-
-        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(),
-                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(),
-                peerMsg.length());
-
-        mDut.sendMessage(uid, sessionId, requestorId, ssi.getBytes(), ssi.length(), messageId);
-        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);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onMessageSendFail(messageId, reasonFail);
-
-        mDut.sendMessage(uid, sessionId, requestorId, ssi.getBytes(), ssi.length(), messageId);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).sendMessage(transactionId.capture(), eq(subscribeId),
-                eq(requestorId), eq(peerMac), eq(ssi.getBytes()), eq(ssi.length()));
-
-        mDut.onMessageSendSuccess(transactionId.getValue());
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener).onMessageSendSuccess(messageId);
-
-        verifyNoMoreInteractions(mockListener, mMockNative);
-    }
-
-    /**
-     * Summary: in a single publish session interact with multiple peers
-     * (different MAC addresses).
-     */
-    @Test
-    public void testMultipleMessageSources() throws Exception {
-        final int uid = 300;
-        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;
-        final int peerId2 = 873;
-        final byte[] peerMac1 = HexEncoding.decode("000102030405".toCharArray(), false);
-        final byte[] peerMac2 = HexEncoding.decode("060708090A0B".toCharArray(), false);
-        final String msgFromPeer1 = "hey from 000102...";
-        final String msgFromPeer2 = "hey from 0607...";
-        final String msgToPeer1 = "hey there 000102...";
-        final String msgToPeer2 = "hey there 0506...";
-        final int msgToPeerId1 = 546;
-        final int msgToPeerId2 = 9654;
-        final int reason = WifiNanSessionListener.FAIL_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();
-
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
-        IWifiNanSessionListener mockSessionListener = mock(IWifiNanSessionListener.class);
-        InOrder inOrder = inOrder(mMockNative, mockListener, mockSessionListener);
-
-        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);
-        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);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionIdConfig);
-        validateInternalTransactionInfoCleanedUp(transactionIdPublish);
-        inOrder.verify(mockListener).onConfigCompleted(configRequest);
-        inOrder.verify(mockSessionListener).onMessageReceived(peerId1, msgFromPeer1.getBytes(),
-                msgFromPeer1.length());
-        inOrder.verify(mockSessionListener).onMessageReceived(peerId2, msgFromPeer2.getBytes(),
-                msgFromPeer2.length());
-        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();
-
-        mDut.onMessageSendFail(transactionIdMsg1, reason);
-        mDut.onMessageSendSuccess(transactionIdMsg2);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg1);
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg2);
-        inOrder.verify(mockSessionListener).onMessageSendFail(msgToPeerId1, reason);
-        inOrder.verify(mockSessionListener).onMessageSendSuccess(msgToPeerId2);
-        verifyNoMoreInteractions(mMockNative, mockListener, mockSessionListener);
-    }
-
-    /**
-     * Summary: interact with a peer which changed its identity (MAC address)
-     * but which keeps its requestor instance ID. Should be transparent.
-     */
-    @Test
-    public void testMessageWhilePeerChangesIdentity() throws Exception {
-        final int uid = 300;
-        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;
-        final byte[] peerMacOrig = HexEncoding.decode("000102030405".toCharArray(), false);
-        final byte[] peerMacLater = HexEncoding.decode("060708090A0B".toCharArray(), false);
-        final String msgFromPeer1 = "hey from 000102...";
-        final String msgFromPeer2 = "hey from 0607...";
-        final String msgToPeer1 = "hey there 000102...";
-        final String msgToPeer2 = "hey there 0506...";
-        final int msgToPeerId1 = 546;
-        final int msgToPeerId2 = 9654;
-        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();
-
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        IWifiNanEventListener mockListener = mock(IWifiNanEventListener.class);
-        IWifiNanSessionListener mockSessionListener = mock(IWifiNanSessionListener.class);
-        InOrder inOrder = inOrder(mMockNative, mockListener, mockSessionListener);
-
-        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);
-        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, peerId, peerMacOrig, msgFromPeer1.getBytes(),
-                msgFromPeer1.length());
-        mDut.sendMessage(uid, sessionId, peerId, msgToPeer1.getBytes(), msgToPeer1.length(),
-                msgToPeerId1);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionIdConfig);
-        validateInternalTransactionInfoCleanedUp(transactionIdPublish);
-        inOrder.verify(mockListener).onConfigCompleted(configRequest);
-        inOrder.verify(mockSessionListener).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);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg);
-        inOrder.verify(mockSessionListener).onMessageSendSuccess(msgToPeerId1);
-        inOrder.verify(mockSessionListener).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();
-
-        mDut.onMessageSendSuccess(transactionIdMsg);
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionIdMsg);
-        inOrder.verify(mockSessionListener).onMessageSendSuccess(msgToPeerId2);
-
-        verifyNoMoreInteractions(mMockNative, mockListener, mockSessionListener);
-    }
-
-    @Test
-    public void testConfigs() throws Exception {
-        final int uid1 = 9999;
-        final int clusterLow1 = 5;
-        final int clusterHigh1 = 100;
-        final int masterPref1 = 111;
-        final int uid2 = 1001;
-        final boolean support5g2 = true;
-        final int clusterLow2 = 7;
-        final int clusterHigh2 = 155;
-        final int masterPref2 = 0;
-        final int uid3 = 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();
-
-        ConfigRequest configRequest2 = new ConfigRequest.Builder().setSupport5gBand(support5g2)
-                .setClusterLow(clusterLow2).setClusterHigh(clusterHigh2)
-                .setMasterPreference(masterPref2).build();
-
-        ConfigRequest configRequest3 = new ConfigRequest.Builder().build();
-
-        IWifiNanEventListener mockListener1 = mock(IWifiNanEventListener.class);
-        IWifiNanEventListener mockListener2 = mock(IWifiNanEventListener.class);
-        IWifiNanEventListener mockListener3 = mock(IWifiNanEventListener.class);
-
-        InOrder inOrder = inOrder(mMockNative, mockListener1, mockListener2, mockListener3);
-
-        mDut.connect(uid1, mockListener1, WifiNanEventListener.LISTEN_CONFIG_COMPLETED);
-        mDut.requestConfig(uid1, configRequest1);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
-                crCapture.capture());
-        collector.checkThat("merge: stage 0", configRequest1, equalTo(crCapture.getValue()));
-
-        mDut.onConfigCompleted(transactionId.getValue());
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener1).onConfigCompleted(configRequest1);
-
-        mDut.connect(uid2, mockListener2, WifiNanEventListener.LISTEN_CONFIG_COMPLETED);
-        mDut.requestConfig(uid2, configRequest2);
-        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());
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener1).onConfigCompleted(crCapture.getValue());
-
-        mDut.connect(uid3, mockListener3, WifiNanEventListener.LISTEN_CONFIG_COMPLETED);
-        mDut.requestConfig(uid3, configRequest3);
-        mMockLooper.dispatchAll();
-
-        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());
-        mMockLooper.dispatchAll();
-
-        validateInternalTransactionInfoCleanedUp(transactionId.getValue());
-        inOrder.verify(mockListener1).onConfigCompleted(crCapture.getValue());
-
-        mDut.disconnect(uid2);
-        mMockLooper.dispatchAll();
-
-        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);
-    }
-
-    /**
-     * 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 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 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)
-                .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);
-
-        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);
-        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();
-
-        validateInternalClientInfoCleanedUp(uid);
-        validateInternalTransactionInfoCleanedUp(transactionIdPublish);
-
-        mDut.onConfigCompleted(transactionIdConfig);
-        mDut.onPublishSuccess(transactionIdPublish, publishId);
-        mMockLooper.dispatchAll();
-
-        mDut.onPublishTerminated(publishId, reason);
-        mMockLooper.dispatchAll();
-
-        verifyZeroInteractions(mockListener, mockSessionListener);
-    }
-
-    /**
-     * Summary: destroy a session while there are pending transactions. Validate
-     * that no callbacks are called and that internal state is cleaned-up.
-     */
-    @Test
-    public void testDestroySessionWithPendingTransactions() throws Exception {
-        final int uid = 128;
-        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)
-                .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);
-
-        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);
-        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);
-        mMockLooper.dispatchAll();
-
-        mDut.onPublishTerminated(publishId, reason);
-        mDut.destroySession(uid, subscribeSessionId);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mockListener).onConfigCompleted(configRequest);
-        verifyZeroInteractions(mockPublishSessionListener);
-        verifyNoMoreInteractions(mockSubscribeSessionListener);
-    }
-
-    @Test
-    public void testTransactionIdIncrement() {
-        int loopCount = 100;
-
-        short prevId = 0;
-        for (int i = 0; i < loopCount; ++i) {
-            short id = mDut.createNextTransactionId();
-            if (i != 0) {
-                assertTrue("Transaction ID incrementing", id > prevId);
-            }
-            prevId = id;
-        }
-    }
-
-    /*
-     * Tests of internal state of WifiNanStateManager: 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.
-     */
-
-    /**
-     * 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.
-     *
-     * @param transactionId The transaction ID whose state should be erased.
-     */
-    public void validateInternalTransactionInfoCleanedUp(short transactionId) throws Exception {
-        Object info = getInternalPendingTransactionInfo(mDut, transactionId);
-        collector.checkThat("Transaction record not cleared up for transactionId=" + transactionId,
-                info, 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.
-     *
-     * @param uid The ID of the client which should be deleted.
-     */
-    public void validateInternalClientInfoCleanedUp(int uid) throws Exception {
-        WifiNanClientState client = getInternalClientState(mDut, uid);
-        collector.checkThat("Client record not cleared up for uid=" + uid, client, nullValue());
-    }
-
-    /*
-     * Utilities
-     */
-
-    private static WifiNanStateManager installNewNanStateManagerAndResetState() 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 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 {
-        Field field = WifiNanStateManager.class.getDeclaredField("mClients");
-        field.setAccessible(true);
-        @SuppressWarnings("unchecked")
-        SparseArray<WifiNanClientState> clients = (SparseArray<WifiNanClientState>) field.get(dut);
-
-        return clients.get(uid);
-    }
-}
-
diff --git a/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java
new file mode 100644
index 0000000..4443bb1
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceCallbackTest.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi.p2p;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIfaceCallback;
+import android.hardware.wifi.supplicant.V1_0.WpsConfigMethods;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+
+import com.android.server.wifi.util.NativeUtil;
+
+import org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+
+/**
+ * Unit tests for SupplicantP2pIfaceCallback
+ */
+public class SupplicantP2pIfaceCallbackTest {
+    private static final String TAG = "SupplicantP2pIfaceCallbackTest";
+
+    private String mIface = "test_p2p0";
+    private WifiP2pMonitor mMonitor;
+    private SupplicantP2pIfaceCallback mDut;
+
+    private byte[] mDeviceAddressInvalid1 = { 0x00 };
+    private byte[] mDeviceAddressInvalid2 = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
+    private byte[] mDeviceAddress1Bytes = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
+    private String mDeviceAddress1String = "00:11:22:33:44:55";
+    private byte[] mDeviceAddress2Bytes = { 0x01, 0x12, 0x23, 0x34, 0x45, 0x56 };
+    private String mDeviceAddress2String = "01:12:23:34:45:56";
+    private byte[] mDeviceInfoBytes = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+    private static final byte[] DEVICE_ADDRESS = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+    private class SupplicantP2pIfaceCallbackSpy extends SupplicantP2pIfaceCallback {
+        SupplicantP2pIfaceCallbackSpy(String iface, WifiP2pMonitor monitor) {
+            super(iface, monitor);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mMonitor = mock(WifiP2pMonitor.class);
+        mDut = new SupplicantP2pIfaceCallbackSpy(mIface, mMonitor);
+    }
+
+    /**
+     * Sunny day scenario for onDeviceFound call.
+     */
+    @Test
+    public void testOnDeviceFound_success() throws Exception {
+        byte[] fakePrimaryDeviceTypeBytes = { 0x01, 0x02, 0x03 };
+        String fakePrimaryDeviceTypeString = "010203";
+        String fakeDeviceName = "test device name";
+        short fakeConfigMethods = 0x1234;
+        byte fakeCapabilities = 123;
+        int fakeGroupCapabilities = 456;
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(String iface, WifiP2pDevice device) {
+                // NOTE: mDeviceAddress1Bytes seems to be ignored by
+                // legacy implementation of WifiP2pDevice.
+                assertEquals(iface, mIface);
+                assertEquals(device.deviceName, fakeDeviceName);
+                assertEquals(device.primaryDeviceType, fakePrimaryDeviceTypeString);
+                assertEquals(device.deviceCapability, fakeCapabilities);
+                assertEquals(device.groupCapability, fakeGroupCapabilities);
+                assertEquals(device.wpsConfigMethodsSupported, fakeConfigMethods);
+                assertEquals(device.deviceAddress, mDeviceAddress2String);
+                assertEquals(device.status, WifiP2pDevice.AVAILABLE);
+            }
+        })
+        .when(mMonitor).broadcastP2pDeviceFound(
+                anyString(), any(WifiP2pDevice.class));
+
+        mDut.onDeviceFound(
+                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
+                fakePrimaryDeviceTypeBytes,
+                fakeDeviceName, fakeConfigMethods,
+                fakeCapabilities, fakeGroupCapabilities,
+                mDeviceInfoBytes);
+
+        mDut.onDeviceFound(
+                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
+                fakePrimaryDeviceTypeBytes,
+                fakeDeviceName, fakeConfigMethods,
+                fakeCapabilities, fakeGroupCapabilities,
+                null);
+
+        // Make sure we issued a broadcast each time.
+        verify(mMonitor, times(2)).broadcastP2pDeviceFound(
+                anyString(), any(WifiP2pDevice.class));
+    }
+
+    /**
+     * Failing scenarios for onDeviceFound call.
+     */
+    @Test
+    public void testOnDeviceFound_invalidArguments() throws Exception {
+        byte[] fakePrimaryDeviceTypeBytes = { 0x01, 0x02, 0x03 };
+        String fakePrimaryDeviceTypeString = "010203";
+        String fakeDeviceName = "test device name";
+        short fakeConfigMethods = 0x1234;
+        byte fakeCapabilities = 123;
+        int fakeGroupCapabilities = 456;
+
+        mDut.onDeviceFound(
+                mDeviceAddress2Bytes, null,
+                fakePrimaryDeviceTypeBytes,
+                fakeDeviceName, fakeConfigMethods,
+                fakeCapabilities, fakeGroupCapabilities,
+                mDeviceInfoBytes);
+        verify(mMonitor, never()).broadcastP2pDeviceFound(
+                anyString(), any(WifiP2pDevice.class));
+
+
+        mDut.onDeviceFound(
+                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
+                null,
+                fakeDeviceName, fakeConfigMethods,
+                fakeCapabilities, fakeGroupCapabilities,
+                mDeviceInfoBytes);
+        verify(mMonitor, never()).broadcastP2pDeviceFound(
+                anyString(), any(WifiP2pDevice.class));
+
+
+        mDut.onDeviceFound(
+                mDeviceAddress1Bytes, mDeviceAddress2Bytes,
+                fakePrimaryDeviceTypeBytes,
+                null, fakeConfigMethods,
+                fakeCapabilities, fakeGroupCapabilities,
+                mDeviceInfoBytes);
+        verify(mMonitor, never()).broadcastP2pDeviceFound(
+                anyString(), any(WifiP2pDevice.class));
+
+
+        mDut.onDeviceFound(
+                mDeviceAddress1Bytes, mDeviceAddressInvalid1,
+                fakePrimaryDeviceTypeBytes,
+                null, fakeConfigMethods,
+                fakeCapabilities, fakeGroupCapabilities,
+                mDeviceInfoBytes);
+        verify(mMonitor, never()).broadcastP2pDeviceFound(
+                anyString(), any(WifiP2pDevice.class));
+
+
+        mDut.onDeviceFound(
+                mDeviceAddress1Bytes, mDeviceAddressInvalid2,
+                fakePrimaryDeviceTypeBytes,
+                null, fakeConfigMethods,
+                fakeCapabilities, fakeGroupCapabilities,
+                mDeviceInfoBytes);
+        verify(mMonitor, never()).broadcastP2pDeviceFound(
+                anyString(), any(WifiP2pDevice.class));
+    }
+
+    /**
+     * Sunny day scenario for onDeviceLost call.
+     */
+    @Test
+    public void testOnDeviceLost_success() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(String iface, WifiP2pDevice device) {
+                assertEquals(iface, mIface);
+                assertEquals(device.deviceAddress, mDeviceAddress1String);
+                assertEquals(device.status, WifiP2pDevice.UNAVAILABLE);
+            }
+        })
+        .when(mMonitor).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+
+        mDut.onDeviceLost(mDeviceAddress1Bytes);
+
+        // Make sure we issued a broadcast each time.
+        verify(mMonitor, times(1)).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+    }
+
+    /**
+     * Failing scenarios for onDeviceLost call.
+     */
+    @Test
+    public void testOnDeviceLost_invalidArguments() throws Exception {
+        mDut.onDeviceLost(null);
+        verify(mMonitor, never()).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+
+        mDut.onDeviceLost(mDeviceAddressInvalid1);
+        verify(mMonitor, never()).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+
+        mDut.onDeviceLost(mDeviceAddressInvalid2);
+        verify(mMonitor, never()).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+    }
+
+    /**
+     * Sunny day scenario for onGoNegotiationRequest call.
+     */
+    @Test
+    public void testOnGoNegotiationRequest_success() throws Exception {
+        HashSet<Integer> setups = new HashSet<Integer>();
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(String iface, WifiP2pConfig config) {
+                assertEquals(iface, mIface);
+                assertNotNull(config.wps);
+                setups.add(config.wps.setup);
+                assertEquals(config.deviceAddress, mDeviceAddress1String);
+            }
+        })
+        .when(mMonitor).broadcastP2pGoNegotiationRequest(
+                anyString(), any(WifiP2pConfig.class));
+
+        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
+                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.USER_SPECIFIED);
+        assertTrue(setups.contains(WpsInfo.DISPLAY));
+
+        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
+                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.PUSHBUTTON);
+        assertTrue(setups.contains(WpsInfo.PBC));
+
+        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes,
+                (short)ISupplicantP2pIfaceCallback.WpsDevPasswordId.REGISTRAR_SPECIFIED);
+        assertTrue(setups.contains(WpsInfo.KEYPAD));
+
+        // Invalid should default to PBC
+        setups.clear();
+        mDut.onGoNegotiationRequest(mDeviceAddress1Bytes, (short)0xffff);
+        assertTrue(setups.contains(WpsInfo.PBC));
+    }
+
+    /**
+     * Failing scenarios for onGoNegotiationRequest call.
+     */
+    @Test
+    public void testOnGoNegotiationRequest_invalidArguments() throws Exception {
+        mDut.onGoNegotiationRequest(null, (short)0);
+        verify(mMonitor, never()).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+
+        mDut.onGoNegotiationRequest(mDeviceAddressInvalid1, (short)0);
+        verify(mMonitor, never()).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+
+        mDut.onGoNegotiationRequest(mDeviceAddressInvalid2, (short)0);
+        verify(mMonitor, never()).broadcastP2pDeviceLost(
+                anyString(), any(WifiP2pDevice.class));
+    }
+
+    /**
+     * Sunny day scenario for onGroupStarted call.
+     */
+    @Test
+    public void testOnGroupStarted_success() throws Exception {
+        String fakeName = "group name";
+        String fakePassphrase = "secret";
+        ArrayList<Byte> fakeSsidBytesList = new ArrayList<Byte>() {{
+            add((byte)0x30);
+            add((byte)0x31);
+            add((byte)0x32);
+            add((byte)0x33);
+        }};
+        String fakeSsidString = "0123";
+        HashSet<String> passwords = new HashSet<String>();
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(String iface, WifiP2pGroup group) {
+                assertEquals(iface, mIface);
+                assertNotNull(group.getOwner());
+                assertEquals(group.getOwner().deviceAddress, mDeviceAddress1String);
+                assertEquals(group.getNetworkId(), WifiP2pGroup.PERSISTENT_NET_ID);
+                passwords.add(group.getPassphrase());
+                assertEquals(group.getInterface(), fakeName);
+                assertEquals(group.getNetworkName(), fakeSsidString);
+            }
+        })
+        .when(mMonitor).broadcastP2pGroupStarted(
+                anyString(), any(WifiP2pGroup.class));
+
+        mDut.onGroupStarted(
+                fakeName, true, fakeSsidBytesList, 1, null, fakePassphrase,
+                mDeviceAddress1Bytes, true);
+        assertTrue(passwords.contains(fakePassphrase));
+
+        mDut.onGroupStarted(
+                fakeName, true, fakeSsidBytesList, 1, null, null,
+                mDeviceAddress1Bytes, true);
+        assertTrue(passwords.contains(null));
+
+        verify(mMonitor, times(2)).broadcastP2pGroupStarted(
+                anyString(), any(WifiP2pGroup.class));
+    }
+
+    /**
+     * Failing scenarios for onGroupStarted call.
+     */
+    @Test
+    public void testOnGroupStarted_invalidArguments() throws Exception {
+        String fakeName = "group name";
+        String fakePassphrase = "secret";
+        ArrayList<Byte> fakeSsidBytesList = new ArrayList<Byte>() {{
+            add((byte)0x30);
+            add((byte)0x31);
+            add((byte)0x32);
+            add((byte)0x33);
+        }};
+        String fakeSsidString = "0123";
+
+        mDut.onGroupStarted(
+                null, true, fakeSsidBytesList, 1, null, fakePassphrase,
+                mDeviceAddress1Bytes, true);
+        verify(mMonitor, never()).broadcastP2pGroupStarted(
+                anyString(), any(WifiP2pGroup.class));
+
+        mDut.onGroupStarted(
+                fakeName, true, null, 1, null, fakePassphrase,
+                mDeviceAddress1Bytes, true);
+        verify(mMonitor, never()).broadcastP2pGroupStarted(
+                anyString(), any(WifiP2pGroup.class));
+
+        mDut.onGroupStarted(
+                fakeName, true, fakeSsidBytesList, 1, null, fakePassphrase,
+                null, true);
+        verify(mMonitor, never()).broadcastP2pGroupStarted(
+                anyString(), any(WifiP2pGroup.class));
+    }
+
+    /**
+     * Test provision discovery callback.
+     */
+    @Test
+    public void testOnProvisionDiscoveryCompleted() throws Exception {
+        byte[] p2pDeviceAddr = DEVICE_ADDRESS;
+        boolean isRequest = false;
+        byte status = ISupplicantP2pIfaceCallback.P2pProvDiscStatusCode.SUCCESS;
+        short configMethods = WpsConfigMethods.DISPLAY;
+        String generatedPin = "12345678";
+
+        ArgumentCaptor<WifiP2pProvDiscEvent> discEventCaptor =
+                ArgumentCaptor.forClass(WifiP2pProvDiscEvent.class);
+        mDut.onProvisionDiscoveryCompleted(
+                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
+        verify(mMonitor).broadcastP2pProvisionDiscoveryEnterPin(
+                anyString(), discEventCaptor.capture());
+        assertEquals(WifiP2pProvDiscEvent.ENTER_PIN, discEventCaptor.getValue().event);
+
+        configMethods = WpsConfigMethods.KEYPAD;
+        mDut.onProvisionDiscoveryCompleted(
+                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
+        verify(mMonitor).broadcastP2pProvisionDiscoveryShowPin(
+                anyString(), discEventCaptor.capture());
+        assertEquals(WifiP2pProvDiscEvent.SHOW_PIN, discEventCaptor.getValue().event);
+        assertEquals(generatedPin, discEventCaptor.getValue().pin);
+
+        isRequest = true;
+        configMethods = WpsConfigMethods.KEYPAD;
+        mDut.onProvisionDiscoveryCompleted(
+                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
+        verify(mMonitor, times(2)).broadcastP2pProvisionDiscoveryEnterPin(
+                anyString(), discEventCaptor.capture());
+        assertEquals(WifiP2pProvDiscEvent.ENTER_PIN, discEventCaptor.getValue().event);
+
+        configMethods = WpsConfigMethods.DISPLAY;
+        mDut.onProvisionDiscoveryCompleted(
+                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
+        verify(mMonitor, times(2)).broadcastP2pProvisionDiscoveryShowPin(
+                anyString(), discEventCaptor.capture());
+        assertEquals(WifiP2pProvDiscEvent.SHOW_PIN, discEventCaptor.getValue().event);
+        assertEquals(generatedPin, discEventCaptor.getValue().pin);
+
+        isRequest = false;
+        configMethods = WpsConfigMethods.PUSHBUTTON;
+        mDut.onProvisionDiscoveryCompleted(
+                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
+        verify(mMonitor).broadcastP2pProvisionDiscoveryPbcResponse(
+                anyString(), discEventCaptor.capture());
+        assertEquals(WifiP2pProvDiscEvent.PBC_RSP, discEventCaptor.getValue().event);
+
+        isRequest = true;
+        mDut.onProvisionDiscoveryCompleted(
+                p2pDeviceAddr, isRequest, status, configMethods, generatedPin);
+        verify(mMonitor).broadcastP2pProvisionDiscoveryPbcRequest(
+                anyString(), discEventCaptor.capture());
+        assertEquals(WifiP2pProvDiscEvent.PBC_REQ, discEventCaptor.getValue().event);
+    }
+
+    /**
+     * Test staAuth with device address, should trigger ApStaConnected broadcast
+     */
+    @Test
+    public void testStaAuth_success() {
+        // Trigger onStaAuthorized callback, ensure wifimonitor broadcast is sent with WifiP2pDevice
+        // using the p2pDeviceAddress
+        ArgumentCaptor<WifiP2pDevice> p2pDeviceCaptor =
+                ArgumentCaptor.forClass(WifiP2pDevice.class);
+        mDut.onStaAuthorized(mDeviceAddress1Bytes, mDeviceAddress2Bytes);
+        verify(mMonitor).broadcastP2pApStaConnected(any(String.class), p2pDeviceCaptor.capture());
+        assertEquals(mDeviceAddress2String, p2pDeviceCaptor.getValue().deviceAddress);
+    }
+
+    /**
+     * Test staAuth without device address, should trigger ApStaConnected broadcast using srcAddress
+     */
+    @Test
+    public void testStaAuth_noDeviceAddress_success() {
+        // Trigger onStaAuthorized callback, using a zero'd p2pDeviceAddress, ensure wifimonitor
+        // broadcast is sent with WifiP2pDevice using the srcAddress
+        ArgumentCaptor<WifiP2pDevice> p2pDeviceCaptor =
+                ArgumentCaptor.forClass(WifiP2pDevice.class);
+        mDut.onStaAuthorized(mDeviceAddress1Bytes, NativeUtil.ANY_MAC_BYTES);
+        verify(mMonitor).broadcastP2pApStaConnected(any(String.class), p2pDeviceCaptor.capture());
+        assertEquals(mDeviceAddress1String, p2pDeviceCaptor.getValue().deviceAddress);
+    }
+
+    // TLVS hex data encoded as a hex string.
+    // Taken directly from an observed supplicant service response event
+    private static final String SERV_DISC_RESP_TLVS = "1d00010100076578616d706c650b5f6166706f766572"
+            + "746370c00c001001001e000101000b5f6166706f766572746370c00c000c01074578616d706c65c0273c"
+            + "00010100096d797072696e746572045f697070c00c00100109747874766572733d311a70646c3d617070"
+            + "6c69636174696f6e2f706f73747363726970741900010100045f697070c00c000c01094d795072696e74"
+            + "6572c0275f000201000a757569643a36383539646564652d383537342d353961622d393333322d313233"
+            + "3435363738393031313a3a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f"
+            + "6e6e656374696f6e4d616e616765723a3159000201000a757569643a36383539646564652d383537342d"
+            + "353961622d393333322d3132333435363738393031313a3a75726e3a736368656d61732d75706e702d6f"
+            + "72673a736572766963653a41565472616e73706f72743a315a000201000a757569643a36383539646564"
+            + "652d383537342d353961622d393333322d3132333435363738393031313a3a75726e3a736368656d6173"
+            + "2d75706e702d6f72673a6465766963653a4d6564696152656e64657265723a313e000201000a75756964"
+            + "3a36383539646564652d383537342d353961622d393333322d3132333435363738393031313a3a75706e"
+            + "703a726f6f746465766963652d000201000a757569643a36383539646564652d383537342d353961622d"
+            + "393333322d313233343536373839303131";
+
+    /**
+     * Pretty basic onServiceDiscoveryResponse callback test.
+     * Mocks the callback event, passing some observed real data to it, and ensures that it returns
+     * a non-null WifiP2pServiceResponse list.
+     */
+    @Test
+    public void testOnServiceDiscoveryResponseCompleted_success() throws Exception {
+        ArrayList<Byte> tlvs = NativeUtil.byteArrayToArrayList(hexStr2Bin(SERV_DISC_RESP_TLVS));
+        ArgumentCaptor<List<WifiP2pServiceResponse>> respListCaptor =
+                ArgumentCaptor.forClass(List.class);
+        mDut.onServiceDiscoveryResponse(
+                mDeviceAddress1Bytes,
+                (short) 10 /* unused updateIndicator value */,
+                tlvs);
+        verify(mMonitor).broadcastP2pServiceDiscoveryResponse(anyString(),
+                respListCaptor.capture());
+        assertNotNull(respListCaptor.getValue());
+    }
+
+    /**
+     * Converts hex string to byte array.
+     *
+     * @param hex hex string. if invalid, return null.
+     * @return binary data.
+     */
+    private static byte[] hexStr2Bin(String hex) {
+        int sz = hex.length() / 2;
+        byte[] b = new byte[hex.length() / 2];
+        for (int i = 0; i < sz; i++) {
+            try {
+                b[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
+            } catch (Exception e) {
+                return null;
+            }
+        }
+        return b;
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
new file mode 100644
index 0000000..bcce0ac
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/p2p/SupplicantP2pIfaceHalTest.java
@@ -0,0 +1,2618 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wifi.p2p;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.hardware.wifi.supplicant.V1_0.ISupplicant;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantNetwork;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pIface;
+import android.hardware.wifi.supplicant.V1_0.ISupplicantP2pNetwork;
+import android.hardware.wifi.supplicant.V1_0.IfaceType;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatus;
+import android.hardware.wifi.supplicant.V1_0.SupplicantStatusCode;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pGroupList;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+import com.android.server.wifi.util.NativeUtil;
+
+import org.junit.Assert.*;
+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.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * Unit tests for SupplicantP2pIfaceHal
+ */
+public class SupplicantP2pIfaceHalTest {
+    private static final String TAG = "SupplicantP2pIfaceHalTest";
+    private SupplicantP2pIfaceHal mDut;
+    @Mock IServiceManager mServiceManagerMock;
+    @Mock ISupplicant mISupplicantMock;
+    @Mock ISupplicantIface mISupplicantIfaceMock;
+    @Mock ISupplicantP2pIface mISupplicantP2pIfaceMock;
+    @Mock ISupplicantP2pNetwork mISupplicantP2pNetworkMock;
+
+    SupplicantStatus mStatusSuccess;
+    SupplicantStatus mStatusFailure;
+    RemoteException mRemoteException;
+    ISupplicant.IfaceInfo mStaIface;
+    ISupplicant.IfaceInfo mP2pIface;
+    ArrayList<ISupplicant.IfaceInfo> mIfaceInfoList;
+
+    final String mIfaceName = "virtual_interface_name";
+    final String mSsid = "\"SSID\"";
+    final ArrayList<Byte> mSsidBytes = new ArrayList<Byte>() {{
+        add((byte)'S'); add((byte)'S'); add((byte)'I'); add((byte)'D');
+    }};
+    final String mPeerMacAddress = "00:11:22:33:44:55";
+    final byte mPeerMacAddressBytes[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
+    final String mGroupOwnerMacAddress = "01:12:23:34:45:56";
+    final byte mGroupOwnerMacAddressBytes[] = { 0x01, 0x12, 0x23, 0x34, 0x45, 0x56 };
+    final String mInvalidMacAddress1 = "00:11:22:33:44";
+    final String mInvalidMacAddress2 = ":::::";
+    final String mInvalidMacAddress3 = "invalid";
+    final byte mInvalidMacAddressBytes1[] = null;
+    final byte mInvalidMacAddressBytes2[] = {};
+    final byte mInvalidMacAddressBytes3[] = { 0x00, 0x01, 0x02, 0x03, 0x04 };
+    final byte mInvalidMacAddressBytes4[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+    HashSet<String> mInvalidMacAddresses = new HashSet<String>(Arrays.asList(
+            mInvalidMacAddress1, mInvalidMacAddress2,
+            mInvalidMacAddress3));
+    HashSet<byte[]> mInvalidMacAddressesBytes = new HashSet<byte[]>(Arrays.asList(
+            mInvalidMacAddressBytes1, mInvalidMacAddressBytes2,
+            mInvalidMacAddressBytes3, mInvalidMacAddressBytes4));
+
+    final String mInvalidService1 = null;
+    final String mInvalidService2 = "service";
+    final String mValidServiceRequestString = "30313233";
+    final byte[] mValidServiceRequestBytes = { 0x30, 0x31, 0x32, 0x33 };
+    final String mInvalidServiceRequestString = "not a hex string";
+    final String mInvalidUpnpService1 = "upnp";
+    final String mInvalidUpnpService2 = "upnp 1";
+    final String mInvalidUpnpService3 = "upnp invalid_number name";
+    final String mInvalidBonjourService1 = "bonjour";
+    final String mInvalidBonjourService2 = "bonjour 123456";
+    final String mInvalidBonjourService3 = "bonjour invalid_hex 123456";
+    final String mInvalidBonjourService4 = "bonjour 123456 invalid_hex";
+    final String mValidUpnpService = "upnp 10 serviceName";
+    final int mValidUpnpServiceVersion = 16;
+    final String mValidUpnpServiceName = "serviceName";
+    final String mValidBonjourService = "bonjour 30313233 34353637";
+    final ArrayList<Byte> mValidBonjourServiceRequest = new ArrayList<Byte>() {{
+        add((byte)'0'); add((byte)'1'); add((byte)'2'); add((byte)'3');
+    }};
+    final ArrayList<Byte> mValidBonjourServiceResponse = new ArrayList<Byte>() {{
+        add((byte)'4'); add((byte)'5'); add((byte)'6'); add((byte)'7');
+    }};
+
+
+    private ArgumentCaptor<IHwBinder.DeathRecipient> mDeathRecipientCaptor =
+            ArgumentCaptor.forClass(IHwBinder.DeathRecipient.class);
+    private ArgumentCaptor<IServiceNotification.Stub> mServiceNotificationCaptor =
+            ArgumentCaptor.forClass(IServiceNotification.Stub.class);
+    private InOrder mInOrder;
+
+    private class SupplicantP2pIfaceHalSpy extends SupplicantP2pIfaceHal {
+        SupplicantP2pIfaceHalSpy() {
+            super(null);
+        }
+
+        @Override
+        protected IServiceManager getServiceManagerMockable() throws RemoteException {
+            return mServiceManagerMock;
+        }
+
+        @Override
+        protected ISupplicant getSupplicantMockable() throws RemoteException {
+            return mISupplicantMock;
+        }
+
+        @Override
+        protected ISupplicantP2pIface getP2pIfaceMockable(ISupplicantIface iface) {
+            return mISupplicantP2pIfaceMock;
+        }
+
+        @Override
+        protected ISupplicantP2pNetwork getP2pNetworkMockable(ISupplicantNetwork network) {
+            return mISupplicantP2pNetworkMock;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mStatusSuccess = createSupplicantStatus(SupplicantStatusCode.SUCCESS);
+        mStatusFailure = createSupplicantStatus(SupplicantStatusCode.FAILURE_UNKNOWN);
+        mRemoteException = new RemoteException("Test Remote Exception");
+        mStaIface = createIfaceInfo(IfaceType.STA, "wlan0");
+        mP2pIface = createIfaceInfo(IfaceType.P2P, "p2p0");
+
+        mIfaceInfoList = new ArrayList<ISupplicant.IfaceInfo>();
+        mIfaceInfoList.add(mStaIface);
+        mIfaceInfoList.add(mP2pIface);
+
+        when(mServiceManagerMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong())).thenReturn(true);
+        when(mServiceManagerMock.registerForNotifications(anyString(), anyString(),
+                any(IServiceNotification.Stub.class))).thenReturn(true);
+        when(mISupplicantMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong())).thenReturn(true);
+        when(mISupplicantP2pIfaceMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong())).thenReturn(true);
+        mDut = new SupplicantP2pIfaceHalSpy();
+    }
+
+    /**
+     * Sunny day scenario for SupplicantP2pIfaceHal initialization
+     * Asserts successful initialization
+     */
+    @Test
+    public void testInitialize_success() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+    }
+
+    /**
+     * Tests the initialization flow, with a RemoteException occurring when 'getInterface' is called
+     * Ensures initialization fails.
+     */
+    @Test
+    public void testInitialize_remoteExceptionFailure() throws Exception {
+        executeAndValidateInitializationSequence(true, false, false);
+    }
+
+    /**
+     * Tests the initialization flow, with listInterfaces returning 0 interfaces.
+     * Ensures failure
+     */
+    @Test
+    public void testInitialize_zeroInterfacesFailure() throws Exception {
+        executeAndValidateInitializationSequence(false, true, false);
+    }
+
+    /**
+     * Tests the initialization flow, with a null interface being returned by getInterface.
+     * Ensures initialization fails.
+     */
+    @Test
+    public void testInitialize_nullInterfaceFailure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, true);
+    }
+
+    /**
+     * Sunny day scenario for getName()
+     */
+    @Test
+    public void testGetName_success() throws Exception {
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantIface.getNameCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, mIfaceName);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getName(any(ISupplicantIface.getNameCallback.class));
+
+        // Default value when service is not initialized.
+        assertNull(mDut.getName());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertEquals(mIfaceName, mDut.getName());
+    }
+
+    /**
+     * Verify that getName returns null, if status is not SUCCESS.
+     */
+    @Test
+    public void testGetName_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantIface.getNameCallback cb) throws RemoteException {
+                cb.onValues(mStatusFailure, "none");
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getName(any(ISupplicantIface.getNameCallback.class));
+        assertNull(mDut.getName());
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that getName disconnects and returns null, if HAL throws exception.
+     */
+    @Test
+    public void testGetName_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantIface.getNameCallback cb) throws RemoteException {
+                throw new RemoteException("Test");
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getName(any(ISupplicantIface.getNameCallback.class));
+        assertNull(mDut.getName());
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for find()
+     */
+    @Test
+    public void testFind_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.find(anyInt())).thenReturn(mStatusSuccess);
+        // Default value when service is not yet initialized.
+        assertFalse(mDut.find(1));
+
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.find(1));
+        assertFalse(mDut.find(-1));
+    }
+
+    /**
+     * Verify that find returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testFind_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.find(anyInt())).thenReturn(mStatusFailure);
+        assertFalse(mDut.find(1));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that find disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testFind_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.find(anyInt())).thenThrow(mRemoteException);
+        assertFalse(mDut.find(0));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for stopFind()
+     */
+    @Test
+    public void testStopFind_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.stopFind()).thenReturn(mStatusSuccess);
+        // Default value when service is not yet initialized.
+        assertFalse(mDut.stopFind());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.stopFind());
+    }
+
+    /**
+     * Verify that stopFind returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testStopFind_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.stopFind()).thenReturn(mStatusFailure);
+        assertFalse(mDut.stopFind());
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that stopFind disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testStopFind_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.stopFind()).thenThrow(mRemoteException);
+        assertFalse(mDut.stopFind());
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for flush()
+     */
+    @Test
+    public void testFlush_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.flush()).thenReturn(mStatusSuccess);
+        // Default value when service is not yet initialized.
+        assertFalse(mDut.flush());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.flush());
+    }
+
+    /**
+     * Verify that flush returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testFlush_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.flush()).thenReturn(mStatusFailure);
+        assertFalse(mDut.flush());
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that flush disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testFlush_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.flush()).thenThrow(mRemoteException);
+        assertFalse(mDut.flush());
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for serviceFlush()
+     */
+    @Test
+    public void testServiceFlush_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.flushServices()).thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.serviceFlush());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.serviceFlush());
+    }
+
+    /**
+     * Verify that serviceFlush returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testServiceFlush_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.flushServices()).thenReturn(mStatusFailure);
+        assertFalse(mDut.serviceFlush());
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that serviceFlush disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testServiceFlush_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.flushServices()).thenThrow(mRemoteException);
+        assertFalse(mDut.serviceFlush());
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for setPowerSave()
+     */
+    @Test
+    public void testSetPowerSave_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.setPowerSave(eq(mIfaceName), anyBoolean()))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.setPowerSave(mIfaceName, true));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setPowerSave(mIfaceName, true));
+    }
+
+    /**
+     * Verify that setPowerSave returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testSetPowerSave_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setPowerSave(eq(mIfaceName), anyBoolean()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.setPowerSave(mIfaceName, true));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that setPowerSave disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testSetPowerSave_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setPowerSave(eq(mIfaceName), anyBoolean()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.setPowerSave(mIfaceName, true));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for setGroupIdle()
+     */
+    @Test
+    public void testSetGroupIdle_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.setGroupIdle(eq(mIfaceName), anyInt()))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.setGroupIdle(mIfaceName, 1));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setGroupIdle(mIfaceName, 1));
+        assertFalse(mDut.setGroupIdle(mIfaceName, -1));
+    }
+
+    /**
+     * Verify that setGroupIdle returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testSetGroupIdle_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setGroupIdle(eq(mIfaceName), anyInt()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.setGroupIdle(mIfaceName, 1));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that setGroupIdle disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testSetGroupIdle_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setGroupIdle(eq(mIfaceName), anyInt()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.setGroupIdle(mIfaceName, 1));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for setSsidPostfix()
+     */
+    @Test
+    public void testSetSsidPostfix_success() throws Exception {
+        String ssid = "SSID POSTFIX";
+        when(mISupplicantP2pIfaceMock.setSsidPostfix(eq(NativeUtil.decodeSsid("\"" + ssid + "\""))))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.setSsidPostfix(ssid));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setSsidPostfix(ssid));
+        assertFalse(mDut.setSsidPostfix(null));
+    }
+
+    /**
+     * Verify that setSsidPostfix returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testSetSsidPostfix_failure() throws Exception {
+        String ssid = "SSID POSTFIX";
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setSsidPostfix(eq(NativeUtil.decodeSsid("\"" + ssid + "\""))))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.setSsidPostfix(ssid));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that setSsidPostfix disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testSetSsidPostfix_exception() throws Exception {
+        String ssid = "SSID POSTFIX";
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setSsidPostfix(eq(NativeUtil.decodeSsid("\"" + ssid + "\""))))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.setSsidPostfix(ssid));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for connect()
+     */
+    @Test
+    public void testConnect_success() throws Exception {
+        final String configPin = "12345";
+        final HashSet<Integer> methods = new HashSet<>();
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] peer, int method, String pin, boolean joinExisting,
+                    boolean persistent, int goIntent,
+                    ISupplicantP2pIface.connectCallback cb) throws RemoteException {
+                methods.add(method);
+
+                if (method == ISupplicantP2pIface.WpsProvisionMethod.DISPLAY
+                        && TextUtils.isEmpty(pin)) {
+                    // Return the configPin for DISPLAY method if the pin was not provided.
+                    cb.onValues(mStatusSuccess, configPin);
+                } else {
+                    if (method != ISupplicantP2pIface.WpsProvisionMethod.PBC) {
+                        // PIN is only required for PIN methods.
+                        assertEquals(pin, configPin);
+                    }
+                    // For all the other cases, there is no generated pin.
+                    cb.onValues(mStatusSuccess, "");
+                }
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).connect(
+                eq(mPeerMacAddressBytes), anyInt(), anyString(), anyBoolean(), anyBoolean(),
+                anyInt(), any(ISupplicantP2pIface.connectCallback.class));
+
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, "");
+
+        // Default value when service is not initialized.
+        assertNull(mDut.connect(config, false));
+
+        executeAndValidateInitializationSequence(false, false, false);
+
+        assertEquals(configPin, mDut.connect(config, false));
+        assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.DISPLAY));
+        methods.clear();
+
+        config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, configPin);
+        assertTrue(mDut.connect(config, false).isEmpty());
+        assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.DISPLAY));
+        methods.clear();
+
+        config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+        assertTrue(mDut.connect(config, false).isEmpty());
+        assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.PBC));
+        methods.clear();
+
+        config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.KEYPAD, configPin);
+        assertTrue(mDut.connect(config, false).isEmpty());
+        assertTrue(methods.contains(ISupplicantP2pIface.WpsProvisionMethod.KEYPAD));
+    }
+
+    /**
+     * Test connect with invalid arguments.
+     */
+    @Test
+    public void testConnect_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] peer, int method, String pin, boolean joinExisting,
+                    boolean persistent, int goIntent,
+                    ISupplicantP2pIface.connectCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, pin);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).connect(
+                any(byte[].class), anyInt(), anyString(), anyBoolean(), anyBoolean(),
+                anyInt(), any(ISupplicantP2pIface.connectCallback.class));
+
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, "");
+
+        // unsupported.
+        config.wps.setup = -1;
+        assertNull(mDut.connect(config, false));
+
+        // Invalid peer address.
+        config.wps.setup = WpsInfo.DISPLAY;
+        for (String address : mInvalidMacAddresses) {
+            config.deviceAddress = address;
+            assertNull(mDut.connect(config, false));
+        }
+
+        // null pin not valid.
+        config.wps.setup = WpsInfo.DISPLAY;
+        config.wps.pin = null;
+        assertNull(mDut.connect(config, false));
+
+        // Pin should be empty for PBC.
+        config.wps.setup = WpsInfo.PBC;
+        config.wps.pin = "03455323";
+        assertNull(mDut.connect(config, false));
+    }
+
+    /**
+     * Verify that connect returns null, if status is not SUCCESS.
+     */
+    @Test
+    public void testConnect_failure() throws Exception {
+        final String configPin = "12345";
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, configPin);
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] peer, int method, String pin, boolean joinExisting,
+                    boolean persistent, int goIntent,
+                    ISupplicantP2pIface.connectCallback cb) throws RemoteException {
+                cb.onValues(mStatusFailure, null);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).connect(
+                eq(mPeerMacAddressBytes), anyInt(), anyString(), anyBoolean(), anyBoolean(),
+                anyInt(), any(ISupplicantP2pIface.connectCallback.class));
+
+        assertNull(mDut.connect(config, false));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that connect disconnects and returns null, if HAL throws exception.
+     */
+    @Test
+    public void testConnect_exception() throws Exception {
+        final String configPin = "12345";
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.DISPLAY, configPin);
+
+        doThrow(mRemoteException)
+        .when(mISupplicantP2pIfaceMock).connect(
+                eq(mPeerMacAddressBytes), anyInt(), anyString(), anyBoolean(), anyBoolean(),
+                anyInt(), any(ISupplicantP2pIface.connectCallback.class));
+
+        assertNull(mDut.connect(config, false));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for cancelConnect()
+     */
+    @Test
+    public void testCancelConnect_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.cancelConnect())
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.cancelConnect());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.cancelConnect());
+    }
+
+    /**
+     * Verify that cancelConnect returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testCancelConnect_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelConnect())
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.cancelConnect());
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that cancelConnect disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testCancelConnect_exception() throws Exception {
+        String ssid = "\"SSID POSTFIX\"";
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelConnect())
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.cancelConnect());
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for provisionDiscovery()
+     */
+    @Test
+    public void testProvisionDiscovery_success() throws Exception {
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+
+        when(mISupplicantP2pIfaceMock.provisionDiscovery(
+                eq(mPeerMacAddressBytes), anyInt()))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.provisionDiscovery(config));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.provisionDiscovery(config));
+    }
+
+    /**
+     * Test provisionDiscovery with invalid arguments.
+     */
+    @Test
+    public void testProvisionDiscovery_invalidArguments() throws Exception {
+        when(mISupplicantP2pIfaceMock.provisionDiscovery(
+                eq(mPeerMacAddressBytes), anyInt()))
+                .thenReturn(mStatusSuccess);
+        executeAndValidateInitializationSequence(false, false, false);
+
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+
+        // Unsupported method.
+        config.wps.setup = -1;
+        assertFalse(mDut.provisionDiscovery(config));
+
+        config.wps.setup = WpsInfo.PBC;
+        for (String address : mInvalidMacAddresses) {
+            config.deviceAddress = address;
+            assertFalse(mDut.provisionDiscovery(config));
+        }
+    }
+
+    /**
+     * Verify that provisionDiscovery returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testProvisionDiscovery_failure() throws Exception {
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.provisionDiscovery(
+                eq(mPeerMacAddressBytes), anyInt()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.provisionDiscovery(config));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that provisionDiscovery disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testProvisionDiscovery_exception() throws Exception {
+        WifiP2pConfig config = createDummyP2pConfig(mPeerMacAddress, WpsInfo.PBC, "");
+
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.provisionDiscovery(
+                eq(mPeerMacAddressBytes), anyInt()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.provisionDiscovery(config));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for invite()
+     */
+    @Test
+    public void testInvite_success() throws Exception {
+        WifiP2pGroup group = createDummyP2pGroup();
+
+        when(mISupplicantP2pIfaceMock.invite(
+                eq(mIfaceName), eq(mGroupOwnerMacAddressBytes), eq(mPeerMacAddressBytes)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.invite(group, mPeerMacAddress));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.invite(group, mPeerMacAddress));
+    }
+
+    /**
+     * Invite with invalid arguments.
+     */
+    @Test
+    public void testInvite_invalidArguments() throws Exception {
+        WifiP2pGroup group = createDummyP2pGroup();
+
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.invite(
+                anyString(), any(byte[].class), any(byte[].class)))
+                .thenReturn(mStatusSuccess);
+
+        for (String address : mInvalidMacAddresses) {
+            assertFalse(mDut.invite(group, address));
+        }
+
+        for (String address : mInvalidMacAddresses) {
+            group.getOwner().deviceAddress = address;
+            assertFalse(mDut.invite(group, mPeerMacAddress));
+        }
+
+        group.setOwner(null);
+        assertFalse(mDut.invite(group, mPeerMacAddress));
+        assertFalse(mDut.invite(null, mPeerMacAddress));
+    }
+
+    /**
+     * Verify that invite returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testInvite_failure() throws Exception {
+        WifiP2pGroup group = createDummyP2pGroup();
+
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.invite(
+                anyString(), any(byte[].class), any(byte[].class)))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.invite(group, mPeerMacAddress));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that invite disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testInvite_exception() throws Exception {
+        WifiP2pGroup group = createDummyP2pGroup();
+
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.invite(
+                anyString(), any(byte[].class), any(byte[].class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.invite(group, mPeerMacAddress));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for reject()
+     */
+    @Test
+    public void testReject_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.reject(eq(mPeerMacAddressBytes)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.reject(mPeerMacAddress));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.reject(mPeerMacAddress));
+    }
+
+    /**
+     * Reject with invalid arguments.
+     */
+    @Test
+    public void testReject_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.reject(any(byte[].class)))
+                .thenReturn(mStatusSuccess);
+
+        for (String address : mInvalidMacAddresses) {
+            assertFalse(mDut.reject(address));
+        }
+    }
+
+    /**
+     * Verify that reject returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testReject_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.reject(any(byte[].class)))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.reject(mPeerMacAddress));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that reject disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testReject_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.reject(any(byte[].class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.reject(mPeerMacAddress));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for getDeviceAddress()
+     */
+    @Test
+    public void testGetDeviceAddress_success() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pIface.getDeviceAddressCallback cb) {
+                cb.onValues(mStatusSuccess, mPeerMacAddressBytes);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getDeviceAddress(
+                any(ISupplicantP2pIface.getDeviceAddressCallback.class));
+
+        // Default value when service is not initialized.
+        assertNull(mDut.getDeviceAddress());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertEquals(mPeerMacAddress, mDut.getDeviceAddress());
+    }
+
+    /**
+     * Test getDeviceAddress() when invalid mac address is being reported.
+     */
+    @Test
+    public void testGetDeviceAddress_invalidResult() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        HashSet<byte[]> addresses = new HashSet<byte[]>(Arrays.asList(
+                mInvalidMacAddressBytes1, mInvalidMacAddressBytes2,
+                mInvalidMacAddressBytes3, mInvalidMacAddressBytes4));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pIface.getDeviceAddressCallback cb) {
+                byte[] address = addresses.iterator().next();
+                cb.onValues(mStatusSuccess, address);
+                addresses.remove(address);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getDeviceAddress(
+                any(ISupplicantP2pIface.getDeviceAddressCallback.class));
+
+        // Default value when service is not initialized.
+        while (!addresses.isEmpty()) {
+            assertNull(mDut.getDeviceAddress());
+        }
+    }
+
+    /**
+     * Verify that getDeviceAddress returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testGetDeviceAddress_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pIface.getDeviceAddressCallback cb) {
+                cb.onValues(mStatusFailure, null);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getDeviceAddress(
+                any(ISupplicantP2pIface.getDeviceAddressCallback.class));
+
+        assertNull(mDut.getDeviceAddress());
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that getDeviceAddress disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testGetDeviceAddress_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doThrow(mRemoteException).when(mISupplicantP2pIfaceMock).getDeviceAddress(
+                any(ISupplicantP2pIface.getDeviceAddressCallback.class));
+
+        assertNull(mDut.getDeviceAddress());
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for getSsid()
+     */
+    @Test
+    public void testGetSsid_success() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ISupplicantP2pIface.getSsidCallback cb) {
+                cb.onValues(mStatusSuccess, mSsidBytes);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getSsid(
+                eq(mPeerMacAddressBytes),
+                any(ISupplicantP2pIface.getSsidCallback.class));
+
+        // Default value when service is not initialized.
+        assertNull(mDut.getSsid(mPeerMacAddress));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertEquals(mSsid, mDut.getSsid(mPeerMacAddress));
+    }
+
+    /**
+     * Test getSsid() with invalid argument and response.
+     */
+    @Test
+    public void testGetSsid_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ISupplicantP2pIface.getSsidCallback cb) {
+                cb.onValues(mStatusSuccess, mSsidBytes);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getSsid(
+                any(byte[].class), any(ISupplicantP2pIface.getSsidCallback.class));
+
+        for (String address : mInvalidMacAddresses) {
+            assertNull(mDut.getSsid(address));
+        }
+
+        // Simulate null response from HAL.
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ISupplicantP2pIface.getSsidCallback cb) {
+                cb.onValues(mStatusSuccess, null);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getSsid(
+                any(byte[].class), any(ISupplicantP2pIface.getSsidCallback.class));
+
+        assertNull(mDut.getSsid(mPeerMacAddress));
+    }
+
+    /**
+     * Verify that getSsid returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testGetSsid_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ISupplicantP2pIface.getSsidCallback cb) {
+                cb.onValues(mStatusFailure, null);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).getSsid(
+                any(byte[].class), any(ISupplicantP2pIface.getSsidCallback.class));
+
+        assertNull(mDut.getSsid(mPeerMacAddress));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that getSsid disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testGetSsid_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doThrow(mRemoteException)
+        .when(mISupplicantP2pIfaceMock).getSsid(
+                any(byte[].class), any(ISupplicantP2pIface.getSsidCallback.class));
+
+        assertNull(mDut.getSsid(mPeerMacAddress));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for reinvoke()
+     */
+    @Test
+    public void testReinvoke_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.reinvoke(anyInt(), eq(mPeerMacAddressBytes)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.reinvoke(0, mPeerMacAddress));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.reinvoke(0, mPeerMacAddress));
+    }
+
+    /**
+     * Reinvoke with invalid arguments.
+     */
+    @Test
+    public void testReinvoke_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.reinvoke(anyInt(), any(byte[].class)))
+                .thenReturn(mStatusSuccess);
+
+        for (String address : mInvalidMacAddresses) {
+            assertFalse(mDut.reinvoke(0, address));
+        }
+    }
+
+    /**
+     * Verify that reinvoke returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testReinvoke_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.reinvoke(anyInt(), any(byte[].class)))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.reinvoke(0, mPeerMacAddress));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that reinvoke disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testReinvoke_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.reinvoke(anyInt(), any(byte[].class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.reinvoke(0, mPeerMacAddress));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for groupAdd()
+     */
+    @Test
+    public void testGroupAdd_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.addGroup(eq(true), eq(3)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.groupAdd(3, true));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.groupAdd(3, true));
+    }
+
+    /**
+     * Verify that groupAdd returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testGroupAdd_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.addGroup(anyBoolean(), anyInt()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.groupAdd(0, true));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that groupAdd disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testGroupAdd_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.addGroup(anyBoolean(), anyInt()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.groupAdd(0, true));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for groupRemove()
+     */
+    @Test
+    public void testGroupRemove_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.removeGroup(eq(mIfaceName)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.groupRemove(mIfaceName));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.groupRemove(mIfaceName));
+    }
+
+    /**
+     * Verify that groupRemove returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testGroupRemove_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.removeGroup(anyString()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.groupRemove(mIfaceName));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that groupRemove disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testGroupRemove_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.removeGroup(anyString()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.groupRemove(mIfaceName));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for getGroupCapability()
+     */
+    @Test
+    public void testGetGroupCapability_success() throws Exception {
+        final int caps = 123;
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ISupplicantP2pIface.getGroupCapabilityCallback cb) {
+                cb.onValues(mStatusSuccess, caps);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock)
+                .getGroupCapability(
+                        eq(mPeerMacAddressBytes),
+                        any(ISupplicantP2pIface.getGroupCapabilityCallback.class));
+
+        // Default value when service is not initialized.
+        assertEquals(-1, mDut.getGroupCapability(mPeerMacAddress));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertEquals(caps, mDut.getGroupCapability(mPeerMacAddress));
+    }
+
+    /**
+     * GetGroupCapability with invalid arguments.
+     */
+    @Test
+    public void testGetGroupCapability_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ISupplicantP2pIface.getGroupCapabilityCallback cb) {
+                cb.onValues(mStatusSuccess, 0);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock)
+                .getGroupCapability(
+                        eq(mPeerMacAddressBytes),
+                        any(ISupplicantP2pIface.getGroupCapabilityCallback.class));
+
+        for (String address : mInvalidMacAddresses) {
+            assertEquals(-1, mDut.getGroupCapability(address));
+        }
+    }
+
+    /**
+     * Verify that getGroupCapability returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testGetGroupCapability_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ISupplicantP2pIface.getGroupCapabilityCallback cb) {
+                cb.onValues(mStatusFailure, 0);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock)
+                .getGroupCapability(
+                        eq(mPeerMacAddressBytes),
+                        any(ISupplicantP2pIface.getGroupCapabilityCallback.class));
+
+        assertEquals(-1, mDut.getGroupCapability(mPeerMacAddress));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that getGroupCapability disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testGetGroupCapability_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doThrow(mRemoteException)
+                .when(mISupplicantP2pIfaceMock)
+                .getGroupCapability(
+                        eq(mPeerMacAddressBytes),
+                        any(ISupplicantP2pIface.getGroupCapabilityCallback.class));
+        assertEquals(-1, mDut.getGroupCapability(mPeerMacAddress));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for configureExtListen()
+     */
+    @Test
+    public void testConfigureExtListen_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.configureExtListen(eq(123), eq(456)))
+                .thenReturn(mStatusSuccess);
+        when(mISupplicantP2pIfaceMock.configureExtListen(eq(0), eq(0)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.configureExtListen(true, 123, 456));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.configureExtListen(true, 123, 456));
+        // Turning listening off should reset intervals to 0s.
+        assertTrue(mDut.configureExtListen(false, 999, 999));
+        // Disable listening.
+        assertTrue(mDut.configureExtListen(false, -1, -1));
+    }
+
+    /**
+     * Test configureExtListen with invalid parameters.
+     */
+    @Test
+    public void testConfigureExtListen_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.configureExtListen(anyInt(), anyInt()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.configureExtListen(true, -1, 1));
+        assertFalse(mDut.configureExtListen(true, 1, -1));
+    }
+
+    /**
+     * Verify that configureExtListen returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testConfigureExtListen_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.configureExtListen(anyInt(), anyInt()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.configureExtListen(true, 1, 1));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that configureExtListen disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testConfigureExtListen_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.configureExtListen(anyInt(), anyInt()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.configureExtListen(true, 1, 1));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for setListenChannel()
+     */
+    @Test
+    public void testSetListenChannel_success() throws Exception {
+        int lc = 4;
+        int oc = 163;
+        ISupplicantP2pIface.FreqRange range1 = new ISupplicantP2pIface.FreqRange();
+        range1.min = 1000;
+        range1.max = 5810;
+        ISupplicantP2pIface.FreqRange range2 = new ISupplicantP2pIface.FreqRange();
+        range2.min = 5820;
+        range2.max = 6000;
+        ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
+        ranges.add(range1);
+        ranges.add(range2);
+
+        when(mISupplicantP2pIfaceMock.setListenChannel(eq(lc),  anyInt()))
+                .thenReturn(mStatusSuccess);
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(eq(ranges)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.setListenChannel(lc, oc));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setListenChannel(lc, oc));
+    }
+
+    /**
+     * Sunny day scenario for setListenChannel()
+     */
+    @Test
+    public void testSetListenChannel_successResetDisallowedFreq() throws Exception {
+        int lc = 2;
+        int oc = 0;
+        ArrayList<ISupplicantP2pIface.FreqRange> ranges = new ArrayList<>();
+
+        when(mISupplicantP2pIfaceMock.setListenChannel(eq(lc),  anyInt()))
+                .thenReturn(mStatusSuccess);
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(eq(ranges)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.setListenChannel(lc, oc));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setListenChannel(lc, oc));
+    }
+
+    /**
+     * Test setListenChannel with invalid parameters.
+     */
+    @Test
+    public void testSetListenChannel_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setListenChannel(anyInt(), anyInt()))
+                .thenReturn(mStatusSuccess);
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+        assertFalse(mDut.setListenChannel(-1, 1));
+        assertFalse(mDut.setListenChannel(1, -1));
+    }
+
+    /**
+     * Verify that setListenChannel returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testSetListenChannel_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setListenChannel(anyInt(), anyInt()))
+                .thenReturn(mStatusFailure);
+        when(mISupplicantP2pIfaceMock.setDisallowedFrequencies(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+        assertFalse(mDut.setListenChannel(1, 1));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that setListenChannel disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testSetListenChannel_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setListenChannel(anyInt(), anyInt()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.setListenChannel(1, 1));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for serviceAdd()
+     */
+    @Test
+    public void testServiceAdd_success() throws Exception {
+        WifiP2pServiceInfo info = createDummyP2pServiceInfo(
+                mValidUpnpService, mValidBonjourService);
+        final HashSet<String> services = new HashSet<String>();
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int version, String name) {
+                services.add("upnp");
+                assertEquals(mValidUpnpServiceVersion, version);
+                assertEquals(mValidUpnpServiceName, name);
+                return mStatusSuccess;
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).addUpnpService(anyInt(), anyString());
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> request, ArrayList<Byte> response) {
+                services.add("bonjour");
+                assertEquals(mValidBonjourServiceRequest, request);
+                assertEquals(mValidBonjourServiceResponse, response);
+                return mStatusSuccess;
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).addBonjourService(
+                any(ArrayList.class), any(ArrayList.class));
+
+        // Default value when service is not initialized.
+        assertFalse(mDut.serviceAdd(info));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.serviceAdd(info));
+        // Confirm that both services have been added.
+        assertTrue(services.contains("upnp"));
+        assertTrue(services.contains("bonjour"));
+
+        // Empty services should cause no trouble.
+        assertTrue(mDut.serviceAdd(createDummyP2pServiceInfo()));
+    }
+
+    /**
+     * Test serviceAdd with invalid parameters.
+     */
+    @Test
+    public void testServiceAdd_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        when(mISupplicantP2pIfaceMock.addUpnpService(anyInt(), anyString()))
+                .thenReturn(mStatusSuccess);
+        when(mISupplicantP2pIfaceMock.addBonjourService(
+                any(ArrayList.class), any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+
+        assertFalse(mDut.serviceAdd(null));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidService1)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidService2)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidUpnpService1)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidUpnpService2)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidUpnpService3)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService1)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService2)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService3)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mInvalidBonjourService4)));
+    }
+
+    /**
+     * Verify that serviceAdd returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testServiceAdd_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        when(mISupplicantP2pIfaceMock.addUpnpService(anyInt(), anyString()))
+                .thenReturn(mStatusFailure);
+        when(mISupplicantP2pIfaceMock.addBonjourService(
+                any(ArrayList.class), any(ArrayList.class)))
+                .thenReturn(mStatusFailure);
+
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidUpnpService)));
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidBonjourService)));
+
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that serviceAdd disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testServiceAdd_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        when(mISupplicantP2pIfaceMock.addUpnpService(anyInt(), anyString()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidUpnpService)));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.addBonjourService(
+                any(ArrayList.class), any(ArrayList.class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.serviceAdd(createDummyP2pServiceInfo(mValidBonjourService)));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for serviceRemove()
+     */
+    @Test
+    public void testServiceRemove_success() throws Exception {
+        WifiP2pServiceInfo info = createDummyP2pServiceInfo(
+                mValidUpnpService, mValidBonjourService);
+        final HashSet<String> services = new HashSet<String>();
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(int version, String name) {
+                services.add("upnp");
+                assertEquals(mValidUpnpServiceVersion, version);
+                assertEquals(mValidUpnpServiceName, name);
+                return mStatusSuccess;
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).removeUpnpService(anyInt(), anyString());
+
+        doAnswer(new AnswerWithArguments() {
+            public SupplicantStatus answer(ArrayList<Byte> request) {
+                services.add("bonjour");
+                assertEquals(mValidBonjourServiceRequest, request);
+                return mStatusSuccess;
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).removeBonjourService(any(ArrayList.class));
+
+        // Default value when service is not initialized.
+        assertFalse(mDut.serviceRemove(info));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.serviceRemove(info));
+        // Confirm that both services have been removed.
+        assertTrue(services.contains("upnp"));
+        assertTrue(services.contains("bonjour"));
+
+        // Empty services should cause no trouble.
+        assertTrue(mDut.serviceRemove(createDummyP2pServiceInfo()));
+    }
+
+    /**
+     * Test serviceRemove with invalid parameters.
+     */
+    @Test
+    public void testServiceRemove_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        when(mISupplicantP2pIfaceMock.removeUpnpService(anyInt(), anyString()))
+                .thenReturn(mStatusSuccess);
+        when(mISupplicantP2pIfaceMock.removeBonjourService(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+
+        assertFalse(mDut.serviceRemove(null));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidService1)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidService2)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidUpnpService1)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidUpnpService2)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidUpnpService3)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService1)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService2)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService3)));
+        // Response parameter is ignored by serviceRemove call, hence the following would pass.
+        // The production code would need to parse otherwise redundant parameter to fail on this
+        // one.
+        //
+        // assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mInvalidBonjourService4)));
+    }
+
+    /**
+     * Verify that serviceRemove returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testServiceRemove_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        when(mISupplicantP2pIfaceMock.removeUpnpService(anyInt(), anyString()))
+                .thenReturn(mStatusFailure);
+        when(mISupplicantP2pIfaceMock.removeBonjourService(any(ArrayList.class)))
+                .thenReturn(mStatusFailure);
+
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidUpnpService)));
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidBonjourService)));
+
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that serviceRemove disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testServiceRemove_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        when(mISupplicantP2pIfaceMock.removeUpnpService(anyInt(), anyString()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidUpnpService)));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.removeBonjourService(any(ArrayList.class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.serviceRemove(createDummyP2pServiceInfo(mValidBonjourService)));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for requestServiceDiscovery()
+     */
+    @Test
+    public void testRequestServiceDiscovery_success() throws Exception {
+        final int caps = 123;
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ArrayList<Byte> query,
+                    ISupplicantP2pIface.requestServiceDiscoveryCallback cb) {
+                cb.onValues(mStatusSuccess, 1234);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock)
+                .requestServiceDiscovery(
+                        eq(mPeerMacAddressBytes),
+                        eq(mValidBonjourServiceRequest),
+                        any(ISupplicantP2pIface.requestServiceDiscoveryCallback.class));
+
+        // Default value when service is not initialized.
+        assertNull(mDut.requestServiceDiscovery(mPeerMacAddress, mValidServiceRequestString));
+
+        executeAndValidateInitializationSequence(false, false, false);
+        assertEquals("1234", mDut.requestServiceDiscovery(
+                mPeerMacAddress, mValidServiceRequestString));
+    }
+
+    /**
+     * RequestServiceDiscovery with invalid arguments.
+     */
+    @Test
+    public void testRequestServiceDiscovery_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(byte[] address, ArrayList<Byte> query,
+                    ISupplicantP2pIface.requestServiceDiscoveryCallback cb) {
+                cb.onValues(mStatusSuccess, 0);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock)
+                .requestServiceDiscovery(
+                        any(byte[].class), any(ArrayList.class),
+                        any(ISupplicantP2pIface.requestServiceDiscoveryCallback.class));
+
+        for (String address : mInvalidMacAddresses) {
+            assertNull(mDut.requestServiceDiscovery(
+                    address, mValidServiceRequestString));
+        }
+        assertNull(mDut.requestServiceDiscovery(mPeerMacAddress, null));
+        assertNull(mDut.requestServiceDiscovery(mPeerMacAddress, mInvalidServiceRequestString));
+    }
+
+    /**
+     * Verify that requestServiceDiscovery returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testRequestServiceDiscovery_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(
+                    byte[] address, ArrayList<Byte> query,
+                    ISupplicantP2pIface.requestServiceDiscoveryCallback cb) {
+                cb.onValues(mStatusFailure, 0);
+            }
+        })
+        .when(mISupplicantP2pIfaceMock)
+                .requestServiceDiscovery(
+                        any(byte[].class), any(ArrayList.class),
+                        any(ISupplicantP2pIface.requestServiceDiscoveryCallback.class));
+
+        assertNull(mDut.requestServiceDiscovery(mPeerMacAddress, mValidServiceRequestString));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that requestServiceDiscovery disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testRequestServiceDiscovery_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doThrow(mRemoteException)
+                .when(mISupplicantP2pIfaceMock)
+                .requestServiceDiscovery(
+                        any(byte[].class), any(ArrayList.class),
+                        any(ISupplicantP2pIface.requestServiceDiscoveryCallback.class));
+        assertNull(mDut.requestServiceDiscovery(mPeerMacAddress, mValidServiceRequestString));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+    // Test constant used in cancelServiceDiscovery tests
+    static final String SERVICE_IDENTIFIER_STR = "521918410304";
+    static final long SERVICE_IDENTIFIER_LONG = 521918410304L;
+
+    /**
+     * Sunny day scenario for cancelServiceDiscovery()
+     */
+    @Test
+    public void testCancelServiceDiscovery_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.cancelServiceDiscovery(SERVICE_IDENTIFIER_LONG))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.cancelServiceDiscovery(SERVICE_IDENTIFIER_STR));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.cancelServiceDiscovery(SERVICE_IDENTIFIER_STR));
+    }
+
+    /**
+     * Test cancelServiceDiscovery with invalid parameters.
+     */
+    @Test
+    public void testCancelServiceDiscovery_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelServiceDiscovery(anyLong()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.cancelServiceDiscovery(null));
+        assertFalse(mDut.cancelServiceDiscovery("not a number"));
+    }
+
+    /**
+     * Verify that cancelServiceDiscovery returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testCancelServiceDiscovery_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelServiceDiscovery(anyLong()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.cancelServiceDiscovery(SERVICE_IDENTIFIER_STR));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that cancelServiceDiscovery disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testCancelServiceDiscovery_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelServiceDiscovery(anyLong()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.cancelServiceDiscovery(SERVICE_IDENTIFIER_STR));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for setMiracastMode()
+     */
+    @Test
+    public void testSetMiracastMode_success() throws Exception {
+        HashSet<Byte> modes = new HashSet<Byte>();
+
+        when(mISupplicantP2pIfaceMock.setMiracastMode(anyByte()))
+                .thenAnswer(new AnswerWithArguments() {
+                    public SupplicantStatus answer(byte mode) {
+                        modes.add(mode);
+                        return mStatusSuccess;
+                    }
+                });
+        // Default value when service is not initialized.
+        assertFalse(mDut.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE));
+        assertTrue(modes.contains(ISupplicantP2pIface.MiracastMode.SOURCE));
+
+        assertTrue(mDut.setMiracastMode(WifiP2pManager.MIRACAST_SINK));
+        assertTrue(modes.contains(ISupplicantP2pIface.MiracastMode.SINK));
+
+        // Any invalid number yields disabled miracast mode.
+        assertTrue(mDut.setMiracastMode(-1));
+        assertTrue(modes.contains(ISupplicantP2pIface.MiracastMode.DISABLED));
+    }
+
+    /**
+     * Verify that setMiracastMode returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testSetMiracastMode_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setMiracastMode(anyByte()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that setMiracastMode disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testSetMiracastMode_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setMiracastMode(anyByte()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for startWpsPbc()
+     */
+    @Test
+    public void testStartWpsPbc_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.startWpsPbc(eq(mIfaceName), eq(mPeerMacAddressBytes)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.startWpsPbc(mIfaceName, mPeerMacAddress));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.startWpsPbc(mIfaceName, mPeerMacAddress));
+    }
+
+    /**
+     * StartWpsPbc with invalid arguments.
+     */
+    @Test
+    public void testStartWpsPbc_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.startWpsPbc(anyString(), any(byte[].class)))
+                .thenReturn(mStatusSuccess);
+
+        for (String address : mInvalidMacAddresses) {
+            assertFalse(mDut.startWpsPbc(mIfaceName, address));
+        }
+
+        assertFalse(mDut.startWpsPbc(null, mPeerMacAddress));
+    }
+
+    /**
+     * Verify that startWpsPbc returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testStartWpsPbc_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.startWpsPbc(anyString(), any(byte[].class)))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.startWpsPbc(mIfaceName, mPeerMacAddress));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that startWpsPbc disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testStartWpsPbc_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.startWpsPbc(anyString(), any(byte[].class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.startWpsPbc(mIfaceName, mPeerMacAddress));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for startWpsPinKeypad()
+     */
+    @Test
+    public void testStartWpsPinKeypad_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.startWpsPinKeypad(eq(mIfaceName), eq("1234")))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.startWpsPinKeypad(mIfaceName, "1234"));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.startWpsPinKeypad(mIfaceName, "1234"));
+    }
+
+    /**
+     * StartWpsPinKeypad with invalid arguments.
+     */
+    @Test
+    public void testStartWpsPinKeypad_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.startWpsPinKeypad(anyString(), anyString()))
+                .thenReturn(mStatusSuccess);
+
+        assertFalse(mDut.startWpsPinKeypad(null, "1234"));
+        assertFalse(mDut.startWpsPinKeypad(mIfaceName, null));
+        // StartWpsPinPinKeypad does not validate, that PIN indeed holds an integer encoded in a
+        // string. This code would be redundant, as HAL requires string to be passed.
+    }
+
+    /**
+     * Verify that startWpsPinKeypad returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testStartWpsPinKeypad_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.startWpsPinKeypad(anyString(), anyString()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.startWpsPinKeypad(mIfaceName, "1234"));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that startWpsPinKeypad disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testStartWpsPinKeypad_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.startWpsPinKeypad(anyString(), anyString()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.startWpsPinKeypad(mIfaceName, "1234"));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for startWpsPinDisplay()
+     */
+    @Test
+    public void testStartWpsPinDisplay_success() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(String ifName, byte[] bssid,
+                    ISupplicantP2pIface.startWpsPinDisplayCallback cb) {
+                cb.onValues(mStatusSuccess, "1234");
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).startWpsPinDisplay(
+                eq(mIfaceName), eq(mPeerMacAddressBytes),
+                any(ISupplicantP2pIface.startWpsPinDisplayCallback.class));
+
+        // Default value when service is not initialized.
+        assertNull(mDut.startWpsPinDisplay(mIfaceName, mPeerMacAddress));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertEquals("1234", mDut.startWpsPinDisplay(mIfaceName, mPeerMacAddress));
+    }
+
+    /**
+     * StartWpsPinDisplay with invalid arguments.
+     */
+    @Test
+    public void testStartWpsPinDisplay_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(String ifName, byte[] bssid,
+                    ISupplicantP2pIface.startWpsPinDisplayCallback cb) {
+                cb.onValues(mStatusSuccess, "1234");
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).startWpsPinDisplay(
+                anyString(), any(byte[].class),
+                any(ISupplicantP2pIface.startWpsPinDisplayCallback.class));
+
+        for (String address : mInvalidMacAddresses) {
+            assertNull(mDut.startWpsPinDisplay(mIfaceName, address));
+        }
+
+        assertNull(mDut.startWpsPinDisplay(null, mPeerMacAddress));
+    }
+
+    /**
+     * Verify that startWpsPinDisplay returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testStartWpsPinDisplay_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(String ifName, byte[] bssid,
+                    ISupplicantP2pIface.startWpsPinDisplayCallback cb) {
+                cb.onValues(mStatusFailure, "1234");
+            }
+        })
+        .when(mISupplicantP2pIfaceMock).startWpsPinDisplay(
+                anyString(), any(byte[].class),
+                any(ISupplicantP2pIface.startWpsPinDisplayCallback.class));
+
+        assertNull(mDut.startWpsPinDisplay(mIfaceName, mPeerMacAddress));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that startWpsPinDisplay disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testStartWpsPinDisplay_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        doThrow(mRemoteException)
+                .when(mISupplicantP2pIfaceMock).startWpsPinDisplay(
+                        anyString(), any(byte[].class),
+                        any(ISupplicantP2pIface.startWpsPinDisplayCallback.class));
+        assertNull(mDut.startWpsPinDisplay(mIfaceName, mPeerMacAddress));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for cancelWps()
+     */
+    @Test
+    public void testCancelWps_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.cancelWps(eq(mIfaceName)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.cancelWps(mIfaceName));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.cancelWps(mIfaceName));
+    }
+
+    /**
+     * CancelWps with invalid arguments.
+     */
+    @Test
+    public void testCancelWps_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelWps(anyString()))
+                .thenReturn(mStatusSuccess);
+
+        assertFalse(mDut.cancelWps(null));
+    }
+
+    /**
+     * Verify that cancelWps returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testCancelWps_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelWps(anyString()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.cancelWps(mIfaceName));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that cancelWps disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testCancelWps_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.cancelWps(anyString()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.cancelWps(mIfaceName));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for enableWfd()
+     */
+    @Test
+    public void testEnableWfd_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.enableWfd(eq(true)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.enableWfd(true));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.enableWfd(true));
+    }
+
+    /**
+     * Verify that enableWfd returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testEnableWfd_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.enableWfd(anyBoolean()))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.enableWfd(true));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that enableWfd disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testEnableWfd_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.enableWfd(anyBoolean()))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.enableWfd(false));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+
+    /**
+     * Sunny day scenario for setWfdDeviceInfo()
+     */
+    @Test
+    public void testSetWfdDeviceInfo_success() throws Exception {
+        when(mISupplicantP2pIfaceMock.setWfdDeviceInfo(eq(mValidServiceRequestBytes)))
+                .thenReturn(mStatusSuccess);
+        // Default value when service is not initialized.
+        assertFalse(mDut.setWfdDeviceInfo(mValidServiceRequestString));
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.setWfdDeviceInfo(mValidServiceRequestString));
+    }
+
+    /**
+     * SetWfdDeviceInfo with invalid arguments.
+     */
+    @Test
+    public void testSetWfdDeviceInfo_invalidArguments() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setWfdDeviceInfo(any(byte[].class)))
+                .thenReturn(mStatusSuccess);
+
+        assertFalse(mDut.setWfdDeviceInfo(null));
+        assertFalse(mDut.setWfdDeviceInfo(mInvalidServiceRequestString));
+    }
+
+    /**
+     * Verify that setWfdDeviceInfo returns false, if status is not SUCCESS.
+     */
+    @Test
+    public void testSetWfdDeviceInfo_failure() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setWfdDeviceInfo(any(byte[].class)))
+                .thenReturn(mStatusFailure);
+        assertFalse(mDut.setWfdDeviceInfo(mValidServiceRequestString));
+        // Check that service is still alive.
+        assertTrue(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify that setWfdDeviceInfo disconnects and returns false, if HAL throws exception.
+     */
+    @Test
+    public void testSetWfdDeviceInfo_exception() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+        when(mISupplicantP2pIfaceMock.setWfdDeviceInfo(any(byte[].class)))
+                .thenThrow(mRemoteException);
+        assertFalse(mDut.setWfdDeviceInfo(mValidServiceRequestString));
+        // Check service is dead.
+        assertFalse(mDut.isInitializationComplete());
+    }
+
+    /**
+     * Verify the loading of group info.
+     * Specifically, all groups returned by listNetworks are added as a persistent group, so long as
+     * they are NOT current.
+     */
+    @Test
+    public void testLoadGroups() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false);
+
+        // Class to hold the P2p group info returned from the HIDL interface.
+        class P2pGroupInfo {
+            public String ssid;
+            public byte[] bssid;
+            public boolean isGo;
+            public boolean isCurrent;
+            P2pGroupInfo(String ssid, byte[] bssid, boolean isGo, boolean isCurrent) {
+                this.ssid = ssid;
+                this.bssid = bssid;
+                this.isGo = isGo;
+                this.isCurrent = isCurrent;
+            }
+        }
+
+        Map<Integer, P2pGroupInfo> groups = new HashMap<>();
+        groups.put(0, new P2pGroupInfo(
+                "test_34",
+                NativeUtil.macAddressToByteArray("56:34:ab:12:12:34"),
+                false, false));
+        groups.put(1, new P2pGroupInfo(
+                "test_1234",
+                NativeUtil.macAddressToByteArray("16:ed:ab:12:45:34"),
+                true, false));
+        groups.put(2, new P2pGroupInfo(
+                "test_4545",
+                NativeUtil.macAddressToByteArray("32:89:23:56:45:34"),
+                true, false));
+        groups.put(3, new P2pGroupInfo(
+                "iShouldntBeHere",
+                NativeUtil.macAddressToByteArray("aa:bb:cc:56:45:34"),
+                true, true));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pIface.listNetworksCallback cb) {
+                cb.onValues(mStatusSuccess, new ArrayList<Integer>(groups.keySet()));
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .listNetworks(any(ISupplicantP2pIface.listNetworksCallback.class));
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                try {
+                    doAnswer(new AnswerWithArguments() {
+                        public void answer(ISupplicantP2pNetwork.getSsidCallback cb) {
+                            cb.onValues(mStatusSuccess,
+                                    NativeUtil.stringToByteArrayList(groups.get(networkId).ssid));
+                            return;
+                        }
+                    }).when(mISupplicantP2pNetworkMock)
+                            .getSsid(any(ISupplicantP2pNetwork.getSsidCallback.class));
+                    doAnswer(new AnswerWithArguments() {
+                        public void answer(ISupplicantP2pNetwork.getBssidCallback cb) {
+                            cb.onValues(mStatusSuccess, groups.get(networkId).bssid);
+                            return;
+                        }
+                    }).when(mISupplicantP2pNetworkMock)
+                            .getBssid(any(ISupplicantP2pNetwork.getBssidCallback.class));
+                    doAnswer(new AnswerWithArguments() {
+                        public void answer(ISupplicantP2pNetwork.isCurrentCallback cb) {
+                            cb.onValues(mStatusSuccess, groups.get(networkId).isCurrent);
+                            return;
+                        }
+                    }).when(mISupplicantP2pNetworkMock)
+                            .isCurrent(any(ISupplicantP2pNetwork.isCurrentCallback.class));
+                    doAnswer(new AnswerWithArguments() {
+                        public void answer(ISupplicantP2pNetwork.isGoCallback cb) {
+                            cb.onValues(mStatusSuccess, groups.get(networkId).isGo);
+                            return;
+                        }
+                    }).when(mISupplicantP2pNetworkMock)
+                            .isGo(any(ISupplicantP2pNetwork.isGoCallback.class));
+                } catch (RemoteException e) {
+                }
+                cb.onValues(mStatusSuccess, mISupplicantP2pNetworkMock);
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+
+        WifiP2pGroupList p2pGroups = new WifiP2pGroupList();
+        assertTrue(mDut.loadGroups(p2pGroups));
+
+        assertEquals(3, p2pGroups.getGroupList().size());
+        for (WifiP2pGroup group : p2pGroups.getGroupList()) {
+            int networkId = group.getNetworkId();
+            assertEquals(groups.get(networkId).ssid, group.getNetworkName());
+            assertEquals(
+                    NativeUtil.macAddressFromByteArray(groups.get(networkId).bssid),
+                    group.getOwner().deviceAddress);
+            assertEquals(groups.get(networkId).isGo, group.isGroupOwner());
+        }
+    }
+
+    /**
+     * Sunny day scenario for setClientList()
+     */
+    @Test
+    public void testSetClientList() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                if (networkId == testNetworkId) {
+                    cb.onValues(mStatusSuccess, mISupplicantP2pNetworkMock);
+                } else {
+                    cb.onValues(mStatusFailure, null);
+                }
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        when(mISupplicantP2pNetworkMock.setClientList(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+
+        String clientList = client1 + " " + client2;
+        assertTrue(mDut.setClientList(testNetworkId, clientList));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        ArgumentCaptor<ArrayList> capturedClients = ArgumentCaptor.forClass(ArrayList.class);
+        verify(mISupplicantP2pNetworkMock).setClientList(capturedClients.capture());
+
+        // Convert these to long to help with comparisons.
+        ArrayList<byte[]> clients = capturedClients.getValue();
+        ArrayList<Long> expectedClients = new ArrayList<Long>() {{
+                add(NativeUtil.macAddressToLong(mGroupOwnerMacAddressBytes));
+                add(NativeUtil.macAddressToLong(mPeerMacAddressBytes));
+            }};
+        ArrayList<Long> receivedClients = new ArrayList<Long>();
+        for (byte[] client : clients) {
+            receivedClients.add(NativeUtil.macAddressToLong(client));
+        }
+        assertEquals(expectedClients, receivedClients);
+    }
+
+    /**
+     * Failure scenario for setClientList() when getNetwork returns null.
+     */
+    @Test
+    public void testSetClientListFailureDueToGetNetwork() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                cb.onValues(mStatusFailure, null);
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        when(mISupplicantP2pNetworkMock.setClientList(any(ArrayList.class)))
+                .thenReturn(mStatusSuccess);
+
+        String clientList = client1 + " " + client2;
+        assertFalse(mDut.setClientList(testNetworkId, clientList));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        verify(mISupplicantP2pNetworkMock, never()).setClientList(any(ArrayList.class));
+    }
+
+    /**
+     * Sunny day scenario for getClientList()
+     */
+    @Test
+    public void testGetClientList() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                if (networkId == testNetworkId) {
+                    cb.onValues(mStatusSuccess, mISupplicantP2pNetworkMock);
+                } else {
+                    cb.onValues(mStatusFailure, null);
+                }
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pNetwork.getClientListCallback cb) {
+                ArrayList<byte[]> clients = new ArrayList<byte[]>() {{
+                        add(mGroupOwnerMacAddressBytes);
+                        add(mPeerMacAddressBytes);
+                    }};
+                cb.onValues(mStatusSuccess, clients);
+                return;
+            }
+        }).when(mISupplicantP2pNetworkMock)
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+
+        String clientList = client1 + " " + client2;
+        assertEquals(clientList, mDut.getClientList(testNetworkId));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        verify(mISupplicantP2pNetworkMock)
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+    }
+
+    /**
+     * Failure scenario for getClientList() when getNetwork returns null.
+     */
+    @Test
+    public void testGetClientListFailureDueToGetNetwork() throws Exception {
+        int testNetworkId = 5;
+        final String client1 = mGroupOwnerMacAddress;
+        final String client2 = mPeerMacAddress;
+
+        executeAndValidateInitializationSequence(false, false, false);
+        doAnswer(new AnswerWithArguments() {
+            public void answer(final int networkId, ISupplicantP2pIface.getNetworkCallback cb) {
+                cb.onValues(mStatusFailure, null);
+                return;
+            }
+        }).when(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicantP2pNetwork.getClientListCallback cb) {
+                ArrayList<byte[]> clients = new ArrayList<byte[]>() {{
+                        add(mGroupOwnerMacAddressBytes);
+                        add(mPeerMacAddressBytes);
+                    }};
+                cb.onValues(mStatusSuccess, clients);
+                return;
+            }
+        }).when(mISupplicantP2pNetworkMock)
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+
+        assertEquals(null, mDut.getClientList(testNetworkId));
+        verify(mISupplicantP2pIfaceMock)
+                .getNetwork(anyInt(), any(ISupplicantP2pIface.getNetworkCallback.class));
+        verify(mISupplicantP2pNetworkMock, never())
+                .getClientList(any(ISupplicantP2pNetwork.getClientListCallback.class));
+    }
+
+    /**
+     * Sunny day scenario for saveConfig()
+     */
+    @Test
+    public void testSaveConfig() throws Exception {
+        when(mISupplicantP2pIfaceMock.saveConfig()).thenReturn(mStatusSuccess);
+
+        // Should fail before initialization.
+        assertFalse(mDut.saveConfig());
+        executeAndValidateInitializationSequence(false, false, false);
+        assertTrue(mDut.saveConfig());
+        verify(mISupplicantP2pIfaceMock).saveConfig();
+    }
+
+    /**
+     * Calls.initialize(), mocking various call back answers and verifying flow, asserting for the
+     * expected result. Verifies if ISupplicantP2pIface manager is initialized or reset.
+     * Each of the arguments will cause a different failure mode when set true.
+     */
+    private void executeAndValidateInitializationSequence(boolean causeRemoteException,
+            boolean getZeroInterfaces, boolean getNullInterface) throws Exception {
+        boolean shouldSucceed = !causeRemoteException && !getZeroInterfaces && !getNullInterface;
+        // Setup callback mock answers
+        ArrayList<ISupplicant.IfaceInfo> interfaces;
+        if (getZeroInterfaces) {
+            interfaces = new ArrayList<ISupplicant.IfaceInfo>();
+        } else {
+            interfaces = mIfaceInfoList;
+        }
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ISupplicant.listInterfacesCallback cb) throws RemoteException {
+                cb.onValues(mStatusSuccess, interfaces);
+            }
+        })
+        .when(mISupplicantMock).listInterfaces(any(ISupplicant.listInterfacesCallback.class));
+
+        if (causeRemoteException) {
+            doThrow(new RemoteException("Some error!!!"))
+                    .when(mISupplicantMock).getInterface(any(ISupplicant.IfaceInfo.class),
+                    any(ISupplicant.getInterfaceCallback.class));
+        } else {
+            doAnswer(new GetGetInterfaceAnswer(getNullInterface))
+                    .when(mISupplicantMock).getInterface(any(ISupplicant.IfaceInfo.class),
+                    any(ISupplicant.getInterfaceCallback.class));
+        }
+
+        mInOrder = inOrder(mServiceManagerMock, mISupplicantMock);
+        // Initialize SupplicantP2pIfaceHal, should call serviceManager.registerForNotifications
+        assertTrue(mDut.initialize());
+        // verify: service manager initialization sequence
+        mInOrder.verify(mServiceManagerMock).linkToDeath(any(IHwBinder.DeathRecipient.class),
+                anyLong());
+        mInOrder.verify(mServiceManagerMock).registerForNotifications(
+                eq(ISupplicant.kInterfaceName), eq(""), mServiceNotificationCaptor.capture());
+        // act: cause the onRegistration(...) callback to execute
+        mServiceNotificationCaptor.getValue().onRegistration(ISupplicant.kInterfaceName, "", true);
+
+        assertEquals(shouldSucceed, mDut.isInitializationComplete());
+        // verify: listInterfaces is called
+        mInOrder.verify(mISupplicantMock).listInterfaces(
+                any(ISupplicant.listInterfacesCallback.class));
+        if (!getZeroInterfaces) {
+            mInOrder.verify(mISupplicantMock)
+                    .getInterface(any(ISupplicant.IfaceInfo.class),
+                    any(ISupplicant.getInterfaceCallback.class));
+        }
+    }
+
+
+    private SupplicantStatus createSupplicantStatus(int code) {
+        SupplicantStatus status = new SupplicantStatus();
+        status.code = code;
+        return status;
+    }
+
+    /**
+     * Create an IfaceInfo with given type and name
+     */
+    private ISupplicant.IfaceInfo createIfaceInfo(int type, String name) {
+        ISupplicant.IfaceInfo info = new ISupplicant.IfaceInfo();
+        info.type = type;
+        info.name = name;
+        return info;
+    }
+
+    /**
+     * Create new dummy WifiP2pConfig instance.
+     */
+    private WifiP2pConfig createDummyP2pConfig(String peerAddress, int wpsProvMethod, String pin) {
+        WifiP2pConfig config = new WifiP2pConfig();
+        config.wps = new WpsInfo();
+        config.deviceAddress = peerAddress;
+
+        config.wps.setup = wpsProvMethod;
+        config.wps.pin = pin;
+
+        return config;
+    }
+
+    /**
+     * Create new dummy WifiP2pGroup instance.
+     */
+    private WifiP2pGroup createDummyP2pGroup() {
+        WifiP2pGroup group = new WifiP2pGroup();
+        group.setInterface(mIfaceName);
+
+        WifiP2pDevice owner = new WifiP2pDevice();
+        owner.deviceAddress = mGroupOwnerMacAddress;
+        group.setOwner(owner);
+
+        return group;
+    }
+
+    /**
+     * Create new dummy WifiP2pServiceInfo instance.
+     */
+    private WifiP2pServiceInfo createDummyP2pServiceInfo(String... services) {
+        class TestP2pServiceInfo extends WifiP2pServiceInfo {
+            TestP2pServiceInfo(String[] services) {
+                super(Arrays.asList(services));
+            }
+        }
+        return new TestP2pServiceInfo(services);
+    }
+
+    private class GetGetInterfaceAnswer extends AnswerWithArguments {
+        boolean mGetNullInterface;
+
+        GetGetInterfaceAnswer(boolean getNullInterface) {
+            mGetNullInterface = getNullInterface;
+        }
+
+        public void answer(ISupplicant.IfaceInfo iface, ISupplicant.getInterfaceCallback cb) {
+            if (mGetNullInterface) {
+                cb.onValues(mStatusSuccess, null);
+            } else {
+                cb.onValues(mStatusSuccess, mISupplicantIfaceMock);
+            }
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pMonitorTest.java b/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pMonitorTest.java
new file mode 100644
index 0000000..c2c3473
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pMonitorTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.p2p;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.WifiInjector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiP2pMonitor}.
+ */
+@SmallTest
+public class WifiP2pMonitorTest {
+    private static final String P2P_IFACE_NAME = "p2p0";
+    private static final String SECOND_P2P_IFACE_NAME = "p2p1";
+    private WifiP2pMonitor mWifiP2pMonitor;
+    private TestLooper mLooper;
+    private Handler mHandlerSpy;
+    private Handler mSecondHandlerSpy;
+
+    @Before
+    public void setUp() throws Exception {
+        mWifiP2pMonitor = new WifiP2pMonitor(mock(WifiInjector.class));
+        mLooper = new TestLooper();
+        mHandlerSpy = spy(new Handler(mLooper.getLooper()));
+        mSecondHandlerSpy = spy(new Handler(mLooper.getLooper()));
+        mWifiP2pMonitor.setMonitoring(P2P_IFACE_NAME, true);
+    }
+
+    /**
+     * Broadcast message test.
+     */
+    @Test
+    public void testBroadcastSupplicantDisconnectionEvent() {
+        mWifiP2pMonitor.registerHandler(
+                P2P_IFACE_NAME, WifiP2pMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
+        mWifiP2pMonitor.broadcastSupplicantDisconnectionEvent(P2P_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiP2pMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+    /**
+     * Broadcast message to two handlers test.
+     */
+    @Test
+    public void testBroadcastEventToTwoHandlers() {
+        mWifiP2pMonitor.registerHandler(
+                P2P_IFACE_NAME, WifiP2pMonitor.SUP_CONNECTION_EVENT, mHandlerSpy);
+        mWifiP2pMonitor.registerHandler(
+                P2P_IFACE_NAME, WifiP2pMonitor.SUP_CONNECTION_EVENT, mSecondHandlerSpy);
+        mWifiP2pMonitor.broadcastSupplicantConnectionEvent(P2P_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiP2pMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
+        verify(mSecondHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiP2pMonitor.SUP_CONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+    /**
+     * Broadcast message when iface is null.
+     */
+    @Test
+    public void testBroadcastEventWhenIfaceIsNull() {
+        mWifiP2pMonitor.registerHandler(
+                P2P_IFACE_NAME, WifiP2pMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
+        mWifiP2pMonitor.broadcastSupplicantDisconnectionEvent(null);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiP2pMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+    /**
+     * Broadcast message when iface handler is null.
+     */
+    @Test
+    public void testBroadcastEventWhenIfaceHandlerIsNull() {
+        mWifiP2pMonitor.registerHandler(
+                P2P_IFACE_NAME, WifiP2pMonitor.SUP_DISCONNECTION_EVENT, mHandlerSpy);
+        mWifiP2pMonitor.broadcastSupplicantDisconnectionEvent(SECOND_P2P_IFACE_NAME);
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        assertEquals(WifiP2pMonitor.SUP_DISCONNECTION_EVENT, messageCaptor.getValue().what);
+    }
+}
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 767cddf..60e5256 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 boolean isAllChannelsScanned(int band) {
@@ -98,7 +98,7 @@
     protected Set<Integer> expectedBandScanFreqs(int band) {
         ChannelCollection collection = mScanner.getChannelHelper().createChannelCollection();
         collection.addBand(band);
-        return collection.getSupplicantScanFreqs();
+        return collection.getScanFreqs();
     }
 
     protected Set<Integer> expectedBandAndChannelScanFreqs(int band, int... channels) {
@@ -107,7 +107,7 @@
         for (int channel : channels) {
             collection.addChannel(channel);
         }
-        return collection.getSupplicantScanFreqs();
+        return collection.getScanFreqs();
     }
 
     @Test
@@ -120,7 +120,7 @@
                 .build();
 
         doSuccessfulSingleScanTest(settings, expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ),
-                new HashSet<Integer>(),
+                new HashSet<String>(),
                 ScanResults.create(0, isAllChannelsScanned(WifiScanner.WIFI_BAND_24_GHZ),
                         2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), false);
     }
@@ -134,7 +134,7 @@
                 .build();
 
         doSuccessfulSingleScanTest(settings, createFreqSet(5650),
-                new HashSet<Integer>(),
+                new HashSet<String>(),
                 ScanResults.create(0, 5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false);
     }
 
@@ -150,7 +150,7 @@
                 .build();
 
         doSuccessfulSingleScanTest(settings, expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ),
-                new HashSet<Integer>(),
+                new HashSet<String>(),
                 ScanResults.create(0, isAllChannelsScanned(WifiScanner.WIFI_BAND_24_GHZ),
                         2400, 2450, 2450, 2400, 2450, 2450, 2400, 2450, 2450), true);
     }
@@ -161,43 +161,48 @@
      */
     @Test
     public void singleScanSuccessWithHiddenNetworkIds() {
-        int[] hiddenNetworkIds = {0, 5};
+        String[] hiddenNetworkSSIDs = {"test_ssid_1", "test_ssid_2"};
         WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
                 .withBasePeriod(10000)
                 .withMaxApPerScan(10)
-                .withHiddenNetworkIds(hiddenNetworkIds)
+                .withHiddenNetworkSSIDs(hiddenNetworkSSIDs)
                 .addBucketWithChannels(20000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN, 5650)
                 .build();
 
-        Set<Integer> hiddenNetworkIdSet = new HashSet<Integer>();
-        for (int i = 0; i < hiddenNetworkIds.length; i++) {
-            hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
+        Set<String> hiddenNetworkSSIDSet = new HashSet<>();
+        for (int i = 0; i < hiddenNetworkSSIDs.length; i++) {
+            hiddenNetworkSSIDSet.add(hiddenNetworkSSIDs[i]);
         }
         doSuccessfulSingleScanTest(settings, createFreqSet(5650),
-                hiddenNetworkIdSet,
+                hiddenNetworkSSIDSet,
                 ScanResults.create(0, 5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false);
     }
 
     /**
      * Tests whether the provided hidden networkId's in scan settings is truncated to max size
-     * supported by wpa_supplicant when invoking native scan.
+     * supported by wificond when invoking native scan.
      */
     @Test
     public void singleScanSuccessWithTruncatedHiddenNetworkIds() {
-        int[] hiddenNetworkIds = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
+        String[] hiddenNetworkSSIDs = {
+                "test_ssid_0", "test_ssid_1", "test_ssid_2", "test_ssid_3", "test_ssid_4",
+                "test_ssid_5", "test_ssid_6", "test_ssid_7", "test_ssid_8", "test_ssid_9",
+                "test_ssid_10", "test_ssid_11", "test_ssid_12", "test_ssid_13", "test_ssid_14",
+                "test_ssid_15", "test_ssid_16", "test_ssid_17", "test_ssid_18", "test_ssid_19"
+        };
         WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
                 .withBasePeriod(10000)
                 .withMaxApPerScan(10)
-                .withHiddenNetworkIds(hiddenNetworkIds)
+                .withHiddenNetworkSSIDs(hiddenNetworkSSIDs)
                 .addBucketWithChannels(20000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN, 5650)
                 .build();
 
-        Set<Integer> hiddenNetworkIdSet = new HashSet<Integer>();
-        for (int i = 0; i < SupplicantWifiScannerImpl.MAX_HIDDEN_NETWORK_IDS_PER_SCAN; i++) {
-            hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
+        Set<String> hiddenNetworkSSIDSet = new HashSet<>();
+        for (int i = 0; i < WificondScannerImpl.MAX_HIDDEN_NETWORK_IDS_PER_SCAN; i++) {
+            hiddenNetworkSSIDSet.add(hiddenNetworkSSIDs[i]);
         }
         doSuccessfulSingleScanTest(settings, createFreqSet(5650),
-                hiddenNetworkIdSet,
+                hiddenNetworkSSIDSet,
                 ScanResults.create(0, 5650, 5650, 5650, 5650, 5650, 5650, 5650, 5650), false);
     }
 
@@ -220,7 +225,7 @@
         WifiNative.ScanEventHandler eventHandler2 = mock(WifiNative.ScanEventHandler.class);
 
         // scan start succeeds
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
         assertFalse("second scan while first scan running should fail immediately",
@@ -243,7 +248,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan fails
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(false);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(false);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
@@ -273,14 +278,14 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
         mLooper.dispatchAll();
 
         // Fire timeout
-        mAlarmManager.dispatch(SupplicantWifiScannerImpl.TIMEOUT_ALARM_TAG);
+        mAlarmManager.dispatch(WificondScannerImpl.TIMEOUT_ALARM_TAG);
         mLooper.dispatchAll();
 
         order.verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
@@ -289,7 +294,7 @@
     }
 
     /**
-     * Test that a scan failure is reported if supplicant sends a scan failed event
+     * Test that a scan failure is reported if wificond sends a scan failed event
      */
     @Test
     public void singleScanFailOnFailedEvent() {
@@ -307,7 +312,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
@@ -361,14 +366,14 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scans succeed
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // start first scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
 
         expectSuccessfulSingleScan(order, eventHandler,
                 expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ),
-                new HashSet<Integer>(),
+                new HashSet<String>(),
                 ScanResults.create(0, isAllChannelsScanned(WifiScanner.WIFI_BAND_24_GHZ),
                         2400, 2450, 2450), false);
 
@@ -377,7 +382,7 @@
 
         expectSuccessfulSingleScan(order, eventHandler,
                 expectedBandScanFreqs(WifiScanner.WIFI_BAND_BOTH_WITH_DFS),
-                new HashSet<Integer>(),
+                new HashSet<String>(),
                 ScanResults.create(0, true,
                         5150, 5175), false);
 
@@ -385,7 +390,7 @@
     }
 
     /**
-     * Validate that scan results that are returned from supplicant, which are timestamped prior to
+     * Validate that scan results that are returned from wificond, which are timestamped prior to
      * the start of the scan, are ignored.
      */
     @Test
@@ -399,7 +404,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,
@@ -434,7 +439,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
@@ -479,19 +484,19 @@
     }
 
     protected void doSuccessfulSingleScanTest(WifiNative.ScanSettings settings,
-            Set<Integer> expectedScan, Set<Integer> expectedHiddenNetIds, ScanResults results,
+            Set<Integer> expectedScan, Set<String> expectedHiddenNetSSIDs, ScanResults results,
             boolean expectFullResults) {
         WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
 
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // scan succeeds
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // start scan
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
 
-        expectSuccessfulSingleScan(order, eventHandler, expectedScan, expectedHiddenNetIds,
+        expectSuccessfulSingleScan(order, eventHandler, expectedScan, expectedHiddenNetSSIDs,
                 results, expectFullResults);
 
         verifyNoMoreInteractions(eventHandler);
@@ -499,8 +504,8 @@
 
     protected void expectSuccessfulSingleScan(InOrder order,
             WifiNative.ScanEventHandler eventHandler, Set<Integer> expectedScan,
-            Set<Integer> expectedHiddenNetIds, ScanResults results, boolean expectFullResults) {
-        order.verify(mWifiNative).scan(eq(expectedScan), eq(expectedHiddenNetIds));
+            Set<String> expectedHiddenNetSSIDs, ScanResults results, boolean expectFullResults) {
+        order.verify(mWifiNative).scan(eq(expectedScan), eq(expectedHiddenNetSSIDs));
 
         when(mWifiNative.getScanResults()).thenReturn(results.getScanDetailArrayList());
 
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java
index d5ff877..e8138ca 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/HalWifiScannerTest.java
@@ -34,6 +34,7 @@
                 new int[]{2400, 2450},
                 new int[]{5150, 5175},
                 new int[]{5600, 5650});
-        mScanner = new HalWifiScannerImpl(mContext, mWifiNative, mLooper.getLooper(), mClock);
+        mScanner = new HalWifiScannerImpl(mContext, mWifiNative, mWifiMonitor, mLooper.getLooper(),
+                mClock);
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java
index 3e482a9..de3f7ff 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/KnownBandsChannelHelperTest.java
@@ -285,7 +285,7 @@
             assertThat(bucketSettings, channelsAre());
 
             assertEquals(Collections.<Integer>emptySet(),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertTrue(mChannelCollection.isEmpty());
             assertFalse(mChannelCollection.containsChannel(2400));
@@ -306,7 +306,7 @@
             assertThat(bucketSettings, channelsAre());
 
             assertEquals(Collections.<Integer>emptySet(),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertTrue(mChannelCollection.isEmpty());
             assertFalse(mChannelCollection.containsChannel(2400));
@@ -326,7 +326,7 @@
             assertThat(bucketSettings, bandIs(WifiScanner.WIFI_BAND_24_GHZ));
 
             assertEquals(new HashSet<Integer>(Arrays.asList(2400, 2450)),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -346,7 +346,7 @@
             assertThat(bucketSettings, channelsAre(2400));
 
             assertEquals(new HashSet<Integer>(Arrays.asList(2400)),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -367,7 +367,7 @@
             assertThat(bucketSettings, channelsAre(2400, 2450));
 
             assertEquals(new HashSet<Integer>(Arrays.asList(2400, 2450)),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -388,7 +388,7 @@
             assertThat(bucketSettings, bandIs(WifiScanner.WIFI_BAND_24_GHZ));
 
             assertEquals(new HashSet<Integer>(Arrays.asList(2400, 2450)),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -409,7 +409,7 @@
             assertThat(bucketSettings, channelsAre(2400, 2450, 5150));
 
             assertEquals(new HashSet<Integer>(Arrays.asList(2400, 2450, 5150)),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -429,7 +429,7 @@
             mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
             assertThat(bucketSettings, bandIs(WifiScanner.WIFI_BAND_BOTH_WITH_DFS));
 
-            assertNull(mChannelCollection.getSupplicantScanFreqs());
+            assertNull(mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/NoBandChannelHelperTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/NoBandChannelHelperTest.java
index 2863b9f..145b214 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/NoBandChannelHelperTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/NoBandChannelHelperTest.java
@@ -221,7 +221,7 @@
             assertThat(bucketSettings, channelsAre());
 
             assertEquals(Collections.<Integer>emptySet(),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertTrue(mChannelCollection.isEmpty());
             assertFalse(mChannelCollection.containsChannel(2400));
@@ -241,7 +241,7 @@
             assertThat(bucketSettings, channelsAre());
 
             assertEquals(Collections.<Integer>emptySet(),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertTrue(mChannelCollection.isEmpty());
             assertFalse(mChannelCollection.containsChannel(2400));
@@ -259,7 +259,7 @@
             mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
             assertThat(bucketSettings, bandIs(ALL_BANDS));
 
-            assertNull(mChannelCollection.getSupplicantScanFreqs());
+            assertNull(mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -279,7 +279,7 @@
             assertThat(bucketSettings, channelsAre(2400));
 
             assertEquals(new HashSet<Integer>(Arrays.asList(2400)),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -300,7 +300,7 @@
             assertThat(bucketSettings, channelsAre(2400, 2450));
 
             assertEquals(new HashSet<Integer>(Arrays.asList(2400, 2450)),
-                    mChannelCollection.getSupplicantScanFreqs());
+                    mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -320,7 +320,7 @@
             mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
             assertThat(bucketSettings, bandIs(ALL_BANDS));
 
-            assertNull(mChannelCollection.getSupplicantScanFreqs());
+            assertNull(mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -340,7 +340,7 @@
             mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
             assertThat(bucketSettings, bandIs(ALL_BANDS));
 
-            assertNull(mChannelCollection.getSupplicantScanFreqs());
+            assertNull(mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
@@ -360,7 +360,7 @@
             mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
             assertThat(bucketSettings, bandIs(WifiScanner.WIFI_BAND_BOTH_WITH_DFS));
 
-            assertNull(mChannelCollection.getSupplicantScanFreqs());
+            assertNull(mChannelCollection.getScanFreqs());
 
             assertFalse(mChannelCollection.isEmpty());
             assertTrue(mChannelCollection.containsChannel(2400));
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 3498b53..85b19fe 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
@@ -17,13 +17,17 @@
 package com.android.server.wifi.scanner;
 
 import static com.android.server.wifi.ScanTestUtil.*;
+import static com.android.server.wifi.scanner.WifiScanningServiceImpl.WifiSingleScanStateMachine
+        .CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS;
 
 import static org.junit.Assert.*;
-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.Intent;
 import android.content.IntentFilter;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
@@ -31,27 +35,28 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 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.FakeWifiLog;
+import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.ScanResults;
 import com.android.server.wifi.TestUtil;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiMetrics;
-import com.android.server.wifi.WifiMetricsProto;
 import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.util.WifiAsyncChannel;
 
 import org.junit.After;
 import org.junit.Before;
@@ -60,7 +65,8 @@
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.internal.matchers.CapturingMatcher;
+import org.mockito.Spy;
+import org.mockito.compat.CapturingMatcher;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -68,6 +74,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.regex.Pattern;
 
 /**
@@ -77,15 +84,19 @@
 public class WifiScanningServiceTest {
     public static final String TAG = "WifiScanningServiceTest";
 
+    private static final int TEST_MAX_SCAN_BUCKETS_IN_CAPABILITIES = 8;
+
     @Mock Context mContext;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     @Mock WifiScannerImpl mWifiScannerImpl;
     @Mock WifiScannerImpl.WifiScannerImplFactory mWifiScannerImplFactory;
     @Mock IBatteryStats mBatteryStats;
     @Mock WifiInjector mWifiInjector;
+    @Mock FrameworkFacade mFrameworkFacade;
     @Mock Clock mClock;
+    @Spy FakeWifiLog mLog;
     WifiMetrics mWifiMetrics;
-    MockLooper mLooper;
+    TestLooper mLooper;
     WifiScanningServiceImpl mWifiScanningServiceImpl;
 
 
@@ -93,22 +104,28 @@
     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);
 
         ChannelHelper channelHelper = new PresetKnownBandsChannelHelper(
                 new int[]{2400, 2450},
                 new int[]{5150, 5175},
                 new int[]{5600, 5650, 5660});
 
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
+        mWifiMetrics = new WifiMetrics(mClock, mLooper.getLooper());
         when(mWifiScannerImplFactory
-                .create(any(Context.class), any(Looper.class), any(Clock.class)))
+                .create(any(), any(), any()))
                 .thenReturn(mWifiScannerImpl);
         when(mWifiScannerImpl.getChannelHelper()).thenReturn(channelHelper);
         when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
+        when(mWifiInjector.makeLog(anyString())).thenReturn(mLog);
+        WifiAsyncChannel mWifiAsyncChannel = new WifiAsyncChannel("ScanningServiceTest");
+        mWifiAsyncChannel.setWifiLog(mLog);
+        when(mFrameworkFacade.makeWifiAsyncChannel(anyString())).thenReturn(mWifiAsyncChannel);
+        when(mWifiInjector.getFrameworkFacade()).thenReturn(mFrameworkFacade);
+        when(mWifiInjector.getClock()).thenReturn(mClock);
         mWifiScanningServiceImpl = new WifiScanningServiceImpl(mContext, mLooper.getLooper(),
                 mWifiScannerImplFactory, mBatteryStats, mWifiInjector);
     }
@@ -147,7 +164,7 @@
     private static Message verifyHandleMessageAndGetMessage(InOrder order, Handler handler,
             final int what) {
         CapturingMatcher<Message> messageMatcher = new CapturingMatcher<Message>() {
-            public boolean matches(Object argument) {
+            public boolean matchesObject(Object argument) {
                 Message message = (Message) argument;
                 return message.what == what;
             }
@@ -156,7 +173,7 @@
         return messageMatcher.getLastValue();
     }
 
-    private static void verifyScanResultsRecieved(InOrder order, Handler handler, int listenerId,
+    private static void verifyScanResultsReceived(InOrder order, Handler handler, int listenerId,
             WifiScanner.ScanData... expected) {
         Message scanResultMessage = verifyHandleMessageAndGetMessage(order, handler,
                 WifiScanner.CMD_SCAN_RESULT);
@@ -171,7 +188,7 @@
                 ((WifiScanner.ParcelableScanData) scanResultMessage.obj).getResults());
     }
 
-    private static void verifySingleScanCompletedRecieved(InOrder order, Handler handler,
+    private static void verifySingleScanCompletedReceived(InOrder order, Handler handler,
             int listenerId) {
         Message completedMessage = verifyHandleMessageAndGetMessage(order, handler,
                 WifiScanner.CMD_SINGLE_SCAN_COMPLETED);
@@ -329,20 +346,18 @@
     private static final int MAX_AP_PER_SCAN = 16;
     private void startServiceAndLoadDriver() {
         mWifiScanningServiceImpl.startService();
-        setupAndLoadDriver();
+        setupAndLoadDriver(TEST_MAX_SCAN_BUCKETS_IN_CAPABILITIES);
     }
 
-    private void setupAndLoadDriver() {
+    private void setupAndLoadDriver(int max_scan_buckets) {
         when(mWifiScannerImpl.getScanCapabilities(any(WifiNative.ScanCapabilities.class)))
                 .thenAnswer(new AnswerWithArguments() {
                         public boolean answer(WifiNative.ScanCapabilities capabilities) {
                             capabilities.max_scan_cache_size = Integer.MAX_VALUE;
-                            capabilities.max_scan_buckets = 8;
+                            capabilities.max_scan_buckets = max_scan_buckets;
                             capabilities.max_ap_cache_per_scan = MAX_AP_PER_SCAN;
                             capabilities.max_rssi_sample_size = 8;
                             capabilities.max_scan_reporting_threshold = 10;
-                            capabilities.max_hotlist_bssids = 0;
-                            capabilities.max_significant_wifi_change_aps = 0;
                             return true;
                         }
                     });
@@ -394,6 +409,7 @@
     @Test
     public void startService() throws Exception {
         mWifiScanningServiceImpl.startService();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         verifyNoMoreInteractions(mWifiScannerImplFactory);
 
         Handler handler = mock(Handler.class);
@@ -407,7 +423,7 @@
     @Test
     public void disconnectClientBeforeWifiEnabled() throws Exception {
         mWifiScanningServiceImpl.startService();
-
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         BidirectionalAsyncChannel controlChannel = connectChannel(mock(Handler.class));
         mLooper.dispatchAll();
 
@@ -418,8 +434,9 @@
     @Test
     public void loadDriver() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         verify(mWifiScannerImplFactory, times(1))
-                .create(any(Context.class), any(Looper.class), any(Clock.class));
+                .create(any(), any(), any());
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -435,11 +452,11 @@
     @Test
     public void disconnectClientAfterStartingWifi() throws Exception {
         mWifiScanningServiceImpl.startService();
-
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         BidirectionalAsyncChannel controlChannel = connectChannel(mock(Handler.class));
         mLooper.dispatchAll();
 
-        setupAndLoadDriver();
+        setupAndLoadDriver(TEST_MAX_SCAN_BUCKETS_IN_CAPABILITIES);
 
         controlChannel.disconnect();
         mLooper.dispatchAll();
@@ -448,6 +465,7 @@
     @Test
     public void connectAndDisconnectClientAfterStartingWifi() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         BidirectionalAsyncChannel controlChannel = connectChannel(mock(Handler.class));
         mLooper.dispatchAll();
@@ -458,6 +476,7 @@
     @Test
     public void sendInvalidCommand() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -468,11 +487,27 @@
                 "Invalid request");
     }
 
+    @Test
+    public void rejectBackgroundScanRequestWhenHalReturnsInvalidCapabilities() throws Exception {
+        mWifiScanningServiceImpl.startService();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
+
+        setupAndLoadDriver(0);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler);
+        sendBackgroundScanRequest(controlChannel, 122, generateValidScanSettings(), null);
+        mLooper.dispatchAll();
+        verifyFailedResponse(order, handler, 122, WifiScanner.REASON_UNSPECIFIED, "not available");
+    }
+
     private void doSuccessfulSingleScan(WifiScanner.ScanSettings requestSettings,
             WifiNative.ScanSettings nativeSettings, ScanResults results) throws RemoteException {
         int requestId = 12;
         WorkSource workSource = new WorkSource(2292);
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -493,8 +528,13 @@
         eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(order, handler, requestId, results.getScanData());
-        verifySingleScanCompletedRecieved(order, handler, requestId);
+        verifyScanResultsReceived(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId);
+        if (results.getScanData().isAllChannelsScanned()) {
+            verify(mContext).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
+        } else {
+            verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
+        }
         verifyNoMoreInteractions(handler);
         verify(mBatteryStats).noteWifiScanStoppedFromSource(eq(workSource));
         assertDumpContainsRequestLog("addSingleScanRequest", requestId);
@@ -574,6 +614,7 @@
         int requestId = 33;
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -611,6 +652,7 @@
         WorkSource workSource = new WorkSource(Binder.getCallingUid()); // don't explicitly set
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -639,6 +681,7 @@
         assertEquals(mWifiMetrics.getOneshotScanCount(), 1);
         assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_UNKNOWN), 1);
         verify(mBatteryStats).noteWifiScanStoppedFromSource(eq(workSource));
+        verify(mContext).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
     }
 
     /**
@@ -651,6 +694,7 @@
         int requestId = 2293;
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
@@ -692,6 +736,7 @@
         int listenerRequestId = 2295;
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
@@ -739,23 +784,24 @@
         WifiScanner.ScanSettings requestSettings1 = createRequest(channelsToSpec(2400), 0,
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId1 = 12;
-        ScanResults results1 = ScanResults.create(0, 2400);
+        ScanResults results1 = ScanResults.create(0, true, 2400);
 
 
         WifiScanner.ScanSettings requestSettings2 = createRequest(channelsToSpec(2450, 5175), 0,
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId2 = 13;
-        ScanResults results2 = ScanResults.create(0, 2450);
+        ScanResults results2 = ScanResults.create(0, true, 2450);
 
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
-        InOrder order = inOrder(handler, mWifiScannerImpl);
+        InOrder order = inOrder(handler, mWifiScannerImpl, mContext);
 
         // Run scan 1
         sendSingleScanRequest(controlChannel, requestId1, requestSettings1, null);
@@ -771,8 +817,15 @@
         eventHandler1.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(order, handler, requestId1, results1.getScanData());
-        verifySingleScanCompletedRecieved(order, handler, requestId1);
+        // Note: The order of the following verification calls looks out of order if you compare to
+        // the source code of WifiScanningServiceImpl WifiSingleScanStateMachine.reportScanResults.
+        // This is due to the fact that verifyScanResultsReceived and
+        // verifySingleScanCompletedReceived require an additional call to handle the message that
+        // is created in reportScanResults.  This handling is done in the two verify*Received calls
+        // that is run AFTER the reportScanResults method in WifiScanningServiceImpl completes.
+        order.verify(mContext).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
+        verifyScanResultsReceived(order, handler, requestId1, results1.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId1);
 
         // Run scan 2
         sendSingleScanRequest(controlChannel, requestId2, requestSettings2, null);
@@ -788,8 +841,9 @@
         eventHandler2.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(order, handler, requestId2, results2.getScanData());
-        verifySingleScanCompletedRecieved(order, handler, requestId2);
+        order.verify(mContext).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
+        verifyScanResultsReceived(order, handler, requestId2, results2.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId2);
     }
 
     /**
@@ -801,15 +855,16 @@
         WifiScanner.ScanSettings requestSettings1 = createRequest(channelsToSpec(2400), 0,
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId1 = 12;
-        ScanResults results1 = ScanResults.create(0, 2400);
+        ScanResults results1 = ScanResults.create(0, true, 2400);
 
         WifiScanner.ScanSettings requestSettings2 = createRequest(channelsToSpec(2450, 5175), 0,
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId2 = 13;
-        ScanResults results2 = ScanResults.create(0, 2450);
+        ScanResults results2 = ScanResults.create(0, true, 2450);
 
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
@@ -838,8 +893,9 @@
         eventHandler1.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(handlerOrder, handler, requestId1, results1.getScanData());
-        verifySingleScanCompletedRecieved(handlerOrder, handler, requestId1);
+        verifyScanResultsReceived(handlerOrder, handler, requestId1, results1.getScanData());
+        verifySingleScanCompletedReceived(handlerOrder, handler, requestId1);
+        verify(mContext).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
 
         // now that the first scan completed we expect the second one to start
         WifiNative.ScanEventHandler eventHandler2 = verifyStartSingleScan(nativeOrder,
@@ -851,8 +907,9 @@
         eventHandler2.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(handlerOrder, handler, requestId2, results2.getScanData());
-        verifySingleScanCompletedRecieved(handlerOrder, handler, requestId2);
+        verifyScanResultsReceived(handlerOrder, handler, requestId2, results2.getScanData());
+        verifySingleScanCompletedReceived(handlerOrder, handler, requestId2);
+        verify(mContext, times(2)).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
         assertEquals(mWifiMetrics.getOneshotScanCount(), 2);
         assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 2);
     }
@@ -868,19 +925,19 @@
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId1 = 12;
         WorkSource workSource1 = new WorkSource(1121);
-        ScanResults results1 = ScanResults.create(0, 2400);
+        ScanResults results1 = ScanResults.create(0, false, 2400);
 
         WifiScanner.ScanSettings requestSettings2 = createRequest(channelsToSpec(2450, 5175), 0,
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId2 = 13;
         WorkSource workSource2 = new WorkSource(Binder.getCallingUid()); // don't explicitly set
-        ScanResults results2 = ScanResults.create(0, 2450, 5175, 2450);
+        ScanResults results2 = ScanResults.create(0, false, 2450, 5175, 2450);
 
         WifiScanner.ScanSettings requestSettings3 = createRequest(channelsToSpec(5150), 0,
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId3 = 15;
         WorkSource workSource3 = new WorkSource(2292);
-        ScanResults results3 = ScanResults.create(0, 5150, 5150, 5150, 5150);
+        ScanResults results3 = ScanResults.create(0, false, 5150, 5150, 5150, 5150);
 
         WifiNative.ScanSettings nativeSettings2and3 = createSingleScanNativeSettingsForChannels(
                 WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN, channelsToSpec(2450, 5175, 5150));
@@ -891,6 +948,7 @@
 
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
@@ -927,8 +985,9 @@
         eventHandler1.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(handlerOrder, handler, requestId1, results1.getScanData());
-        verifySingleScanCompletedRecieved(handlerOrder, handler, requestId1);
+        verifyScanResultsReceived(handlerOrder, handler, requestId1, results1.getScanData());
+        verifySingleScanCompletedReceived(handlerOrder, handler, requestId1);
+        verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
         verify(mBatteryStats).noteWifiScanStoppedFromSource(eq(workSource1));
         verify(mBatteryStats).noteWifiScanStartedFromSource(eq(workSource2and3));
 
@@ -985,6 +1044,7 @@
 
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
@@ -1052,6 +1112,7 @@
         ScanResults results3 = results2400;
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
@@ -1105,8 +1166,9 @@
 
         mLooper.dispatchAll();
 
-        verifyScanResultsRecieved(handlerOrder, handler, requestId2, results2.getScanData());
-        verifySingleScanCompletedRecieved(handlerOrder, handler, requestId2);
+        verifyScanResultsReceived(handlerOrder, handler, requestId2, results2.getScanData());
+        verifySingleScanCompletedReceived(handlerOrder, handler, requestId2);
+        verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
         assertEquals(mWifiMetrics.getOneshotScanCount(), 3);
         assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 3);
 
@@ -1124,6 +1186,278 @@
     }
 
     /**
+     * Verify that WifiService provides a way to get the most recent SingleScan results.
+     */
+    @Test
+    public void retrieveSingleScanResults() throws Exception {
+        WifiScanner.ScanSettings requestSettings =
+                createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS,
+                              0, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        ScanResults expectedResults = ScanResults.create(0, true, 2400, 5150, 5175);
+        doSuccessfulSingleScan(requestSettings,
+                               computeSingleScanNativeSettings(requestSettings),
+                               expectedResults);
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response = verifyHandleMessageAndGetMessage(order, handler);
+        List<ScanResult> results = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response.obj).getResults());
+        assertEquals(results.size(), expectedResults.getRawScanResults().length);
+
+        // Make sure that we logged the scan results in the dump method.
+        String serviceDump = dumpService();
+        Pattern logLineRegex = Pattern.compile("Latest scan results:");
+        assertTrue("dump did not contain Latest scan results: " + serviceDump + "\n",
+                logLineRegex.matcher(serviceDump).find());
+    }
+
+    /**
+     * Verify that WifiService provides a way to get the most recent SingleScan results even when
+     * they are empty.
+     */
+    @Test
+    public void retrieveSingleScanResultsEmpty() throws Exception {
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        doSuccessfulSingleScan(requestSettings, computeSingleScanNativeSettings(requestSettings),
+                ScanResults.create(0, new int[0]));
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response = verifyHandleMessageAndGetMessage(order, handler);
+
+        List<ScanResult> results = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response.obj).getResults());
+        assertEquals(results.size(), 0);
+    }
+
+    /**
+     * Verify that WifiService will return empty SingleScan results if a scan has not been
+     * performed.
+     */
+    @Test
+    public void retrieveSingleScanResultsBeforeAnySingleScans() throws Exception {
+        startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response = verifyHandleMessageAndGetMessage(order, handler);
+
+        List<ScanResult> results = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response.obj).getResults());
+        assertEquals(results.size(), 0);
+    }
+
+    /**
+     * Verify that the newest full scan results are returned by WifiService.getSingleScanResults.
+     */
+    @Test
+    public void retrieveMostRecentFullSingleScanResults() throws Exception {
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        ScanResults expectedResults = ScanResults.create(0, true, 2400, 5150, 5175);
+        doSuccessfulSingleScan(requestSettings,
+                               computeSingleScanNativeSettings(requestSettings),
+                               expectedResults);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response = verifyHandleMessageAndGetMessage(order, handler);
+
+        List<ScanResult> results = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response.obj).getResults());
+        assertEquals(results.size(), expectedResults.getRawScanResults().length);
+
+        // now update with a new scan that only has one result
+        int secondScanRequestId = 35;
+        ScanResults expectedSingleResult = ScanResults.create(0, true, 5150);
+        sendSingleScanRequest(controlChannel, secondScanRequestId, requestSettings, null);
+
+        mLooper.dispatchAll();
+        WifiNative.ScanEventHandler eventHandler = verifyStartSingleScan(order,
+                computeSingleScanNativeSettings(requestSettings));
+        verifySuccessfulResponse(order, handler, secondScanRequestId);
+
+        // dispatch scan 2 results
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(expectedSingleResult.getScanData());
+        eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+        verifyScanResultsReceived(order, handler, secondScanRequestId,
+                expectedSingleResult.getScanData());
+        verifySingleScanCompletedReceived(order, handler, secondScanRequestId);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response2 = verifyHandleMessageAndGetMessage(order, handler);
+
+        List<ScanResult> results2 = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response2.obj).getResults());
+        assertEquals(results2.size(), expectedSingleResult.getRawScanResults().length);
+    }
+
+    /**
+     * Verify that the newest partial scan results are not returned by
+     * WifiService.getSingleScanResults.
+     */
+    @Test
+    public void doesNotRetrieveMostRecentPartialSingleScanResults() throws Exception {
+        WifiScanner.ScanSettings fullRequestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        ScanResults expectedFullResults = ScanResults.create(0, true, 2400, 5150, 5175);
+        doSuccessfulSingleScan(fullRequestSettings,
+                computeSingleScanNativeSettings(fullRequestSettings),
+                expectedFullResults);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response = verifyHandleMessageAndGetMessage(order, handler);
+
+        List<ScanResult> results = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response.obj).getResults());
+        assertEquals(results.size(), expectedFullResults.getRawScanResults().length);
+
+        // now update with a new scan that only has one result
+        int secondScanRequestId = 35;
+        WifiScanner.ScanSettings partialRequestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH,
+                0, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        ScanResults expectedPartialResults = ScanResults.create(0, false, 5150);
+        sendSingleScanRequest(controlChannel, secondScanRequestId, partialRequestSettings, null);
+
+        mLooper.dispatchAll();
+        WifiNative.ScanEventHandler eventHandler = verifyStartSingleScan(order,
+                computeSingleScanNativeSettings(partialRequestSettings));
+        verifySuccessfulResponse(order, handler, secondScanRequestId);
+
+        // dispatch scan 2 results
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(expectedPartialResults.getScanData());
+        eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+        verifyScanResultsReceived(order, handler, secondScanRequestId,
+                expectedPartialResults.getScanData());
+        verifySingleScanCompletedReceived(order, handler, secondScanRequestId);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response2 = verifyHandleMessageAndGetMessage(order, handler);
+
+        List<ScanResult> results2 = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response2.obj).getResults());
+        assertEquals(results2.size(), expectedFullResults.getRawScanResults().length);
+    }
+
+    /**
+     * Verify that the scan results returned by WifiService.getSingleScanResults are not older
+     * than {@link com.android.server.wifi.scanner.WifiScanningServiceImpl
+     * .WifiSingleScanStateMachine#CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}.
+     */
+    @Test
+    public void doesNotRetrieveStaleScanResultsFromLastFullSingleScan() throws Exception {
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        ScanResults scanResults = ScanResults.create(0, true, 2400, 5150, 5175);
+
+        // Out of the 3 scan results, modify the timestamp of 2 of them to be within the expiration
+        // age and 1 out of it.
+        long currentTimeInMillis = CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS * 2;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeInMillis);
+        scanResults.getRawScanResults()[0].timestamp = (currentTimeInMillis - 1) * 1000;
+        scanResults.getRawScanResults()[1].timestamp = (currentTimeInMillis - 2) * 1000;
+        scanResults.getRawScanResults()[2].timestamp =
+                (currentTimeInMillis - CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS) * 1000;
+        List<ScanResult> expectedResults = new ArrayList<ScanResult>() {{
+                add(scanResults.getRawScanResults()[0]);
+                add(scanResults.getRawScanResults()[1]);
+            }};
+
+        doSuccessfulSingleScan(requestSettings,
+                computeSingleScanNativeSettings(requestSettings), scanResults);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response = verifyHandleMessageAndGetMessage(order, handler);
+
+        List<ScanResult> results = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response.obj).getResults());
+        assertScanResultsEquals(expectedResults.toArray(new ScanResult[expectedResults.size()]),
+                results.toArray(new ScanResult[results.size()]));
+    }
+
+    /**
+     * Cached scan results should be cleared after the driver is unloaded.
+     */
+    @Test
+    public void validateScanResultsClearedAfterDriverUnloaded() throws Exception {
+        WifiScanner.ScanSettings requestSettings =
+                createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS,
+                              0, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        ScanResults expectedResults = ScanResults.create(0, true, 2400, 5150, 5175);
+        doSuccessfulSingleScan(requestSettings,
+                               computeSingleScanNativeSettings(requestSettings),
+                               expectedResults);
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response = verifyHandleMessageAndGetMessage(order, handler);
+        List<ScanResult> results = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response.obj).getResults());
+        assertEquals(results.size(), expectedResults.getRawScanResults().length);
+
+        // disable wifi
+        TestUtil.sendWifiScanAvailable(mBroadcastReceiver,
+                                       mContext,
+                                       WifiManager.WIFI_STATE_DISABLED);
+        // Now get scan results again. The returned list should be empty since we
+        // clear the cache when exiting the DriverLoaded state.
+        controlChannel.sendMessage(
+                Message.obtain(null, WifiScanner.CMD_GET_SINGLE_SCAN_RESULTS, 0));
+        mLooper.dispatchAll();
+        Message response2 = verifyHandleMessageAndGetMessage(order, handler);
+        List<ScanResult> results2 = Arrays.asList(
+                ((WifiScanner.ParcelableScanResults) response2.obj).getResults());
+        assertEquals(results2.size(), 0);
+    }
+
+    /**
      * Register a single scan listener and do a single scan
      */
     @Test
@@ -1137,6 +1471,7 @@
         int listenerRequestId = 13;
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -1160,9 +1495,10 @@
         eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(order, handler, requestId, results.getScanData());
-        verifySingleScanCompletedRecieved(order, handler, requestId);
-        verifyScanResultsRecieved(order, handler, listenerRequestId, results.getScanData());
+        verifyScanResultsReceived(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId);
+        verifyScanResultsReceived(order, handler, listenerRequestId, results.getScanData());
+        verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
         verifyNoMoreInteractions(handler);
 
         assertDumpContainsRequestLog("registerScanListener", listenerRequestId);
@@ -1184,6 +1520,7 @@
         int listenerRequestId = 13;
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -1210,8 +1547,9 @@
         eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(order, handler, requestId, results.getScanData());
-        verifySingleScanCompletedRecieved(order, handler, requestId);
+        verifyScanResultsReceived(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId);
+        verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
         verifyNoMoreInteractions(handler);
 
         assertDumpContainsRequestLog("registerScanListener", listenerRequestId);
@@ -1223,7 +1561,7 @@
      * satisfied by the first scan. Verify that the first completes and the second two are merged.
      */
     @Test
-    public void scanListenerRecievesAllResults() throws RemoteException {
+    public void scanListenerReceivesAllResults() throws RemoteException {
         WifiScanner.ScanSettings requestSettings1 = createRequest(channelsToSpec(2400), 0,
                 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
         int requestId1 = 12;
@@ -1247,6 +1585,7 @@
 
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
                         any(WifiNative.ScanEventHandler.class))).thenReturn(true);
@@ -1286,9 +1625,10 @@
         eventHandler1.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
 
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(handlerOrder, handler, requestId1, results1.getScanData());
-        verifySingleScanCompletedRecieved(handlerOrder, handler, requestId1);
-        verifyScanResultsRecieved(handlerOrder, handler, listenerRequestId, results1.getScanData());
+        verifyScanResultsReceived(handlerOrder, handler, requestId1, results1.getScanData());
+        verifySingleScanCompletedReceived(handlerOrder, handler, requestId1);
+        verifyScanResultsReceived(handlerOrder, handler, listenerRequestId, results1.getScanData());
+        verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), eq(UserHandle.ALL));
 
         // now that the first scan completed we expect the second and third ones to start
         WifiNative.ScanEventHandler eventHandler2and3 = verifyStartSingleScan(nativeOrder,
@@ -1315,6 +1655,7 @@
     private void doSuccessfulBackgroundScan(WifiScanner.ScanSettings requestSettings,
             WifiNative.ScanSettings nativeSettings) {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
 
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
@@ -1421,9 +1762,6 @@
         for (i = 0; i < requestPnoSettings.networkList.length; i++) {
             nativePnoSettings.networkList[i] = new WifiNative.PnoNetwork();
             nativePnoSettings.networkList[i].ssid = requestPnoSettings.networkList[i].ssid;
-            nativePnoSettings.networkList[i].networkId =
-                    requestPnoSettings.networkList[i].networkId;
-            nativePnoSettings.networkList[i].priority = requestPnoSettings.networkList[i].priority;
             nativePnoSettings.networkList[i].flags = requestPnoSettings.networkList[i].flags;
             nativePnoSettings.networkList[i].auth_bit_field =
                     requestPnoSettings.networkList[i].authBitField;
@@ -1470,7 +1808,7 @@
                 ((WifiScanner.ParcelableScanResults) networkFoundMessage.obj).getResults());
     }
 
-    private void verifyPnoNetworkFoundRecieved(InOrder order, Handler handler, int listenerId,
+    private void verifyPnoNetworkFoundReceived(InOrder order, Handler handler, int listenerId,
             ScanResult[] expected) {
         Message scanResultMessage = verifyHandleMessageAndGetMessage(order, handler,
                 WifiScanner.CMD_PNO_NETWORK_FOUND);
@@ -1549,12 +1887,13 @@
     }
 
     /**
-     * Tests Supplicant PNO scan when the PNO scan results contain IE info. This ensures that the
+     * Tests wificond PNO scan when the PNO scan results contain IE info. This ensures that the
      * PNO scan results are plumbed back to the client as a PNO network found event.
      */
     @Test
     public void testSuccessfulHwPnoScanWithNoBackgroundScan() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
         InOrder order = inOrder(handler, mWifiScannerImpl);
@@ -1569,7 +1908,7 @@
         sendPnoScanRequest(controlChannel, requestId, scanSettings.first, pnoSettings.first);
         expectHwPnoScanWithNoBackgroundScan(order, handler, requestId, pnoSettings.second,
                 scanResults);
-        verifyPnoNetworkFoundRecieved(order, handler, requestId, scanResults.getRawScanResults());
+        verifyPnoNetworkFoundReceived(order, handler, requestId, scanResults.getRawScanResults());
     }
 
     /**
@@ -1579,6 +1918,7 @@
     @Test
     public void testSuccessfulHwPnoScanWithBackgroundScan() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
         InOrder order = inOrder(handler, mWifiScannerImpl);
@@ -1593,7 +1933,7 @@
         sendPnoScanRequest(controlChannel, requestId, scanSettings.first, pnoSettings.first);
         expectHwPnoScanWithBackgroundScan(order, handler, requestId, scanSettings.second,
                 pnoSettings.second, scanResults);
-        verifyPnoNetworkFoundRecieved(order, handler, requestId, scanResults.getRawScanResults());
+        verifyPnoNetworkFoundReceived(order, handler, requestId, scanResults.getRawScanResults());
     }
 
     /**
@@ -1603,6 +1943,7 @@
     @Test
     public void testSuccessfulHwPnoScanWithBackgroundScanWithNoIE() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
         InOrder order = inOrder(handler, mWifiScannerImpl);
@@ -1622,7 +1963,7 @@
         ArrayList<ScanResult> sortScanList =
                 new ArrayList<ScanResult>(Arrays.asList(scanResults.getRawScanResults()));
         Collections.sort(sortScanList, WifiScannerImpl.SCAN_RESULT_SORT_COMPARATOR);
-        verifyPnoNetworkFoundRecieved(order, handler, requestId,
+        verifyPnoNetworkFoundReceived(order, handler, requestId,
                 sortScanList.toArray(new ScanResult[sortScanList.size()]));
     }
 
@@ -1633,6 +1974,7 @@
     @Test
     public void testSuccessfulSwPnoScan() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
         InOrder order = inOrder(handler, mWifiScannerImpl);
@@ -1646,7 +1988,7 @@
 
         sendPnoScanRequest(controlChannel, requestId, scanSettings.first, pnoSettings.first);
         expectSwPnoScan(order, scanSettings.second, scanResults);
-        verifyPnoNetworkFoundRecieved(order, handler, requestId, scanResults.getRawScanResults());
+        verifyPnoNetworkFoundReceived(order, handler, requestId, scanResults.getRawScanResults());
     }
 
     /**
@@ -1656,6 +1998,7 @@
     @Test
     public void processSingleScanRequestAfterDisconnect() throws Exception {
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         BidirectionalAsyncChannel controlChannel = connectChannel(mock(Handler.class));
         mLooper.dispatchAll();
 
@@ -1694,6 +2037,7 @@
         int requestId = 9;
 
         startServiceAndLoadDriver();
+        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
         Handler handler = mock(Handler.class);
         BidirectionalAsyncChannel controlChannel = connectChannel(handler);
         mLooper.dispatchAll();
@@ -1714,8 +2058,8 @@
 
         eventHandler1.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(order, handler, requestId, results.getScanData());
-        verifySingleScanCompletedRecieved(order, handler, requestId);
+        verifyScanResultsReceived(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId);
         verifyNoMoreInteractions(handler);
 
         controlChannel.sendMessage(Message.obtain(null, AsyncChannel.CMD_CHANNEL_DISCONNECTED,
@@ -1730,8 +2074,8 @@
 
         eventHandler2.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
         mLooper.dispatchAll();
-        verifyScanResultsRecieved(order, handler, requestId, results.getScanData());
-        verifySingleScanCompletedRecieved(order, handler, requestId);
+        verifyScanResultsReceived(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedReceived(order, handler, requestId);
         verifyNoMoreInteractions(handler);
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantPnoScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
similarity index 83%
rename from tests/wifitests/src/com/android/server/wifi/scanner/SupplicantPnoScannerTest.java
rename to tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
index 560710f..c63a9a0 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantPnoScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
@@ -22,16 +22,15 @@
 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.net.wifi.WifiScanner;
 import android.os.SystemClock;
+import android.os.test.TestLooper;
 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;
@@ -46,31 +45,29 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Set;
 
-
 /**
- * Unit tests for {@link com.android.server.wifi.scanner.SupplicantWifiScannerImpl.setPnoList}.
+ * Unit tests for {@link com.android.server.wifi.scanner.WificondScannerImpl.setPnoList}.
  */
 @SmallTest
-public class SupplicantPnoScannerTest {
+public class WificondPnoScannerTest {
 
     @Mock Context mContext;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
-    MockLooper mLooper;
+    TestLooper mLooper;
     @Mock WifiNative mWifiNative;
     MockResources mResources;
     @Mock Clock mClock;
-    SupplicantWifiScannerImpl mScanner;
+    WificondScannerImpl mScanner;
 
     @Before
     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();
 
@@ -78,11 +75,11 @@
         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());
     }
 
     /**
-     * Verify that the HW disconnected PNO scan triggers a supplicant PNO scan and invokes the
+     * Verify that the HW disconnected PNO scan triggers a wificond PNO scan and invokes the
      * OnPnoNetworkFound callback when the scan results are received.
      */
     @Test
@@ -121,8 +118,7 @@
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
         // Verify that the PNO scan was paused and single scan runs successfully
         expectSuccessfulSingleScanWithHwPnoEnabled(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>(),
-                scanResults);
+                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), scanResults);
         verifyNoMoreInteractions(eventHandler);
 
         order = inOrder(pnoEventHandler, mWifiNative);
@@ -130,7 +126,7 @@
         // alarm fires.
         assertTrue("dispatch pno monitor alarm",
                 mAlarmManager.dispatch(
-                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
+                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
         assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
         // Now verify that PNO scan is resumed successfully
         expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
@@ -191,16 +187,15 @@
         assertTrue(mScanner.startSingleScan(settings, eventHandler));
         // Verify that the PNO scan was paused and single scan runs successfully
         expectSuccessfulSingleScanWithHwPnoEnabled(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>(),
-                scanResults);
+                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), scanResults);
         verifyNoMoreInteractions(eventHandler);
 
         // Fail the PNO resume and check that the OnPnoScanFailed callback is invoked.
         order = inOrder(pnoEventHandler, mWifiNative);
-        when(mWifiNative.setPnoScan(true)).thenReturn(false);
+        when(mWifiNative.startPnoScan(any(WifiNative.PnoSettings.class))).thenReturn(false);
         assertTrue("dispatch pno monitor alarm",
                 mAlarmManager.dispatch(
-                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
+                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
         assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
         order.verify(pnoEventHandler).onPnoScanFailed();
         verifyNoMoreInteractions(pnoEventHandler);
@@ -209,7 +204,7 @@
         startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
         assertTrue("dispatch pno monitor alarm",
                 mAlarmManager.dispatch(
-                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
+                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
         assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
         expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
         verifyNoMoreInteractions(pnoEventHandler);
@@ -232,19 +227,19 @@
         startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
 
         // Fail the PNO stop.
-        when(mWifiNative.setPnoScan(false)).thenReturn(false);
+        when(mWifiNative.stopPnoScan()).thenReturn(false);
         assertTrue(mScanner.resetHwPnoList());
         assertTrue("dispatch pno monitor alarm",
                 mAlarmManager.dispatch(
-                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
+                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
         mLooper.dispatchAll();
-        verify(mWifiNative).setPnoScan(false);
+        verify(mWifiNative).stopPnoScan();
 
         // Add a new PNO scan request and ensure it runs successfully.
         startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
         assertTrue("dispatch pno monitor alarm",
                 mAlarmManager.dispatch(
-                        SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
+                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
         mLooper.dispatchAll();
         InOrder order = inOrder(pnoEventHandler, mWifiNative);
         ScanResults scanResults = createDummyScanResults(false);
@@ -275,24 +270,24 @@
         // Stop PNO now. This should trigger the debounce timer and not stop PNO.
         assertTrue(mScanner.resetHwPnoList());
         assertTrue(mAlarmManager.isPending(
-                SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
-        order.verify(mWifiNative, never()).setPnoScan(false);
+                WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
+        order.verify(mWifiNative, never()).stopPnoScan();
 
         // Now restart PNO scan with an extra network in settings.
         pnoSettings.networkList =
                 Arrays.copyOf(pnoSettings.networkList, pnoSettings.networkList.length + 1);
         pnoSettings.networkList[pnoSettings.networkList.length - 1] =
-                createDummyPnoNetwork("ssid_pno_new", 6, 6);
+                createDummyPnoNetwork("ssid_pno_new");
         startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
 
         // This should bypass the debounce timer and stop PNO scan immediately and then start
         // a new debounce timer for the start.
-        order.verify(mWifiNative).setPnoScan(false);
+        order.verify(mWifiNative).stopPnoScan();
 
         // Trigger the debounce timer and ensure we start PNO scan again.
-        mAlarmManager.dispatch(SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG);
+        mAlarmManager.dispatch(WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG);
         mLooper.dispatchAll();
-        order.verify(mWifiNative).setPnoScan(true);
+        order.verify(mWifiNative).startPnoScan(pnoSettings);
     }
 
     /**
@@ -318,15 +313,16 @@
         // Stop PNO now. This should trigger the debounce timer and not stop PNO.
         assertTrue(mScanner.resetHwPnoList());
         assertTrue(mAlarmManager.isPending(
-                SupplicantWifiScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
-        order.verify(mWifiNative, never()).setPnoScan(false);
+                WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
+        order.verify(mWifiNative, never()).stopPnoScan();
 
         // Now restart PNO scan with the same settings.
         startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
 
         // Trigger the debounce timer and ensure that we neither stop/start.
         mLooper.dispatchAll();
-        order.verify(mWifiNative, never()).setPnoScan(anyBoolean());
+        order.verify(mWifiNative, never()).startPnoScan(any(WifiNative.PnoSettings.class));
+        order.verify(mWifiNative, never()).stopPnoScan();
     }
 
     private void doSuccessfulSwPnoScanTest(boolean isConnectedPno) {
@@ -348,21 +344,19 @@
 
     private void createScannerWithHwPnoScanSupport() {
         mResources.setBoolean(R.bool.config_wifi_background_scan_support, true);
-        mScanner =
-                new SupplicantWifiScannerImpl(mContext, mWifiNative, mLooper.getLooper(), mClock);
+        mScanner = new WificondScannerImpl(mContext, mWifiNative, mWifiMonitor,
+                mLooper.getLooper(), mClock);
     }
 
     private void createScannerWithSwPnoScanSupport() {
         mResources.setBoolean(R.bool.config_wifi_background_scan_support, false);
-        mScanner =
-                new SupplicantWifiScannerImpl(mContext, mWifiNative, mLooper.getLooper(), mClock);
+        mScanner = new WificondScannerImpl(mContext, mWifiNative, mWifiMonitor,
+                mLooper.getLooper(), mClock);
     }
 
-    private WifiNative.PnoNetwork createDummyPnoNetwork(String ssid, int networkId, int priority) {
+    private WifiNative.PnoNetwork createDummyPnoNetwork(String ssid) {
         WifiNative.PnoNetwork pnoNetwork = new WifiNative.PnoNetwork();
         pnoNetwork.ssid = ssid;
-        pnoNetwork.networkId = networkId;
-        pnoNetwork.priority = priority;
         return pnoNetwork;
     }
 
@@ -370,8 +364,8 @@
         WifiNative.PnoSettings pnoSettings = new WifiNative.PnoSettings();
         pnoSettings.isConnected = isConnected;
         pnoSettings.networkList = new WifiNative.PnoNetwork[2];
-        pnoSettings.networkList[0] = createDummyPnoNetwork("ssid_pno_1", 1, 1);
-        pnoSettings.networkList[1] = createDummyPnoNetwork("ssid_pno_2", 2, 2);
+        pnoSettings.networkList[0] = createDummyPnoNetwork("ssid_pno_1");
+        pnoSettings.networkList[1] = createDummyPnoNetwork("ssid_pno_2");
         return pnoSettings;
     }
 
@@ -394,11 +388,10 @@
             WifiNative.PnoSettings pnoSettings, WifiNative.ScanEventHandler scanEventHandler,
             WifiNative.PnoEventHandler pnoEventHandler) {
         reset(mWifiNative);
-        when(mWifiNative.setNetworkVariable(anyInt(), anyString(), anyString())).thenReturn(true);
-        when(mWifiNative.enableNetworkWithoutConnect(anyInt())).thenReturn(true);
         // Scans succeed
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
-        when(mWifiNative.setPnoScan(anyBoolean())).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
+        when(mWifiNative.startPnoScan(any(WifiNative.PnoSettings.class))).thenReturn(true);
+        when(mWifiNative.stopPnoScan()).thenReturn(true);
 
         if (mScanner.isHwPnoSupported(pnoSettings.isConnected)) {
             // This should happen only for HW PNO scan
@@ -413,7 +406,7 @@
     private Set<Integer> expectedBandScanFreqs(int band) {
         ChannelCollection collection = mScanner.getChannelHelper().createChannelCollection();
         collection.addBand(band);
-        return collection.getSupplicantScanFreqs();
+        return collection.getScanFreqs();
     }
 
     /**
@@ -421,14 +414,8 @@
      */
     private void expectHwDisconnectedPnoScanStart(InOrder order,
             WifiNative.PnoSettings pnoSettings) {
-        for (int i = 0; i < pnoSettings.networkList.length; i++) {
-            WifiNative.PnoNetwork network = pnoSettings.networkList[i];
-            order.verify(mWifiNative).setNetworkVariable(network.networkId,
-                    WifiConfiguration.priorityVarName, Integer.toString(network.priority));
-            order.verify(mWifiNative).enableNetworkWithoutConnect(network.networkId);
-        }
         // Verify  HW PNO scan started
-        order.verify(mWifiNative).setPnoScan(true);
+        order.verify(mWifiNative).startPnoScan(any(WifiNative.PnoSettings.class));
     }
 
     /**
@@ -445,7 +432,8 @@
         when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
 
         // Notify scan has finished
-        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
+        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(),
+                                 WifiMonitor.PNO_SCAN_RESULTS_EVENT);
         assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
 
         order.verify(eventHandler).onPnoNetworkFound(scanResults.getRawScanResults());
@@ -457,11 +445,11 @@
      */
     private void expectSuccessfulSingleScanWithHwPnoEnabled(InOrder order,
             WifiNative.ScanEventHandler eventHandler, Set<Integer> expectedScanFreqs,
-            Set<Integer> expectedHiddenNetIds, ScanResults scanResults) {
+            ScanResults scanResults) {
         // Pause PNO scan first
-        order.verify(mWifiNative).setPnoScan(false);
+        order.verify(mWifiNative).stopPnoScan();
 
-        order.verify(mWifiNative).scan(eq(expectedScanFreqs), eq(expectedHiddenNetIds));
+        order.verify(mWifiNative).scan(eq(expectedScanFreqs), any(Set.class));
 
         when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
 
@@ -483,10 +471,10 @@
             WifiNative.ScanEventHandler eventHandler, ScanResults scanResults) {
 
         // Verify scan started
-        order.verify(mWifiNative).scan(any(Set.class), any(Set.class));
+        order.verify(mWifiNative).scan(any(), any(Set.class));
 
         // Make sure that HW PNO scan was not started
-        verify(mWifiNative, never()).setPnoScan(anyBoolean());
+        verify(mWifiNative, never()).startPnoScan(any(WifiNative.PnoSettings.class));
 
         // Setup scan results
         when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
@@ -498,7 +486,7 @@
         // Verify background scan results delivered
         order.verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
         WifiScanner.ScanData[] scanData = mScanner.getLatestBatchedScanResults(true);
-        WifiScanner.ScanData lastScanData = scanData[scanData.length -1];
+        WifiScanner.ScanData lastScanData = scanData[scanData.length - 1];
         assertScanDataEquals(scanResults.getScanData(), lastScanData);
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantWifiScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
similarity index 93%
rename from tests/wifitests/src/com/android/server/wifi/scanner/SupplicantWifiScannerTest.java
rename to tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
index 7bf5481..3a5f6b9 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/SupplicantWifiScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
@@ -38,18 +38,17 @@
 import org.mockito.InOrder;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.Set;
 
 /**
- * Unit tests for {@link com.android.server.wifi.scanner.SupplicantWifiScannerImpl}.
+ * Unit tests for {@link com.android.server.wifi.scanner.WificondScannerImpl}.
  */
 @SmallTest
-public class SupplicantWifiScannerTest extends BaseWifiScannerImplTest {
+public class WificondScannerTest extends BaseWifiScannerImplTest {
 
     @Before
     public void setup() throws Exception {
-        mScanner = new SupplicantWifiScannerImpl(mContext, mWifiNative,
+        mScanner = new WificondScannerImpl(mContext, mWifiNative, mWifiMonitor,
                 mLooper.getLooper(), mClock);
     }
 
@@ -319,7 +318,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // All scans fail
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(false);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(false);
 
         // Start scan
         mScanner.startBatchedScan(settings, eventHandler);
@@ -327,7 +326,7 @@
         assertBackgroundPeriodAlarmPending();
 
         expectFailedScanStart(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>());
+                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
 
         // Fire alarm to start next scan
         dispatchBackgroundPeriodAlarm();
@@ -335,7 +334,7 @@
         assertBackgroundPeriodAlarmPending();
 
         expectFailedScanStart(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>());
+                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
 
         verifyNoMoreInteractions(eventHandler);
     }
@@ -354,7 +353,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // All scan starts succeed
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // Start scan
         mScanner.startBatchedScan(settings, eventHandler);
@@ -362,7 +361,7 @@
         assertBackgroundPeriodAlarmPending();
 
         expectFailedEventScan(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>());
+                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
 
         // Fire alarm to start next scan
         dispatchBackgroundPeriodAlarm();
@@ -370,7 +369,7 @@
         assertBackgroundPeriodAlarmPending();
 
         expectFailedEventScan(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), new HashSet<Integer>());
+                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
 
         verifyNoMoreInteractions(eventHandler);
     }
@@ -401,7 +400,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // All scan starts succeed
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // Start scan
         mScanner.startBatchedScan(settings, eventHandler);
@@ -455,7 +454,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // All scan starts succeed
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // Start scan
         mScanner.startBatchedScan(settings, eventHandler);
@@ -528,7 +527,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // All scan starts succeed
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // Start scan
         mScanner.startBatchedScan(settings, eventHandler);
@@ -566,7 +565,7 @@
         InOrder order = inOrder(eventHandler, mWifiNative);
 
         // All scans succeed
-        when(mWifiNative.scan(any(Set.class), any(Set.class))).thenReturn(true);
+        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
 
         // Start scan
         mScanner.startBatchedScan(settings, eventHandler);
@@ -610,7 +609,7 @@
             }
         }
         expectSuccessfulBackgroundScan(order, eventHandler, period.getScanFreqs(),
-                new HashSet<Integer>(), nativeResults, scanDatas, fullResults, periodId);
+                nativeResults, scanDatas, fullResults, periodId);
     }
 
     /**
@@ -619,11 +618,11 @@
      */
     private void expectSuccessfulBackgroundScan(InOrder order,
             WifiNative.ScanEventHandler eventHandler, Set<Integer> scanFreqs,
-            Set<Integer> networkIds, ArrayList<ScanDetail> nativeResults,
+            ArrayList<ScanDetail> nativeResults,
             WifiScanner.ScanData[] expectedScanResults,
             ScanResult[] fullResults, int periodId) {
         // Verify scan started
-        order.verify(mWifiNative).scan(eq(scanFreqs), eq(networkIds));
+        order.verify(mWifiNative).scan(eq(scanFreqs), any(Set.class));
 
         // Setup scan results
         when(mWifiNative.getScanResults()).thenReturn(nativeResults);
@@ -647,17 +646,15 @@
     }
 
     private void expectFailedScanStart(InOrder order, WifiNative.ScanEventHandler eventHandler,
-            Set<Integer> scanFreqs, Set<Integer> networkIds) {
+            Set<Integer> scanFreqs) {
         // Verify scan started
-        order.verify(mWifiNative).scan(eq(scanFreqs), eq(networkIds));
-
-        // TODO: verify failure event
+        order.verify(mWifiNative).scan(eq(scanFreqs), any(Set.class));
     }
 
     private void expectFailedEventScan(InOrder order, WifiNative.ScanEventHandler eventHandler,
-            Set<Integer> scanFreqs, Set<Integer> networkIds) {
+            Set<Integer> scanFreqs) {
         // Verify scan started
-        order.verify(mWifiNative).scan(eq(scanFreqs), eq(networkIds));
+        order.verify(mWifiNative).scan(eq(scanFreqs), any(Set.class));
 
         // Notify scan has failed
         mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_FAILED_EVENT);
@@ -668,17 +665,17 @@
 
     private void assertBackgroundPeriodAlarmPending() {
         assertTrue("background period alarm not pending",
-                mAlarmManager.isPending(SupplicantWifiScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
+                mAlarmManager.isPending(WificondScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
     }
 
     private void assertBackgroundPeriodAlarmNotPending() {
         assertFalse("background period alarm is pending",
-                mAlarmManager.isPending(SupplicantWifiScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
+                mAlarmManager.isPending(WificondScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
     }
 
     private void dispatchBackgroundPeriodAlarm() {
         assertTrue("dispatch background period alarm",
-                mAlarmManager.dispatch(SupplicantWifiScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
+                mAlarmManager.dispatch(WificondScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
         mLooper.dispatchAll();
     }
 
@@ -691,7 +688,7 @@
 
             public final boolean result;
             public final boolean full;
-            private ReportType(boolean result, boolean full) {
+            ReportType(boolean result, boolean full) {
                 this.result = result;
                 this.full = full;
             }
@@ -700,12 +697,12 @@
         private final ScanResults[] mDeliveredResults;
         private final Set<Integer> mRequestedFreqs;
 
-        public ScanPeriod(ReportType reportType, ScanResults deliveredResult,
+        ScanPeriod(ReportType reportType, ScanResults deliveredResult,
                 Set<Integer> requestedFreqs) {
             this(reportType, new ScanResults[] {deliveredResult}, requestedFreqs);
         }
 
-        public ScanPeriod(ReportType reportType, ScanResults[] deliveredResults,
+        ScanPeriod(ReportType reportType, ScanResults[] deliveredResults,
                 Set<Integer> requestedFreqs) {
             mReportType = reportType;
             mDeliveredResults = deliveredResults;
diff --git a/tests/wifitests/src/com/android/server/wifi/util/BitMaskTest.java b/tests/wifitests/src/com/android/server/wifi/util/BitMaskTest.java
new file mode 100644
index 0000000..af7ad04
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/BitMaskTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.BitMask}.
+ */
+public class BitMaskTest {
+    /**
+     * Test that checkoff.testAndClear works as advertised
+     */
+    @Test
+    public void testBitMask() throws Exception {
+
+        BitMask checkoff = new BitMask(0x53);
+
+        Assert.assertTrue(checkoff.testAndClear(0x10)); // First time, bit should be there
+        Assert.assertFalse(checkoff.testAndClear(0x10)); // Second time, should be gone
+        Assert.assertFalse(checkoff.testAndClear(0x100)); // This bit was not set
+        Assert.assertEquals(0x43, checkoff.value); // These should be left
+    }
+}
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/InformationElementUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
index ef9e120..b9a8c31 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
@@ -18,10 +18,14 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.net.wifi.ScanResult.InformationElement;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.wifi.hotspot2.NetworkDetail;
+
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
@@ -239,7 +243,7 @@
     }
 
     /**
-     * Test Capabilities.buildCapabilities() with a RSN IE.
+     * Test Capabilities.generateCapabilitiesString() with a RSN IE.
      * Expect the function to return a string with the proper security information.
      */
     @Test
@@ -258,13 +262,42 @@
         BitSet beaconCap = new BitSet(16);
         beaconCap.set(4);
 
-        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
 
-        assertEquals("[WPA2-PSK]", result);
+        assertEquals("[WPA2-PSK-CCMP+TKIP]", result);
     }
 
     /**
-     * Test Capabilities.buildCapabilities() with a WPA type 1 IE.
+     * Test Capabilities.generateCapabilitiesString() with a RSN IE which is malformed.
+     * Expect the function to return a string with empty key management & pairswise cipher security
+     * information.
+     */
+    @Test
+    public void buildCapabilities_malformedRsnElement() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_RSN;
+        ie.bytes = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
+                (byte) 0xAC, (byte) 0x02, (byte) 0x02, (byte) 0x00,
+                (byte) 0x00, (byte) 0x0F, (byte) 0xAC };
+
+        InformationElement[] ies = new InformationElement[] { ie };
+
+        BitSet beaconCap = new BitSet(16);
+        beaconCap.set(4);
+
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals("[WPA2]", result);
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a WPA type 1 IE.
      * Expect the function to return a string with the proper security information.
      */
     @Test
@@ -283,14 +316,144 @@
 
         BitSet beaconCap = new BitSet(16);
         beaconCap.set(4);
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
 
-        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
-
-        assertEquals("[WPA-PSK]", result);
+        assertEquals("[WPA-PSK-CCMP+TKIP]", result);
     }
 
     /**
-     * Test Capabilities.buildCapabilities() with a vendor specific element which
+     * Test Capabilities.generateCapabilitiesString() with a WPA type 1 IE which is malformed.
+     * Expect the function to return a string with empty key management & pairswise cipher security
+     * information.
+     */
+    @Test
+    public void buildCapabilities_malformedWpa1Element() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_VSA;
+        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x01,
+                (byte) 0x01, (byte) 0x00 };
+
+        InformationElement[] ies = new InformationElement[] { ie };
+
+        BitSet beaconCap = new BitSet(16);
+        beaconCap.set(4);
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals("[WPA]", result);
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with both RSN and WPA1 IE.
+     * Expect the function to return a string with the proper security information.
+     */
+    @Test
+    public void buildCapabilities_rsnAndWpaElement() {
+        InformationElement ieRsn = new InformationElement();
+        ieRsn.id = InformationElement.EID_RSN;
+        ieRsn.bytes = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
+                                   (byte) 0xAC, (byte) 0x02, (byte) 0x02, (byte) 0x00,
+                                   (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
+                                   (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
+                                   (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
+                                   (byte) 0xAC, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
+
+        InformationElement ieWpa = new InformationElement();
+        ieWpa.id = InformationElement.EID_VSA;
+        ieWpa.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x01,
+                                   (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
+                                   (byte) 0xF2, (byte) 0x02, (byte) 0x02, (byte) 0x00,
+                                   (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x04,
+                                   (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x02,
+                                   (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
+                                   (byte) 0xF2, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
+
+        InformationElement[] ies = new InformationElement[] { ieWpa, ieRsn };
+
+        BitSet beaconCap = new BitSet(16);
+        beaconCap.set(4);
+
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals("[WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP]", result);
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with both RSN and WPA1 IE which are malformed.
+     * Expect the function to return a string with empty key management & pairswise cipher security
+     * information.
+     */
+    @Test
+    public void buildCapabilities_malformedRsnAndWpaElement() {
+        InformationElement ieRsn = new InformationElement();
+        ieRsn.id = InformationElement.EID_RSN;
+        ieRsn.bytes = new byte[] { (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F,
+                (byte) 0xAC, (byte) 0x02, (byte) 0x02 };
+
+        InformationElement ieWpa = new InformationElement();
+        ieWpa.id = InformationElement.EID_VSA;
+        ieWpa.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x01,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
+                (byte) 0xF2, (byte) 0x02, (byte) 0x02, (byte) 0x00,
+                (byte) 0x00, (byte) 0x50 };
+
+        InformationElement[] ies = new InformationElement[] { ieWpa, ieRsn };
+
+        BitSet beaconCap = new BitSet(16);
+        beaconCap.set(4);
+
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals("[WPA][WPA2]", result);
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with both WPS and WPA1 IE.
+     * Expect the function to return a string with the proper security information.
+     */
+    @Test
+    public void buildCapabilities_wpaAndWpsElement() {
+        InformationElement ieWpa = new InformationElement();
+        ieWpa.id = InformationElement.EID_VSA;
+        ieWpa.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x01,
+                                   (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
+                                   (byte) 0xF2, (byte) 0x02, (byte) 0x02, (byte) 0x00,
+                                   (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x04,
+                                   (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x02,
+                                   (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x50,
+                                   (byte) 0xF2, (byte) 0x02, (byte) 0x00, (byte) 0x00 };
+
+        InformationElement ieWps = new InformationElement();
+        ieWps.id = InformationElement.EID_VSA;
+        ieWps.bytes = new byte[] { (byte) 0x00, (byte) 0x50, (byte) 0xF2, (byte) 0x04 };
+
+        InformationElement[] ies = new InformationElement[] { ieWpa, ieWps };
+
+        BitSet beaconCap = new BitSet(16);
+        beaconCap.set(4);
+
+
+        InformationElementUtil.Capabilities capabilities =
+                 new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
+        assertEquals("[WPA-PSK-CCMP+TKIP][WPS]", result);
+    }
+
+    /**
+     * Test Capabilities.generateCapabilitiesString() with a vendor specific element which
      * is not WPA type 1. Beacon Capability Information field has the Privacy
      * bit set.
      *
@@ -309,13 +472,17 @@
         BitSet beaconCap = new BitSet(16);
         beaconCap.set(4);
 
-        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
 
         assertEquals("[WEP]", result);
     }
 
     /**
-     * Test Capabilities.buildCapabilities() with a vendor specific element which
+     * Test Capabilities.generateCapabilitiesString() with a vendor specific element which
      * is not WPA type 1. Beacon Capability Information field doesn't have the
      * Privacy bit set.
      *
@@ -334,13 +501,17 @@
         BitSet beaconCap = new BitSet(16);
         beaconCap.clear(4);
 
-        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
 
         assertEquals("", result);
     }
 
     /**
-     * Test Capabilities.buildCapabilities() with a vendor specific element which
+     * Test Capabilities.generateCapabilitiesString() with a vendor specific element which
      * is not WPA type 1. Beacon Capability Information field has the ESS bit set.
      *
      * Expect the function to return a string with [ESS] there.
@@ -358,13 +529,17 @@
         BitSet beaconCap = new BitSet(16);
         beaconCap.set(0);
 
-        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
 
         assertEquals("[ESS]", result);
     }
 
     /**
-     * Test Capabilities.buildCapabilities() with a vendor specific element which
+     * Test Capabilities.generateCapabilitiesString() with a vendor specific element which
      * is not WPA type 1. Beacon Capability Information field doesn't have the
      * ESS bit set.
      *
@@ -383,12 +558,71 @@
         BitSet beaconCap = new BitSet(16);
         beaconCap.clear(0);
 
-        String result = InformationElementUtil.Capabilities.buildCapabilities(ies, beaconCap);
+        InformationElementUtil.Capabilities capabilities =
+                new InformationElementUtil.Capabilities();
+        capabilities.from(ies, beaconCap);
+        String result = capabilities.generateCapabilitiesString();
+
 
         assertEquals("", result);
     }
 
     /**
+     * Verify the expectations when building an ExtendedCapabilites IE from data with no bits set.
+     * Both ExtendedCapabilities#isStrictUtf8() and ExtendedCapabilites#is80211McRTTResponder()
+     * should return false.
+     */
+    @Test
+    public void buildExtendedCapabilities_emptyBitSet() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_EXTENDED_CAPS;
+        ie.bytes = new byte[8];
+
+        InformationElementUtil.ExtendedCapabilities extendedCap =
+                new InformationElementUtil.ExtendedCapabilities();
+        extendedCap.from(ie);
+        assertFalse(extendedCap.isStrictUtf8());
+        assertFalse(extendedCap.is80211McRTTResponder());
+    }
+
+    /**
+     * Verify the expectations when building an ExtendedCapabilites IE from data with UTF-8 SSID
+     * bit set (bit 48).  ExtendedCapabilities#isStrictUtf8() should return true.
+     */
+    @Test
+    public void buildExtendedCapabilites_strictUtf8() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_EXTENDED_CAPS;
+        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                                (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00 };
+
+        InformationElementUtil.ExtendedCapabilities extendedCap =
+                new InformationElementUtil.ExtendedCapabilities();
+        extendedCap.from(ie);
+        assertTrue(extendedCap.isStrictUtf8());
+        assertFalse(extendedCap.is80211McRTTResponder());
+    }
+
+    /**
+     * Verify the expectations when building an ExtendedCapabilites IE from data with RTT Response
+     * Enable bit set (bit 70).  ExtendedCapabilities#is80211McRTTResponder() should return true.
+     */
+    @Test
+    public void buildExtendedCapabilites_80211McRTTResponder() {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_EXTENDED_CAPS;
+        ie.bytes = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                                (byte) 0x40 };
+
+        InformationElementUtil.ExtendedCapabilities extendedCap =
+                new InformationElementUtil.ExtendedCapabilities();
+        extendedCap.from(ie);
+        assertFalse(extendedCap.isStrictUtf8());
+        assertTrue(extendedCap.is80211McRTTResponder());
+    }
+
+    /**
      * Test a that a correctly formed TIM Information Element is decoded into a valid TIM element,
      * and the values are captured
      */
@@ -437,4 +671,86 @@
         trafficIndicationMap.from(ie);
         assertEquals(trafficIndicationMap.isValid(), false);
     }
+
+    /**
+     * Verify that the expected Roaming Consortium information element is parsed and retrieved
+     * from the list of IEs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getRoamingConsortiumIE() throws Exception {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_ROAMING_CONSORTIUM;
+        /**
+         * Roaming Consortium Format;
+         * | Number of OIs | OI#1 and OI#2 Lengths | OI #1 | OI #2 (optional) | OI #3 (optional) |
+         *        1                  1              variable     variable           variable
+         */
+        ie.bytes = new byte[] { (byte) 0x01 /* number of OIs */, (byte) 0x03 /* OI Length */,
+                                (byte) 0x11, (byte) 0x22, (byte) 0x33};
+        InformationElementUtil.RoamingConsortium roamingConsortium =
+                InformationElementUtil.getRoamingConsortiumIE(new InformationElement[] {ie});
+        assertEquals(1, roamingConsortium.anqpOICount);
+        assertEquals(1, roamingConsortium.roamingConsortiums.length);
+        assertEquals(0x112233, roamingConsortium.roamingConsortiums[0]);
+    }
+
+    /**
+     * Verify that the expected Hotspot 2.0 Vendor Specific information element is parsed and
+     * retrieved from the list of IEs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getHS2VendorSpecificIE() throws Exception {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_VSA;
+        /**
+         * Vendor Specific OI Format:
+         * | OI | Type | Hotspot Configuration | PPS MO ID (optional) | ANQP Domain ID (optional)
+         *    3    1              1                    2                        2
+         *
+         * With OI=0x506F9A and Type=0x10 for Hotspot 2.0
+         *
+         * The Format of Hotspot Configuration:
+         *        B0               B1                   B2             B3    B4              B7
+         * | DGAF Disabled | PPS MO ID Flag | ANQP Domain ID Flag | reserved | Release Number |
+         */
+        ie.bytes = new byte[] { (byte) 0x50, (byte) 0x6F, (byte) 0x9A, (byte) 0x10,
+                                (byte) 0x14 /* Hotspot Configuration */, (byte) 0x11, (byte) 0x22};
+        InformationElementUtil.Vsa vsa =
+                InformationElementUtil.getHS2VendorSpecificIE(new InformationElement[] {ie});
+        assertEquals(NetworkDetail.HSRelease.R2, vsa.hsRelease);
+        assertEquals(0x2211, vsa.anqpDomainID);
+    }
+
+    /**
+     * Verify that the expected Interworking information element is parsed and retrieved from the
+     * list of IEs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getInterworkingElementIE() throws Exception {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_INTERWORKING;
+        /**
+         * Interworking Format:
+         * | Access Network Option | Venue Info (optional) | HESSID (optional) |
+         *           1                       2                     6
+         *
+         * Access Network Option Format:
+         *
+         * B0                   B3    B4       B5    B6     B7
+         * | Access Network Type | Internet | ASRA | ESR | UESA |
+         */
+        ie.bytes = new byte[] { (byte) 0x10, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44,
+                                (byte) 0x55, (byte) 0x66 };
+        InformationElementUtil.Interworking interworking =
+                InformationElementUtil.getInterworkingIE(new InformationElement[] {ie});
+        assertTrue(interworking.internet);
+        assertEquals(NetworkDetail.Ant.Private, interworking.ant);
+        assertEquals(0x112233445566L, interworking.hessid);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/util/NativeUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/NativeUtilTest.java
new file mode 100644
index 0000000..3f51c5a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/NativeUtilTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.*;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.NativeUtil}.
+ */
+public class NativeUtilTest {
+    /**
+     * Test that parsing a typical colon-delimited MAC address works.
+     */
+    @Test
+    public void testMacAddressToByteArray() throws Exception {
+        assertArrayEquals(new byte[]{0x61, 0x52, 0x43, 0x34, 0x25, 0x16},
+                NativeUtil.macAddressToByteArray("61:52:43:34:25:16"));
+    }
+
+    /**
+     * Test that parsing an empty MAC address works.
+     */
+    @Test
+    public void testEmptyMacAddressToByteArray() throws Exception {
+        assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0},
+                NativeUtil.macAddressToByteArray(""));
+        assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0},
+                NativeUtil.macAddressToByteArray(null));
+    }
+
+    /**
+     * Test that conversion of byte array of mac address to typical colon-delimited MAC address
+     * works.
+     */
+    @Test
+    public void testByteArrayToMacAddress() throws Exception {
+        assertEquals("61:52:43:34:25:16",
+                NativeUtil.macAddressFromByteArray(new byte[]{0x61, 0x52, 0x43, 0x34, 0x25, 0x16}));
+    }
+
+    /**
+     * Test that parsing a typical colon-delimited MAC OUI address works.
+     */
+    @Test
+    public void testMacAddressOuiToByteArray() throws Exception {
+        assertArrayEquals(new byte[]{0x61, 0x52, 0x43},
+                NativeUtil.macAddressOuiToByteArray("61:52:43"));
+    }
+
+    /**
+     * Test that parsing a hex string to byte array works.
+     */
+    @Test
+    public void testHexStringToByteArray() throws Exception {
+        assertArrayEquals(new byte[]{0x45, 0x12, 0x23, 0x34},
+                NativeUtil.hexStringToByteArray("45122334"));
+    }
+
+    /**
+     * Test that conversion of byte array to hexstring works.
+     */
+    @Test
+    public void testHexStringFromByteArray() throws Exception {
+        assertEquals("45122334",
+                NativeUtil.hexStringFromByteArray(new byte[]{0x45, 0x12, 0x23, 0x34}));
+    }
+
+    /**
+     * Test that conversion of ssid bytes to quoted ASCII string ssid works.
+     */
+    @Test
+    public void testAsciiSsidDecode() throws Exception {
+        assertEquals(
+                new ArrayList<>(
+                        Arrays.asList((byte) 's', (byte) 's', (byte) 'i', (byte) 'd', (byte) '_',
+                                (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) '1',
+                                (byte) '2', (byte) '3')),
+                NativeUtil.decodeSsid("\"ssid_test123\""));
+    }
+
+    /**
+     * Test that conversion of ssid bytes to hex string ssid works.
+     */
+    @Test
+    public void testNonAsciiSsidDecode() throws Exception {
+        assertEquals(
+                new ArrayList<>(
+                        Arrays.asList((byte) 0xf5, (byte) 0xe4, (byte) 0xab, (byte) 0x78,
+                                (byte) 0xab, (byte) 0x34, (byte) 0x32, (byte) 0x43, (byte) 0x9a)),
+                NativeUtil.decodeSsid("f5e4ab78ab3432439a"));
+    }
+
+    /**
+     * Test that conversion of ssid bytes to quoted ASCII string ssid works.
+     */
+    @Test
+    public void testAsciiSsidEncode() throws Exception {
+        assertEquals(
+                "\"ssid_test123\"",
+                NativeUtil.encodeSsid(new ArrayList<>(
+                        Arrays.asList((byte) 's', (byte) 's', (byte) 'i', (byte) 'd', (byte) '_',
+                                (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) '1',
+                                (byte) '2', (byte) '3'))));
+    }
+
+    /**
+     * Test that conversion of byte array to hex string works when the byte array contains 0.
+     */
+    @Test
+    public void testNullCharInAsciiSsidEncode() throws Exception {
+        assertEquals(
+                "007369645f74657374313233",
+                NativeUtil.encodeSsid(new ArrayList<>(
+                        Arrays.asList((byte) 0x00, (byte) 's', (byte) 'i', (byte) 'd', (byte) '_',
+                                (byte) 't', (byte) 'e', (byte) 's', (byte) 't', (byte) '1',
+                                (byte) '2', (byte) '3'))));
+    }
+
+    /**
+     * Test that conversion of byte array to hex string ssid works.
+     */
+    @Test
+    public void testNonAsciiSsidEncode() throws Exception {
+        assertEquals(
+                "f5e4ab78ab3432439a",
+                NativeUtil.encodeSsid(new ArrayList<>(
+                        Arrays.asList((byte) 0xf5, (byte) 0xe4, (byte) 0xab, (byte) 0x78,
+                                (byte) 0xab, (byte) 0x34, (byte) 0x32, (byte) 0x43, (byte) 0x9a))));
+    }
+
+    /**
+     * Test that parsing of quoted SSID to byte array and vice versa works.
+     */
+    @Test
+    public void testSsidEncodeDecode() throws Exception {
+        String ssid = "\"ssid_test123\"";
+        assertEquals(ssid, NativeUtil.encodeSsid(NativeUtil.decodeSsid(ssid)));
+    }
+
+    /**
+     * Test that the enclosing quotes are removed properly.
+     */
+    @Test
+    public void testRemoveEnclosingQuotes() throws Exception {
+        assertEquals("abcdefgh", NativeUtil.removeEnclosingQuotes("\"abcdefgh\""));
+        assertEquals("abcdefgh", NativeUtil.removeEnclosingQuotes("abcdefgh"));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/ScanDetailUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/ScanDetailUtilTest.java
deleted file mode 100644
index 07d2521..0000000
--- a/tests/wifitests/src/com/android/server/wifi/util/ScanDetailUtilTest.java
+++ /dev/null
@@ -1,122 +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 static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.net.wifi.ScanResult;
-import android.net.wifi.ScanResult.InformationElement;
-import android.net.wifi.WifiSsid;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.wifi.ScanDetail;
-
-import org.junit.Test;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-/**
- * Unit tests for {@link com.android.server.wifi.util.ScanDetailUtil}.
- */
-@SmallTest
-public class ScanDetailUtilTest {
-
-    @Test
-    public void convertScanResult() {
-        final String ssid = "SOME SsId";
-
-        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
-                "ab:cd:01:ef:45:89", 1245, 0, "", -78, 2450, 1025, 22, 33, 20, 0, 0, true);
-
-        input.informationElements = new InformationElement[] {
-            createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
-        };
-
-        ScanDetail output = ScanDetailUtil.toScanDetail(input);
-
-        validateScanDetail(input, output);
-    }
-
-    @Test
-    public void convertScanResultWithAnqpLines() {
-        final String ssid = "SOME SsId";
-
-        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
-                "ab:cd:01:ef:45:89", 1245, 0, "some caps", -78, 2450, 1025, 22, 33, 20, 0, 0, true);
-
-        input.informationElements = new InformationElement[] {
-            createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
-        };
-        input.anqpLines = Arrays.asList("LINE 1", "line 2", "Line 3");
-
-        ScanDetail output = ScanDetailUtil.toScanDetail(input);
-
-        validateScanDetail(input, output);
-    }
-
-    @Test
-    public void convertScanResultWithoutWifiSsid() {
-        final String ssid = "Another SSid";
-        ScanResult input = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "other caps",
-                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
-        input.informationElements = new InformationElement[] {
-            createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
-        };
-
-        ScanDetail output = ScanDetailUtil.toScanDetail(input);
-
-        validateScanDetail(input, output);
-    }
-
-    private static InformationElement createIE(int id, byte[] bytes) {
-        InformationElement ie = new InformationElement();
-        ie.id = id;
-        ie.bytes = bytes;
-        return ie;
-    }
-
-    private static void validateScanDetail(ScanResult input, ScanDetail output) {
-        assertNotNull("NetworkDetail was null", output.getNetworkDetail());
-        assertNotNull("ScanResult was null", output.getScanResult());
-        assertEquals("NetworkDetail SSID", input.SSID,
-                output.getNetworkDetail().getSSID());
-        assertEquals("ScanResult SSID", input.SSID,
-                output.getScanResult().SSID);
-        assertEquals("ScanResult wifiSsid", input.wifiSsid,
-                output.getScanResult().wifiSsid);
-        assertEquals("getSSID", input.SSID, output.getSSID());
-        assertEquals("NetworkDetail BSSID", input.BSSID,
-                output.getNetworkDetail().getBSSIDString());
-        assertEquals("getBSSIDString", input.BSSID, output.getBSSIDString());
-        assertEquals("ScanResult frequency", input.frequency,
-                output.getScanResult().frequency);
-        assertEquals("ScanResult level", input.level,
-                output.getScanResult().level);
-        assertEquals("ScanResult capabilities", input.capabilities,
-                output.getScanResult().capabilities);
-        assertEquals("ScanResult timestamp", input.timestamp,
-                output.getScanResult().timestamp);
-        assertArrayEquals("ScanResult information elements", input.informationElements,
-                output.getScanResult().informationElements);
-        assertEquals("ScanResult anqp lines", input.anqpLines,
-                output.getScanResult().anqpLines);
-    }
-
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
new file mode 100644
index 0000000..7b03698
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.*;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.ScanResult.InformationElement;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiSsid;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.ScanDetail;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.ScanResultUtil}.
+ */
+@SmallTest
+public class ScanResultUtilTest {
+
+    @Test
+    public void convertScanResult() {
+        final String ssid = "SOME SsId";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, "", -78, 2450, 1025, 22, 33, 20, 0, 0, true);
+
+        input.informationElements = new InformationElement[] {
+            createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        ScanDetail output = ScanResultUtil.toScanDetail(input);
+
+        validateScanDetail(input, output);
+    }
+
+    @Test
+    public void convertScanResultWithAnqpLines() {
+        final String ssid = "SOME SsId";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, "some caps", -78, 2450, 1025, 22, 33, 20, 0, 0, true);
+
+        input.informationElements = new InformationElement[] {
+            createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+        input.anqpLines = Arrays.asList("LINE 1", "line 2", "Line 3");
+
+        ScanDetail output = ScanResultUtil.toScanDetail(input);
+
+        validateScanDetail(input, output);
+    }
+
+    @Test
+    public void convertScanResultWithoutWifiSsid() {
+        final String ssid = "Another SSid";
+        ScanResult input = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "other caps",
+                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
+        input.informationElements = new InformationElement[] {
+            createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        ScanDetail output = ScanResultUtil.toScanDetail(input);
+
+        validateScanDetail(input, output);
+    }
+
+    @Test
+    public void testScanResultMatchingWithNetwork() {
+        final String ssid = "Another SSid";
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = ScanResultUtil.createQuotedSSID(ssid);
+        ScanResult scanResult = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "",
+                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        scanResult.capabilities = "";
+        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        config.wepKeys[0] = "45592364648547";
+        scanResult.capabilities = "WEP";
+        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        scanResult.capabilities = "PSK";
+        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
+
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        scanResult.capabilities = "EAP";
+        assertTrue(ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, config));
+    }
+
+    @Test
+    public void testNetworkCreationFromScanResult() {
+        final String ssid = "Another SSid";
+        ScanResult scanResult = new ScanResult(ssid, "ab:cd:01:ef:45:89", 1245, 0, "",
+                -78, 2450, 1025, 22, 33, 20, 0, 0, true);
+        WifiConfiguration config;
+
+        scanResult.capabilities = "";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE));
+
+        scanResult.capabilities = "WEP";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE));
+        assertTrue(config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN));
+        assertTrue(config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.SHARED));
+
+        scanResult.capabilities = "PSK";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK));
+
+        scanResult.capabilities = "EAP";
+        config = ScanResultUtil.createNetworkFromScanResult(scanResult);
+        assertEquals(config.SSID, ScanResultUtil.createQuotedSSID(ssid));
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+    }
+
+    private static InformationElement createIE(int id, byte[] bytes) {
+        InformationElement ie = new InformationElement();
+        ie.id = id;
+        ie.bytes = bytes;
+        return ie;
+    }
+
+    private static void validateScanDetail(ScanResult input, ScanDetail output) {
+        assertNotNull("NetworkDetail was null", output.getNetworkDetail());
+        assertNotNull("ScanResult was null", output.getScanResult());
+        assertEquals("NetworkDetail SSID", input.SSID,
+                output.getNetworkDetail().getSSID());
+        assertEquals("ScanResult SSID", input.SSID,
+                output.getScanResult().SSID);
+        assertEquals("ScanResult wifiSsid", input.wifiSsid,
+                output.getScanResult().wifiSsid);
+        assertEquals("getSSID", input.SSID, output.getSSID());
+        assertEquals("NetworkDetail BSSID", input.BSSID,
+                output.getNetworkDetail().getBSSIDString());
+        assertEquals("getBSSIDString", input.BSSID, output.getBSSIDString());
+        assertEquals("ScanResult frequency", input.frequency,
+                output.getScanResult().frequency);
+        assertEquals("ScanResult level", input.level,
+                output.getScanResult().level);
+        assertEquals("ScanResult capabilities", input.capabilities,
+                output.getScanResult().capabilities);
+        assertEquals("ScanResult timestamp", input.timestamp,
+                output.getScanResult().timestamp);
+        assertArrayEquals("ScanResult information elements", input.informationElements,
+                output.getScanResult().informationElements);
+        assertEquals("ScanResult anqp lines", input.anqpLines,
+                output.getScanResult().anqpLines);
+    }
+
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/TelephonyUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/TelephonyUtilTest.java
new file mode 100644
index 0000000..240096a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/TelephonyUtilTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import com.android.server.wifi.WifiConfigurationTestUtil;
+import com.android.server.wifi.util.TelephonyUtil.SimAuthRequestData;
+import com.android.server.wifi.util.TelephonyUtil.SimAuthResponseData;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.TelephonyUtil}.
+ */
+@SmallTest
+public class TelephonyUtilTest {
+    @Test
+    public void getSimIdentityEapSim() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getSubscriberId()).thenReturn("3214561234567890");
+        when(tm.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(tm.getSimOperator()).thenReturn("321456");
+        assertEquals("13214561234567890@wlan.mnc456.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE)));
+        assertEquals("13214561234567890@wlan.mnc456.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.SIM)));
+    }
+
+    @Test
+    public void getSimIdentityEapAka() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getSubscriberId()).thenReturn("3214561234567890");
+        when(tm.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(tm.getSimOperator()).thenReturn("321456");
+        assertEquals("03214561234567890@wlan.mnc456.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.AKA, WifiEnterpriseConfig.Phase2.NONE)));
+        assertEquals("03214561234567890@wlan.mnc456.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.AKA)));
+    }
+
+    @Test
+    public void getSimIdentityEapAkaPrime() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getSubscriberId()).thenReturn("3214561234567890");
+        when(tm.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(tm.getSimOperator()).thenReturn("321456");
+        assertEquals("63214561234567890@wlan.mnc456.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.AKA_PRIME, WifiEnterpriseConfig.Phase2.NONE)));
+        assertEquals("63214561234567890@wlan.mnc456.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.AKA_PRIME)));
+    }
+
+    @Test
+    public void getSimIdentity2DigitMnc() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getSubscriberId()).thenReturn("321560123456789");
+        when(tm.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(tm.getSimOperator()).thenReturn("32156");
+        assertEquals("1321560123456789@wlan.mnc056.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE)));
+    }
+
+    @Test
+    public void getSimIdentityUnknownMccMnc() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getSubscriberId()).thenReturn("3214560123456789");
+        when(tm.getSimState()).thenReturn(TelephonyManager.SIM_STATE_UNKNOWN);
+        when(tm.getSimOperator()).thenReturn(null);
+        assertEquals("13214560123456789@wlan.mnc456.mcc321.3gppnetwork.org",
+                TelephonyUtil.getSimIdentity(tm, WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE)));
+    }
+
+    @Test
+    public void getSimIdentityWithNoTelephonyManager() {
+        assertEquals(null, TelephonyUtil.getSimIdentity(null,
+                WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE)));
+    }
+
+    @Test
+    public void getSimIdentityNonTelephonyConfig() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getSubscriberId()).thenReturn("321560123456789");
+        when(tm.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        when(tm.getSimOperator()).thenReturn("32156");
+        assertEquals(null, TelephonyUtil.getSimIdentity(tm,
+                WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.TTLS, WifiEnterpriseConfig.Phase2.SIM)));
+        assertEquals(null, TelephonyUtil.getSimIdentity(tm,
+                WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.MSCHAPV2)));
+        assertEquals(null, TelephonyUtil.getSimIdentity(tm,
+                WifiConfigurationTestUtil.createEapNetwork(
+                        WifiEnterpriseConfig.Eap.TLS, WifiEnterpriseConfig.Phase2.NONE)));
+        assertEquals(null, TelephonyUtil.getSimIdentity(tm, new WifiConfiguration()));
+    }
+
+    @Test
+    public void isSimConfig() {
+        assertFalse(TelephonyUtil.isSimConfig(null));
+        assertFalse(TelephonyUtil.isSimConfig(new WifiConfiguration()));
+        assertFalse(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createOpenNetwork()));
+        assertFalse(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createWepNetwork()));
+        assertFalse(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createPskNetwork()));
+        assertFalse(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.TTLS, WifiEnterpriseConfig.Phase2.SIM)));
+        assertFalse(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.TLS, WifiEnterpriseConfig.Phase2.NONE)));
+        assertFalse(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.MSCHAPV2)));
+        assertTrue(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE)));
+        assertTrue(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.AKA, WifiEnterpriseConfig.Phase2.NONE)));
+        assertTrue(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.AKA_PRIME, WifiEnterpriseConfig.Phase2.NONE)));
+        assertTrue(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.SIM)));
+        assertTrue(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.AKA)));
+        assertTrue(TelephonyUtil.isSimConfig(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.PEAP, WifiEnterpriseConfig.Phase2.AKA_PRIME)));
+    }
+
+    /**
+     * Produce a base64 encoded length byte + data.
+     */
+    private static String createSimChallengeRequest(byte[] challengeValue) {
+        byte[] challengeLengthAndValue = new byte[challengeValue.length + 1];
+        challengeLengthAndValue[0] = (byte) challengeValue.length;
+        for (int i = 0; i < challengeValue.length; ++i) {
+            challengeLengthAndValue[i + 1] = challengeValue[i];
+        }
+        return Base64.encodeToString(challengeLengthAndValue, android.util.Base64.NO_WRAP);
+    }
+
+    /**
+     * Produce a base64 encoded sres length byte + sres + kc length byte + kc.
+     */
+    private static String createGsmSimAuthResponse(byte[] sresValue, byte[] kcValue) {
+        int overallLength = sresValue.length + kcValue.length + 2;
+        byte[] result = new byte[sresValue.length + kcValue.length + 2];
+        int idx = 0;
+        result[idx++] = (byte) sresValue.length;
+        for (int i = 0; i < sresValue.length; ++i) {
+            result[idx++] = sresValue[i];
+        }
+        result[idx++] = (byte) kcValue.length;
+        for (int i = 0; i < kcValue.length; ++i) {
+            result[idx++] = kcValue[i];
+        }
+        return Base64.encodeToString(result, Base64.NO_WRAP);
+    }
+
+    @Test
+    public void getGsmSimAuthResponseInvalidRequest() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        final String[] invalidRequests = { null, "", "XXXX" };
+        assertEquals("", TelephonyUtil.getGsmSimAuthResponse(invalidRequests, tm));
+    }
+
+    @Test
+    public void getGsmSimAuthResponseFailedSimResponse() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        final String[] failedRequests = { "5E5F" };
+        when(tm.getIccAuthentication(anyInt(), anyInt(),
+                eq(createSimChallengeRequest(new byte[] { 0x5e, 0x5f })))).thenReturn(null);
+
+        assertEquals(null, TelephonyUtil.getGsmSimAuthResponse(failedRequests, tm));
+    }
+
+    @Test
+    public void getGsmSimAuthResponseUsim() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM,
+                        createSimChallengeRequest(new byte[] { 0x1b, 0x2b })))
+                .thenReturn(createGsmSimAuthResponse(new byte[] { 0x1D, 0x2C },
+                                new byte[] { 0x3B, 0x4A }));
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM,
+                        createSimChallengeRequest(new byte[] { 0x01, 0x22 })))
+                .thenReturn(createGsmSimAuthResponse(new byte[] { 0x11, 0x11 },
+                                new byte[] { 0x12, 0x34 }));
+
+        assertEquals(":3b4a:1d2c:1234:1111", TelephonyUtil.getGsmSimAuthResponse(
+                        new String[] { "1B2B", "0122" }, tm));
+    }
+
+    @Test
+    public void getGsmSimAuthResponseSimpleSim() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM,
+                        createSimChallengeRequest(new byte[] { 0x1a, 0x2b })))
+                .thenReturn(null);
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM,
+                        createSimChallengeRequest(new byte[] { 0x1a, 0x2b })))
+                .thenReturn(createGsmSimAuthResponse(new byte[] { 0x1D, 0x2C },
+                                new byte[] { 0x3B, 0x4A }));
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM,
+                        createSimChallengeRequest(new byte[] { 0x01, 0x23 })))
+                .thenReturn(null);
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM,
+                        createSimChallengeRequest(new byte[] { 0x01, 0x23 })))
+                .thenReturn(createGsmSimAuthResponse(new byte[] { 0x33, 0x22 },
+                                new byte[] { 0x11, 0x00 }));
+
+        assertEquals(":3b4a:1d2c:1100:3322", TelephonyUtil.getGsmSimAuthResponse(
+                        new String[] { "1A2B", "0123" }, tm));
+    }
+
+    /**
+     * Produce a base64 encoded tag + res length byte + res + ck length byte + ck + ik length byte +
+     * ik.
+     */
+    private static String create3GSimAuthUmtsAuthResponse(byte[] res, byte[] ck, byte[] ik) {
+        byte[] result = new byte[res.length + ck.length + ik.length + 4];
+        int idx = 0;
+        result[idx++] = (byte) 0xdb;
+        result[idx++] = (byte) res.length;
+        for (int i = 0; i < res.length; ++i) {
+            result[idx++] = res[i];
+        }
+        result[idx++] = (byte) ck.length;
+        for (int i = 0; i < ck.length; ++i) {
+            result[idx++] = ck[i];
+        }
+        result[idx++] = (byte) ik.length;
+        for (int i = 0; i < ik.length; ++i) {
+            result[idx++] = ik[i];
+        }
+        return Base64.encodeToString(result, Base64.NO_WRAP);
+    }
+
+    private static String create3GSimAuthUmtsAutsResponse(byte[] auts) {
+        byte[] result = new byte[auts.length + 2];
+        int idx = 0;
+        result[idx++] = (byte) 0xdc;
+        result[idx++] = (byte) auts.length;
+        for (int i = 0; i < auts.length; ++i) {
+            result[idx++] = auts[i];
+        }
+        return Base64.encodeToString(result, Base64.NO_WRAP);
+    }
+
+    @Test
+    public void get3GAuthResponseInvalidRequest() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+        assertEquals(null, TelephonyUtil.get3GAuthResponse(
+                        new SimAuthRequestData(0, 0, "SSID", new String[] {"0123"}), tm));
+        assertEquals(null, TelephonyUtil.get3GAuthResponse(
+                        new SimAuthRequestData(0, 0, "SSID", new String[] {"xyz2", "1234"}), tm));
+        verifyNoMoreInteractions(tm);
+    }
+
+    @Test
+    public void get3GAuthResponseNullIccAuthentication() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, "AgEjAkVn")).thenReturn(null);
+
+        SimAuthResponseData response = TelephonyUtil.get3GAuthResponse(
+                new SimAuthRequestData(0, 0, "SSID", new String[] {"0123", "4567"}), tm);
+        assertNull(response);
+    }
+
+    @Test
+    public void get3GAuthResponseIccAuthenticationTooShort() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, "AgEjAkVn"))
+                .thenReturn(Base64.encodeToString(new byte[] {(byte) 0xdc}, Base64.NO_WRAP));
+
+        SimAuthResponseData response = TelephonyUtil.get3GAuthResponse(
+                new SimAuthRequestData(0, 0, "SSID", new String[] {"0123", "4567"}), tm);
+        assertNull(response);
+    }
+
+    @Test
+    public void get3GAuthResponseBadTag() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, "AgEjAkVn"))
+                .thenReturn(Base64.encodeToString(new byte[] {0x31, 0x1, 0x2, 0x3, 0x4},
+                                Base64.NO_WRAP));
+
+        SimAuthResponseData response = TelephonyUtil.get3GAuthResponse(
+                new SimAuthRequestData(0, 0, "SSID", new String[] {"0123", "4567"}), tm);
+        assertNull(response);
+    }
+
+    @Test
+    public void get3GAuthResponseUmtsAuth() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, "AgEjAkVn"))
+                .thenReturn(create3GSimAuthUmtsAuthResponse(new byte[] {0x11, 0x12},
+                                new byte[] {0x21, 0x22, 0x23}, new byte[] {0x31}));
+
+        SimAuthResponseData response = TelephonyUtil.get3GAuthResponse(
+                new SimAuthRequestData(0, 0, "SSID", new String[] {"0123", "4567"}), tm);
+        assertNotNull(response);
+        assertEquals("UMTS-AUTH", response.type);
+        assertEquals(":31:212223:1112", response.response);
+    }
+
+    @Test
+    public void get3GAuthResponseUmtsAuts() {
+        TelephonyManager tm = mock(TelephonyManager.class);
+
+        when(tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, "AgEjAkVn"))
+                .thenReturn(create3GSimAuthUmtsAutsResponse(new byte[] {0x22, 0x33}));
+
+        SimAuthResponseData response = TelephonyUtil.get3GAuthResponse(
+                new SimAuthRequestData(0, 0, "SSID", new String[] {"0123", "4567"}), tm);
+        assertNotNull(response);
+        assertEquals("UMTS-AUTS", response.type);
+        assertEquals(":2233", response.response);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/WifiHandlerTest.java b/tests/wifitests/src/com/android/server/wifi/util/WifiHandlerTest.java
new file mode 100644
index 0000000..220e6d7
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/WifiHandlerTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.os.Looper;
+import android.os.Message;
+import android.os.test.TestLooper;
+
+import com.android.server.wifi.FakeWifiLog;
+
+import static org.mockito.Matchers.contains;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+/** Unit tests for {@link WifiHandler}. */
+@RunWith(JUnit4.class)
+public class WifiHandlerTest {
+    private static final String TAG = "WifiHandlerTest";
+    private WifiHandler mCodeUnderTest;
+    @Spy FakeWifiLog mWifiLog;
+    TestLooper mLooper;
+
+    private class WifiHandlerTestClass extends WifiHandler {
+        WifiHandlerTestClass(String tag, Looper looper) {
+            super(tag, looper);
+            super.setWifiLog(mWifiLog);
+        }
+    }
+
+    @Before public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLooper = new TestLooper();
+        mCodeUnderTest = new WifiHandlerTestClass(TAG, mLooper.getLooper());
+    }
+
+    @Test public void testHandleMessage() {
+        Message msg = Message.obtain();
+        msg.what = 0;
+        msg.sendingUid = 0;
+        mCodeUnderTest.handleMessage(msg);
+        verify(mWifiLog).trace(contains("message"));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
new file mode 100644
index 0000000..308e267
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
@@ -0,0 +1,538 @@
+/*
+ * 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.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.net.NetworkScoreManager;
+import android.os.Build;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.server.wifi.BinderUtil;
+import com.android.server.wifi.FakeWifiLog;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiSettingsStore;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+/** Unit tests for {@link WifiPermissionsUtil}. */
+@RunWith(JUnit4.class)
+public class WifiPermissionsUtilTest {
+    public static final String TAG = "WifiPermissionsUtilTest";
+
+    // Mock objects for testing
+    @Mock private WifiPermissionsWrapper mMockPermissionsWrapper;
+    @Mock private Context mMockContext;
+    @Mock private PackageManager mMockPkgMgr;
+    @Mock private ApplicationInfo mMockApplInfo;
+    @Mock private AppOpsManager mMockAppOps;
+    @Mock private UserInfo mMockUserInfo;
+    @Mock private UserManager mMockUserManager;
+    @Mock private WifiSettingsStore mMockWifiSettingsStore;
+    @Mock private ContentResolver mMockContentResolver;
+    @Mock private NetworkScoreManager mNetworkScoreManager;
+    @Mock private WifiInjector mWifiInjector;
+    @Spy private FakeWifiLog mWifiLog;
+
+    private static final String TEST_PACKAGE_NAME = "com.google.somePackage";
+    private static final String INVALID_PACKAGE  = "BAD_PACKAGE";
+    private static final int MANAGED_PROFILE_UID = 1100000;
+    private static final int OTHER_USER_UID = 1200000;
+
+    private final int mCallingUser = UserHandle.USER_CURRENT_OR_SELF;
+    private final String mMacAddressPermission = "android.permission.PEERS_MAC_ADDRESS";
+    private final String mInteractAcrossUsersFullPermission =
+            "android.permission.INTERACT_ACROSS_USERS_FULL";
+    private final String mManifestStringCoarse =
+            Manifest.permission.ACCESS_COARSE_LOCATION;
+
+    // Test variables
+    private int mWifiScanAllowApps;
+    private int mUid;
+    private int mCoarseLocationPermission;
+    private int mAllowCoarseLocationApps;
+    private String mPkgNameOfTopActivity;
+    private int mCurrentUser;
+    private int mLocationModeSetting;
+    private boolean mThrowSecurityException;
+    private int mTargetVersion;
+    private boolean mActiveNwScorer;
+    private Answer<Integer> mReturnPermission;
+    private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
+
+    /**
+    * Set up Mockito tests
+    */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        initTestVars();
+    }
+
+    private void setupTestCase() throws Exception {
+        setupMocks();
+        setupMockInterface();
+    }
+
+    /**
+     * Verify we return true when the UID does have the override config permission
+     */
+    @Test
+    public void testCheckConfigOverridePermissionApproved() throws Exception {
+        mUid = MANAGED_PROFILE_UID;  // do not really care about this value
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        when(mMockPermissionsWrapper.getOverrideWifiConfigPermission(anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        assertTrue(codeUnderTest.checkConfigOverridePermission(mUid));
+    }
+
+    /**
+     * Verify we return false when the UID does not have the override config permission.
+     */
+    @Test
+    public void testCheckConfigOverridePermissionDenied() throws Exception {
+        mUid = OTHER_USER_UID;  // do not really care about this value
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        when(mMockPermissionsWrapper.getOverrideWifiConfigPermission(anyInt()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        assertFalse(codeUnderTest.checkConfigOverridePermission(mUid));
+    }
+
+    /**
+     * Verify we return false when the override config permission check throws a RemoteException.
+     */
+    @Test
+    public void testCheckConfigOverridePermissionWithException() throws Exception {
+        mUid = OTHER_USER_UID;  // do not really care about this value
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        doThrow(new RemoteException("Failed to check permissions for " + mUid))
+                .when(mMockPermissionsWrapper).getOverrideWifiConfigPermission(mUid);
+        assertFalse(codeUnderTest.checkConfigOverridePermission(mUid));
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Caller can read peers mac address
+     *                    This App has permission to request WIFI_SCAN
+     *                    User is current
+     * Validate result is true
+     * - User has all the permissions
+     */
+    @Test
+    public void testCanReadPeersMacAddressCurrentUserAndAllPermissions() throws Exception {
+        boolean output = false;
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, true);
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Caller can read peers mac address
+     *                    This App has permission to request WIFI_SCAN
+     *                    User profile is current
+     * Validate result is true
+     * - User has all the permissions
+     */
+    @Test
+    public void testCanReadPeersMacAddressCurrentProfileAndAllPermissions() throws Exception {
+        boolean output = false;
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mMockUserInfo.id = mCallingUser;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, true);
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Caller can read peers mac address
+     * Validate result is false
+     * - This App doesn't have permission to request Wifi Scan
+     */
+    @Test
+    public void testCannotAccessScanResult_AppNotAllowed() throws Exception {
+        boolean output = true;
+        mThrowSecurityException = false;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, false);
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Caller can read peers mac address
+     *                    This App has permission to request WIFI_SCAN
+     *                    User or profile is not current but the uid has
+     *                    permission to INTERACT_ACROSS_USERS_FULL
+     * Validate result is true
+     * - User has all the permissions
+     */
+    @Test
+    public void testCanAccessScanResults_UserOrProfileNotCurrent() throws Exception {
+        boolean output = false;
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, true);
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Caller can read peers mac address
+     *                    This App has permission to request WIFI_SCAN
+     *                    User or profile is not Current
+     * Validate result is false
+     * - Calling uid doesn't have INTERACT_ACROSS_USERS_FULL permission
+     */
+    @Test
+    public void testCannotAccessScanResults_NoInteractAcrossUsersFullPermission() throws Exception {
+        boolean output = true;
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, false);
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Caller is active network scorer
+     *                    This App has permission to request WIFI_SCAN
+     *                    User is current
+     * Validate result is true
+     */
+    @Test
+    public void testCanAccessScanResults_CallerIsActiveNwScorer() throws Exception {
+        boolean output = false;
+        mThrowSecurityException = false;
+        mActiveNwScorer = true;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, true);
+    }
+
+    /**
+     * Test case Setting: Package is valid
+     *                    Legacy App
+     *                    Foreground
+     *                    This App has permission to request WIFI_SCAN
+     *                    User is current
+     *  Validate result is true - has all permissions
+     */
+    @Test
+    public void testLegacyForegroundAppAndAllPermissions() throws Exception {
+        boolean output = false;
+        mThrowSecurityException = false;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.GINGERBREAD;
+        mPkgNameOfTopActivity = TEST_PACKAGE_NAME;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mUid = MANAGED_PROFILE_UID;
+        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, true);
+    }
+
+    /**
+     * Test case Setting: Package is valid
+     *                    Legacy App
+     *                    Location Mode Enabled
+     *                    Coarse Location Access
+     *                    This App has permission to request WIFI_SCAN
+     *                    User profile is current
+     *  Validate result is true - has all permissions
+     */
+    @Test
+    public void testLegacyAppHasLocationAndAllPermissions() throws Exception {
+        boolean output = false;
+        mThrowSecurityException = false;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.GINGERBREAD;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mUid = MANAGED_PROFILE_UID;
+        mMockUserInfo.id = mCallingUser;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, true);
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Location Mode Enabled
+     * Validate result is false
+     * - Doesn't have Peer Mac Address read permission
+     * - Uid is not an active network scorer
+     * - Location Mode is enabled but the uid
+     * - doesn't have Coarse Location Access
+     * - which implies No Location Permission
+     */
+    @Test
+    public void testCannotAccessScanResults_NoCoarseLocationPermission() throws Exception {
+        boolean output = true;
+        mThrowSecurityException = false;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+        assertEquals(output, false);
+    }
+
+    /**
+     * Test case setting: Invalid Package
+     * Expect a securityException
+     */
+    @Test (expected = SecurityException.class)
+    public void testInvalidPackage() throws Exception {
+        boolean output = false;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        try {
+            output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
+        } catch (SecurityException e) {
+            throw e;
+        }
+    }
+
+    /**
+     * Test case setting: caller does have Location permission.
+     * A SecurityException should not be thrown.
+     */
+    @Test
+    public void testEnforceLocationPermission() throws Exception {
+        mThrowSecurityException = false;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.GINGERBREAD;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mUid = MANAGED_PROFILE_UID;
+        mMockUserInfo.id = mCallingUser;
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        codeUnderTest.enforceLocationPermission(TEST_PACKAGE_NAME, mUid);
+    }
+
+    /**
+     * Test case setting: caller does not have Location permission.
+     * Expect a SecurityException
+     */
+    @Test(expected = SecurityException.class)
+    public void testEnforceLocationPermissionExpectSecurityException() throws Exception {
+        setupTestCase();
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
+        codeUnderTest.enforceLocationPermission(TEST_PACKAGE_NAME, mUid);
+    }
+
+    private Answer<Integer> createPermissionAnswer() {
+        return new Answer<Integer>() {
+            @Override
+            public Integer answer(InvocationOnMock invocation) {
+                int myUid = (int) invocation.getArguments()[1];
+                String myPermission = (String) invocation.getArguments()[0];
+                mPermissionsList.get(myPermission);
+                if (mPermissionsList.containsKey(myPermission)) {
+                    int uid = mPermissionsList.get(myPermission);
+                    if (myUid == uid) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
+                }
+                return PackageManager.PERMISSION_DENIED;
+            }
+        };
+    }
+
+    private void setupMocks() throws Exception {
+        when(mMockPkgMgr.getApplicationInfo(TEST_PACKAGE_NAME, 0))
+            .thenReturn(mMockApplInfo);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
+        when(mMockAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, mUid, TEST_PACKAGE_NAME))
+            .thenReturn(mWifiScanAllowApps);
+        when(mMockAppOps.noteOp(AppOpsManager.OP_COARSE_LOCATION, mUid, TEST_PACKAGE_NAME))
+            .thenReturn(mAllowCoarseLocationApps);
+        if (mThrowSecurityException) {
+            doThrow(new SecurityException("Package " + TEST_PACKAGE_NAME + " doesn't belong"
+                    + " to application bound to user " + mUid))
+                    .when(mMockAppOps).checkPackage(mUid, TEST_PACKAGE_NAME);
+        }
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+            .thenReturn(mMockAppOps);
+        when(mMockUserManager.getProfiles(mCurrentUser))
+            .thenReturn(Arrays.asList(mMockUserInfo));
+        when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+        when(mMockContext.getSystemService(Context.USER_SERVICE))
+            .thenReturn(mMockUserManager);
+        when(mWifiInjector.makeLog(anyString())).thenReturn(mWifiLog);
+    }
+
+    private void initTestVars() {
+        mPermissionsList.clear();
+        mReturnPermission = createPermissionAnswer();
+        mWifiScanAllowApps = AppOpsManager.MODE_ERRORED;
+        mUid = OTHER_USER_UID;
+        mThrowSecurityException = true;
+        mMockUserInfo.id = UserHandle.USER_NULL;
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
+        mTargetVersion = Build.VERSION_CODES.M;
+        mPkgNameOfTopActivity = INVALID_PACKAGE;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_OFF;
+        mCurrentUser = UserHandle.USER_SYSTEM;
+        mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
+        mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
+        mActiveNwScorer = false;
+    }
+
+    private void setupMockInterface() {
+        BinderUtil.setUid(mUid);
+        doAnswer(mReturnPermission).when(mMockPermissionsWrapper).getUidPermission(
+                        anyString(), anyInt());
+        doAnswer(mReturnPermission).when(mMockPermissionsWrapper).getUidPermission(
+                        anyString(), anyInt());
+        when(mMockPermissionsWrapper.getCallingUserId(mUid)).thenReturn(mCallingUser);
+        when(mMockPermissionsWrapper.getCurrentUser()).thenReturn(mCurrentUser);
+        when(mNetworkScoreManager.isCallerActiveScorer(mUid)).thenReturn(mActiveNwScorer);
+        when(mMockPermissionsWrapper.getUidPermission(mManifestStringCoarse, mUid))
+            .thenReturn(mCoarseLocationPermission);
+        when(mMockWifiSettingsStore.getLocationModeSetting(mMockContext))
+            .thenReturn(mLocationModeSetting);
+        when(mMockPermissionsWrapper.getTopPkgName()).thenReturn(mPkgNameOfTopActivity);
+    }
+}
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..0942b83
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
@@ -0,0 +1,589 @@
+/*
+ * 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.*;
+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 {
+    public static final String XML_STRING_EAP_METHOD_REPLACE_FORMAT =
+            "<int name=\"EapMethod\" value=\"%d\" />";
+
+    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.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
+        configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
+        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+        configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
+        configuration.status = WifiConfiguration.Status.DISABLED;
+        configuration.linkedConfigurations = new HashMap<>();
+        configuration.linkedConfigurations.put(TEST_DUMMY_CONFIG_KEY, Integer.valueOf(1));
+        configuration.defaultGwMacAddress = TEST_STATIC_IP_GATEWAY_ADDRESS;
+        configuration.requirePMF = true;
+        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 a WifiConfiguration with status as CURRENT when serializing
+     * is deserialized as ENABLED.
+     */
+    @Test
+    public void testCurrentStatusConfigurationSerializeDeserializeForConfigStore()
+            throws IOException, XmlPullParserException {
+        WifiConfiguration configuration = WifiConfigurationTestUtil.createEapNetwork();
+        configuration.status = WifiConfiguration.Status.CURRENT;
+        byte[] xmlData = serializeWifiConfigurationForConfigStore(configuration);
+        Pair<String, WifiConfiguration> deserializedConfiguration =
+                deserializeWifiConfiguration(xmlData);
+        assertEquals(WifiConfiguration.Status.ENABLED, deserializedConfiguration.second.status);
+    }
+
+    /**
+     * 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 network selection status deprecation is handled correctly during restore
+     * of data after upgrade.
+     * This test tries to simulate the scenario where we have a
+     * {@link NetworkSelectionStatus#getNetworkStatusString()} string stored
+     * in the XML file from a previous release which has now been deprecated. The network should
+     * be restored as enabled.
+     */
+    @Test
+    public void testDeprecatedNetworkSelectionStatusDeserialize()
+            throws IOException, XmlPullParserException {
+        // Create a dummy network selection status.
+        NetworkSelectionStatus status = new NetworkSelectionStatus();
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        status.setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
+        status.setConnectChoice(TEST_DUMMY_CONFIG_KEY);
+        status.setConnectChoiceTimestamp(867889);
+        status.setHasEverConnected(true);
+
+        // Serialize this to XML string.
+        byte[] xmlData = serializeNetworkSelectionStatus(status);
+
+        // Now modify the status string with some invalid string in XML data..
+        String xmlString = new String(xmlData);
+        String deprecatedXmlString =
+                xmlString.replaceAll(
+                        status.getNetworkStatusString(), "NETWORK_SELECTION_DEPRECATED");
+        // Ensure that the modification did take effect.
+        assertFalse(xmlString.equals(deprecatedXmlString));
+
+        // Now Deserialize the modified XML data.
+        byte[] deprecatedXmlData = xmlString.getBytes();
+        NetworkSelectionStatus retrievedStatus =
+                deserializeNetworkSelectionStatus(deprecatedXmlData);
+
+        // The status retrieved should have reset both the |Status| & |DisableReason| fields after
+        // deserialization, but should have restored all the other fields correctly.
+        NetworkSelectionStatus expectedStatus = new NetworkSelectionStatus();
+        expectedStatus.copy(status);
+        expectedStatus.setNetworkSelectionStatus(NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        expectedStatus.setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+
+        WifiConfigurationTestUtil.assertNetworkSelectionStatusEqualForConfigStore(
+                expectedStatus, retrievedStatus);
+    }
+
+    /**
+     * Verify that a network selection disable reason deprecation is handled correctly during
+     * restore of data after upgrade.
+     * This test tries to simulate the scenario where we have a
+     * {@link NetworkSelectionStatus#getNetworkDisableReasonString()} ()} string stored
+     * in the XML file from a previous release which has now been deprecated. The network should
+     * be restored as enabled.
+     */
+    @Test
+    public void testDeprecatedNetworkSelectionDisableReasonDeserialize()
+            throws IOException, XmlPullParserException {
+        // Create a dummy network selection status.
+        NetworkSelectionStatus status = new NetworkSelectionStatus();
+        status.setNetworkSelectionStatus(
+                NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+        status.setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
+        status.setConnectChoice(TEST_DUMMY_CONFIG_KEY);
+        status.setConnectChoiceTimestamp(867889);
+        status.setHasEverConnected(true);
+
+        // Serialize this to XML string.
+        byte[] xmlData = serializeNetworkSelectionStatus(status);
+
+        // Now modify the disable reason string with some invalid string in XML data.
+        String xmlString = new String(xmlData);
+        String deprecatedXmlString =
+                xmlString.replaceAll(status.getNetworkDisableReasonString(), "DISABLED_DEPRECATED");
+        // Ensure that the modification did take effect.
+        assertFalse(xmlString.equals(deprecatedXmlString));
+
+        // Now Deserialize the modified XML data.
+        byte[] deprecatedXmlData = xmlString.getBytes();
+        NetworkSelectionStatus retrievedStatus =
+                deserializeNetworkSelectionStatus(deprecatedXmlData);
+
+        // The status retrieved should have reset both the |Status| & |DisableReason| fields after
+        // deserialization, but should have restored all the other fields correctly.
+        NetworkSelectionStatus expectedStatus = new NetworkSelectionStatus();
+        expectedStatus.copy(status);
+        expectedStatus.setNetworkSelectionStatus(NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
+        expectedStatus.setNetworkSelectionDisableReason(
+                NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+
+        WifiConfigurationTestUtil.assertNetworkSelectionStatusEqualForConfigStore(
+                expectedStatus, retrievedStatus);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Verify that an illegal argument exception is thrown when trying to parse out a corrupted
+     * WifiEnterpriseConfig.
+     *
+     * @throws Exception
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testWifiEnterpriseConfigSerializeDeserializeThrowsIllegalArgException()
+            throws Exception {
+        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);
+        String xmlString = new String(serializeWifiEnterpriseConfig(config));
+        // Manipulate the XML data to set the EAP method to None, this should raise an Illegal
+        // argument exception in WifiEnterpriseConfig.setEapMethod().
+        xmlString = xmlString.replaceAll(
+                String.format(XML_STRING_EAP_METHOD_REPLACE_FORMAT, TEST_EAP_METHOD),
+                String.format(XML_STRING_EAP_METHOD_REPLACE_FORMAT, WifiEnterpriseConfig.Eap.NONE));
+        deserializeWifiEnterpriseConfig(xmlString.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * Verify that WifiConfiguration representation of a legacy Passpoint configuration is
+     * serialized & deserialized correctly.
+     *
+     *@throws Exception
+     */
+    @Test
+    public void testLegacyPasspointConfigSerializeDeserialize() throws Exception {
+        WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
+        config.isLegacyPasspointConfig = true;
+        config.roamingConsortiumIds = new long[] {0x12345678};
+        config.enterpriseConfig.setPlmn("1234");
+        config.enterpriseConfig.setRealm("test.com");
+        serializeDeserializeWifiConfigurationForConfigStore(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 byte[] serializeNetworkSelectionStatus(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);
+        return outputStream.toByteArray();
+    }
+
+    private NetworkSelectionStatus deserializeNetworkSelectionStatus(byte[] data)
+            throws IOException, XmlPullParserException {
+        final XmlPullParser in = Xml.newPullParser();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
+        return NetworkSelectionStatusXmlUtil.parseFromXml(in, in.getDepth());
+    }
+
+    private void serializeDeserializeNetworkSelectionStatus(NetworkSelectionStatus status)
+            throws IOException, XmlPullParserException {
+        // Serialize the status object.
+        byte[] data = serializeNetworkSelectionStatus(status);
+        // Deserialize the status object.
+        NetworkSelectionStatus retrievedStatus = deserializeNetworkSelectionStatus(data);
+
+        WifiConfigurationTestUtil.assertNetworkSelectionStatusEqualForConfigStore(
+                status, retrievedStatus);
+    }
+
+    private byte[] serializeWifiEnterpriseConfig(WifiEnterpriseConfig config)
+            throws IOException, XmlPullParserException {
+        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);
+        return outputStream.toByteArray();
+    }
+
+    private WifiEnterpriseConfig deserializeWifiEnterpriseConfig(byte[] data)
+            throws IOException, XmlPullParserException {
+        final XmlPullParser in = Xml.newPullParser();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
+        return WifiEnterpriseConfigXmlUtil.parseFromXml(in, in.getDepth());
+    }
+
+    private void serializeDeserializeWifiEnterpriseConfig(WifiEnterpriseConfig config)
+            throws IOException, XmlPullParserException {
+        WifiEnterpriseConfig retrievedConfig =
+                deserializeWifiEnterpriseConfig(serializeWifiEnterpriseConfig(config));
+        WifiConfigurationTestUtil.assertWifiEnterpriseConfigEqualForConfigStore(
+                config, retrievedConfig);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/wificond/NativeScanResultTest.java b/tests/wifitests/src/com/android/server/wifi/wificond/NativeScanResultTest.java
new file mode 100644
index 0000000..08573e4
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/wificond/NativeScanResultTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.wificond;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.BitSet;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.wificond.NativeScanResult}.
+ */
+@SmallTest
+public class NativeScanResultTest {
+
+    private static final byte[] TEST_SSID =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_BSSID =
+            new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
+                        (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
+    private static final byte[] TEST_INFO_ELEMENT =
+            new byte[] {(byte) 0x01, (byte) 0x03, (byte) 0x12, (byte) 0xbe, (byte) 0xff};
+    private static final int TEST_FREQUENCY = 2456;
+    private static final int TEST_SIGNAL_MBM = -45;
+    private static final long TEST_TSF = 34455441;
+    private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }};
+    private static final boolean TEST_ASSOCIATED = true;
+
+    /**
+     *  NativeScanResult object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() throws Exception {
+        NativeScanResult scanResult = new NativeScanResult();
+        scanResult.ssid = TEST_SSID;
+        scanResult.bssid = TEST_BSSID;
+        scanResult.infoElement = TEST_INFO_ELEMENT;
+        scanResult.frequency = TEST_FREQUENCY;
+        scanResult.signalMbm = TEST_SIGNAL_MBM;
+        scanResult.tsf = TEST_TSF;
+        scanResult.capability = TEST_CAPABILITY;
+        scanResult.associated = TEST_ASSOCIATED;
+        Parcel parcel = Parcel.obtain();
+        scanResult.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        NativeScanResult scanResultDeserialized = NativeScanResult.CREATOR.createFromParcel(parcel);
+
+        assertArrayEquals(scanResult.ssid, scanResultDeserialized.ssid);
+        assertArrayEquals(scanResult.bssid, scanResultDeserialized.bssid);
+        assertArrayEquals(scanResult.infoElement, scanResultDeserialized.infoElement);
+        assertEquals(scanResult.frequency, scanResultDeserialized.frequency);
+        assertEquals(scanResult.signalMbm, scanResultDeserialized.signalMbm);
+        assertEquals(scanResult.tsf, scanResultDeserialized.tsf);
+        assertTrue(scanResult.capability.equals(scanResultDeserialized.capability));
+        assertEquals(scanResult.associated, scanResultDeserialized.associated);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/wificond/PnoSettingsTest.java b/tests/wifitests/src/com/android/server/wifi/wificond/PnoSettingsTest.java
new file mode 100644
index 0000000..ee40197
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/wificond/PnoSettingsTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.wificond;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.wificond.PnoSettingsResult}.
+ */
+@SmallTest
+public class PnoSettingsTest {
+
+    private static final byte[] TEST_SSID_1 =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_SSID_2 =
+            new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+    private static final int TEST_INTERVAL_MS = 30000;
+    private static final int TEST_MIN_2G_RSSI = -60;
+    private static final int TEST_MIN_5G_RSSI = -65;
+
+    /**
+     *  PnoSettings object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() throws Exception {
+
+        PnoSettings pnoSettings = new PnoSettings();
+
+        PnoNetwork pnoNetwork1 = new PnoNetwork();
+        pnoNetwork1.ssid = TEST_SSID_1;
+        pnoNetwork1.isHidden = true;
+
+        PnoNetwork pnoNetwork2 = new PnoNetwork();
+        pnoNetwork2.ssid = TEST_SSID_2;
+        pnoNetwork2.isHidden = false;
+
+        pnoSettings.pnoNetworks = new ArrayList(Arrays.asList(pnoNetwork1, pnoNetwork2));
+
+        pnoSettings.intervalMs = TEST_INTERVAL_MS;
+        pnoSettings.min2gRssi = TEST_MIN_2G_RSSI;
+        pnoSettings.min5gRssi = TEST_MIN_5G_RSSI;
+
+        Parcel parcel = Parcel.obtain();
+        pnoSettings.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        PnoSettings pnoSettingsDeserialized =
+                pnoSettings.CREATOR.createFromParcel(parcel);
+
+        assertNotNull(pnoSettingsDeserialized);
+        assertEquals(pnoSettings, pnoSettingsDeserialized);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/wificond/SingleScanSettingsTest.java b/tests/wifitests/src/com/android/server/wifi/wificond/SingleScanSettingsTest.java
new file mode 100644
index 0000000..0a63616
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/wificond/SingleScanSettingsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.wificond;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.wificond.SingleScanSettingsResult}.
+ */
+@SmallTest
+public class SingleScanSettingsTest {
+
+    private static final byte[] TEST_SSID_1 =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_SSID_2 =
+            new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+    private static final int TEST_FREQUENCY_1 = 2456;
+    private static final int TEST_FREQUENCY_2 = 5215;
+
+    /**
+     *  SingleScanSettings object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() throws Exception {
+        ChannelSettings channelSettings1 = new ChannelSettings();
+        channelSettings1.frequency = TEST_FREQUENCY_1;
+        ChannelSettings channelSettings2 = new ChannelSettings();
+        channelSettings2.frequency = TEST_FREQUENCY_2;
+
+        HiddenNetwork hiddenNetwork1 = new HiddenNetwork();
+        hiddenNetwork1.ssid = TEST_SSID_1;
+        HiddenNetwork hiddenNetwork2 = new HiddenNetwork();
+        hiddenNetwork2.ssid = TEST_SSID_2;
+
+        SingleScanSettings scanSettings = new SingleScanSettings();
+        scanSettings.channelSettings =
+                new ArrayList(Arrays.asList(channelSettings1, channelSettings2));
+        scanSettings.hiddenNetworks = new ArrayList(Arrays.asList(hiddenNetwork1, hiddenNetwork2));
+
+        Parcel parcel = Parcel.obtain();
+        scanSettings.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        SingleScanSettings scanSettingsDeserialized =
+                SingleScanSettings.CREATOR.createFromParcel(parcel);
+
+        assertNotNull(scanSettingsDeserialized);
+        assertEquals(scanSettings, scanSettingsDeserialized);
+    }
+}