Split updatable/non-updatable Wifi APIs into separate directories

This creates a clearer boundary between what is updatable
(part of the Wifi module) and what is not updatable (outside
the module).

Bug: 176105484
Test: compiles
Change-Id: Ia95e300da9286d79ee57c285c3bfa88ecc98e326
diff --git a/wifi/non-updatable/java/Android.bp b/wifi/non-updatable/java/Android.bp
new file mode 100644
index 0000000..b35b4be
--- /dev/null
+++ b/wifi/non-updatable/java/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This directory contains framework Wifi APIs that are not part of the Wifi module (i.e. not
+// updatable), and are generally only called by the Wifi module.
+
+// necessary since we only want the `path` property to only refer to these files
+filegroup {
+    name: "framework-wifi-non-updatable-sources-internal",
+    srcs: ["src/**/*.java"],
+    path: "src",
+    visibility: ["//visibility:private"],
+}
+
+filegroup {
+    name: "framework-wifi-non-updatable-sources",
+    srcs: [
+        // TODO(b/146011398) package android.net.wifi is now split amongst 2 jars: framework.jar and
+        // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
+        // to a separate package.
+        ":framework-wifi-non-updatable-sources-internal",
+        ":libwificond_ipc_aidl",
+    ],
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java b/wifi/non-updatable/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java
new file mode 100755
index 0000000..c5472ce
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.os.Environment.getDataMiscDirectory;
+
+import android.annotation.Nullable;
+import android.net.MacAddress;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Utility class to convert the legacy softap.conf file format to the new XML format.
+ * Note:
+ * <li>This should be modified by the OEM if they want to migrate configuration for existing
+ * devices for new softap features supported by AOSP in Android 11.
+ * For ex: client allowlist/blocklist feature was already supported by some OEM's before Android 10
+ * while AOSP only supported it in Android 11. </li>
+ * <li>Most of this class was copied over from WifiApConfigStore class in Android 10 and
+ * SoftApStoreData class in Android 11</li>
+ * @hide
+ */
+public final class SoftApConfToXmlMigrationUtil {
+    private static final String TAG = "SoftApConfToXmlMigrationUtil";
+
+    /**
+     * Directory to read the wifi config store files from under.
+     */
+    private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi";
+    /**
+     * The legacy Softap config file which contained key/value pairs.
+     */
+    private static final String LEGACY_AP_CONFIG_FILE = "softap.conf";
+
+    /**
+     * Pre-apex wifi shared folder.
+     */
+    private static File getLegacyWifiSharedDirectory() {
+        return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME);
+    }
+
+    /* @hide constants copied from WifiConfiguration */
+    /**
+     * 2GHz band.
+     */
+    private static final int WIFICONFIG_AP_BAND_2GHZ = 0;
+    /**
+     * 5GHz band.
+     */
+    private static final int WIFICONFIG_AP_BAND_5GHZ = 1;
+    /**
+     * Device is allowed to choose the optimal band (2Ghz or 5Ghz) based on device capability,
+     * operating country code and current radio conditions.
+     */
+    private static final int WIFICONFIG_AP_BAND_ANY = -1;
+    /**
+     * Convert band from WifiConfiguration into SoftApConfiguration
+     *
+     * @param wifiConfigBand band encoded as WIFICONFIG_AP_BAND_xxxx
+     * @return band as encoded as SoftApConfiguration.BAND_xxx
+     */
+    @VisibleForTesting
+    public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) {
+        switch (wifiConfigBand) {
+            case WIFICONFIG_AP_BAND_2GHZ:
+                return SoftApConfiguration.BAND_2GHZ;
+            case WIFICONFIG_AP_BAND_5GHZ:
+                return SoftApConfiguration.BAND_5GHZ;
+            case WIFICONFIG_AP_BAND_ANY:
+                return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ;
+            default:
+                return SoftApConfiguration.BAND_2GHZ;
+        }
+    }
+
+    /**
+     * Load AP configuration from legacy persistent storage.
+     * Note: This is deprecated and only used for migrating data once on reboot.
+     */
+    private static SoftApConfiguration loadFromLegacyFile(InputStream fis) {
+        SoftApConfiguration config = null;
+        DataInputStream in = null;
+        try {
+            SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+            in = new DataInputStream(new BufferedInputStream(fis));
+
+            int version = in.readInt();
+            if (version < 1 || version > 3) {
+                Log.e(TAG, "Bad version on hotspot configuration file");
+                return null;
+            }
+            configBuilder.setSsid(in.readUTF());
+
+            if (version >= 2) {
+                int band = in.readInt();
+                int channel = in.readInt();
+                if (channel == 0) {
+                    configBuilder.setBand(
+                            convertWifiConfigBandToSoftApConfigBand(band));
+                } else {
+                    configBuilder.setChannel(channel,
+                            convertWifiConfigBandToSoftApConfigBand(band));
+                }
+            }
+            if (version >= 3) {
+                configBuilder.setHiddenSsid(in.readBoolean());
+            }
+            int authType = in.readInt();
+            if (authType == WifiConfiguration.KeyMgmt.WPA2_PSK) {
+                configBuilder.setPassphrase(in.readUTF(),
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+            }
+            config = configBuilder.build();
+        } catch (IOException e) {
+            Log.e(TAG, "Error reading hotspot configuration ",  e);
+            config = null;
+        } catch (IllegalArgumentException ie) {
+            Log.e(TAG, "Invalid hotspot configuration ", ie);
+            config = null;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "Error closing hotspot configuration during read", e);
+                }
+            }
+        }
+        // NOTE: OEM's should add their customized parsing code here.
+        return config;
+    }
+
+    // This is the version that Android 11 released with.
+    private static final int CONFIG_STORE_DATA_VERSION = 3;
+
+    private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
+    private static final String XML_TAG_VERSION = "Version";
+    private static final String XML_TAG_SECTION_HEADER_SOFTAP = "SoftAp";
+    private static final String XML_TAG_SSID = "SSID";
+    private static final String XML_TAG_BSSID = "Bssid";
+    private static final String XML_TAG_CHANNEL = "Channel";
+    private static final String XML_TAG_HIDDEN_SSID = "HiddenSSID";
+    private static final String XML_TAG_SECURITY_TYPE = "SecurityType";
+    private static final String XML_TAG_AP_BAND = "ApBand";
+    private static final String XML_TAG_PASSPHRASE = "Passphrase";
+    private static final String XML_TAG_MAX_NUMBER_OF_CLIENTS = "MaxNumberOfClients";
+    private static final String XML_TAG_AUTO_SHUTDOWN_ENABLED = "AutoShutdownEnabled";
+    private static final String XML_TAG_SHUTDOWN_TIMEOUT_MILLIS = "ShutdownTimeoutMillis";
+    private static final String XML_TAG_CLIENT_CONTROL_BY_USER = "ClientControlByUser";
+    private static final String XML_TAG_BLOCKED_CLIENT_LIST = "BlockedClientList";
+    private static final String XML_TAG_ALLOWED_CLIENT_LIST = "AllowedClientList";
+    public static final String XML_TAG_CLIENT_MACADDRESS = "ClientMacAddress";
+
+    private static byte[] convertConfToXml(SoftApConfiguration softApConf) {
+        try {
+            final XmlSerializer out = new FastXmlSerializer();
+            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+
+            // Header for the XML file.
+            out.startDocument(null, true);
+            out.startTag(null, XML_TAG_DOCUMENT_HEADER);
+            XmlUtils.writeValueXml(CONFIG_STORE_DATA_VERSION, XML_TAG_VERSION, out);
+            out.startTag(null, XML_TAG_SECTION_HEADER_SOFTAP);
+
+            // SoftAp conf
+            XmlUtils.writeValueXml(softApConf.getSsid(), XML_TAG_SSID, out);
+            if (softApConf.getBssid() != null) {
+                XmlUtils.writeValueXml(softApConf.getBssid().toString(), XML_TAG_BSSID, out);
+            }
+            XmlUtils.writeValueXml(softApConf.getBand(), XML_TAG_AP_BAND, out);
+            XmlUtils.writeValueXml(softApConf.getChannel(), XML_TAG_CHANNEL, out);
+            XmlUtils.writeValueXml(softApConf.isHiddenSsid(), XML_TAG_HIDDEN_SSID, out);
+            XmlUtils.writeValueXml(softApConf.getSecurityType(), XML_TAG_SECURITY_TYPE, out);
+            if (softApConf.getSecurityType() != SoftApConfiguration.SECURITY_TYPE_OPEN) {
+                XmlUtils.writeValueXml(softApConf.getPassphrase(), XML_TAG_PASSPHRASE, out);
+            }
+            XmlUtils.writeValueXml(softApConf.getMaxNumberOfClients(),
+                    XML_TAG_MAX_NUMBER_OF_CLIENTS, out);
+            XmlUtils.writeValueXml(softApConf.isClientControlByUserEnabled(),
+                    XML_TAG_CLIENT_CONTROL_BY_USER, out);
+            XmlUtils.writeValueXml(softApConf.isAutoShutdownEnabled(),
+                    XML_TAG_AUTO_SHUTDOWN_ENABLED, out);
+            XmlUtils.writeValueXml(softApConf.getShutdownTimeoutMillis(),
+                    XML_TAG_SHUTDOWN_TIMEOUT_MILLIS, out);
+            out.startTag(null, XML_TAG_BLOCKED_CLIENT_LIST);
+            for (MacAddress mac: softApConf.getBlockedClientList()) {
+                XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out);
+            }
+            out.endTag(null, XML_TAG_BLOCKED_CLIENT_LIST);
+            out.startTag(null, XML_TAG_ALLOWED_CLIENT_LIST);
+            for (MacAddress mac: softApConf.getAllowedClientList()) {
+                XmlUtils.writeValueXml(mac.toString(), XML_TAG_CLIENT_MACADDRESS, out);
+            }
+            out.endTag(null, XML_TAG_ALLOWED_CLIENT_LIST);
+
+            // Footer for the XML file.
+            out.endTag(null, XML_TAG_SECTION_HEADER_SOFTAP);
+            out.endTag(null, XML_TAG_DOCUMENT_HEADER);
+            out.endDocument();
+
+            return outputStream.toByteArray();
+        } catch (IOException | XmlPullParserException e) {
+            Log.e(TAG, "Failed to convert softap conf to XML", e);
+            return null;
+        }
+    }
+
+    private SoftApConfToXmlMigrationUtil() { }
+
+    /**
+     * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML
+     * format understood by WifiConfigStore.
+     * Note: Used for unit testing.
+     */
+    @VisibleForTesting
+    @Nullable
+    public static InputStream convert(InputStream fis) {
+        SoftApConfiguration softApConf = loadFromLegacyFile(fis);
+        if (softApConf == null) return null;
+
+        byte[] xmlBytes = convertConfToXml(softApConf);
+        if (xmlBytes == null) return null;
+
+        return new ByteArrayInputStream(xmlBytes);
+    }
+
+    /**
+     * Read the legacy /data/misc/wifi/softap.conf file format and convert to the new XML
+     * format understood by WifiConfigStore.
+     */
+    @Nullable
+    public static InputStream convert() {
+        File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE);
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(file);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+        if (fis == null) return null;
+        return convert(fis);
+    }
+
+    /**
+     * Remove the legacy /data/misc/wifi/softap.conf file.
+     */
+    @Nullable
+    public static void remove() {
+        File file = new File(getLegacyWifiSharedDirectory(), LEGACY_AP_CONFIG_FILE);
+        file.delete();
+    }
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/WifiMigration.java b/wifi/non-updatable/java/src/android/net/wifi/WifiMigration.java
new file mode 100755
index 0000000..5792d27
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/WifiMigration.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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 android.os.Environment.getDataMiscCeDirectory;
+import static android.os.Environment.getDataMiscDirectory;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.AtomicFile;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Class used to provide one time hooks for existing OEM devices to migrate their config store
+ * data and other settings to the wifi apex.
+ * @hide
+ */
+@SystemApi
+public final class WifiMigration {
+    /**
+     * Directory to read the wifi config store files from under.
+     */
+    private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi";
+    /**
+     * Config store file for general shared store file.
+     * AOSP Path on Android 10: /data/misc/wifi/WifiConfigStore.xml
+     */
+    public static final int STORE_FILE_SHARED_GENERAL = 0;
+    /**
+     * Config store file for softap shared store file.
+     * AOSP Path on Android 10: /data/misc/wifi/softap.conf
+     */
+    public static final int STORE_FILE_SHARED_SOFTAP = 1;
+    /**
+     * Config store file for general user store file.
+     * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStore.xml
+     */
+    public static final int STORE_FILE_USER_GENERAL = 2;
+    /**
+     * Config store file for network suggestions user store file.
+     * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStoreNetworkSuggestions.xml
+     */
+    public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "STORE_FILE_SHARED_" }, value = {
+            STORE_FILE_SHARED_GENERAL,
+            STORE_FILE_SHARED_SOFTAP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SharedStoreFileId { }
+
+    /** @hide */
+    @IntDef(prefix = { "STORE_FILE_USER_" }, value = {
+            STORE_FILE_USER_GENERAL,
+            STORE_FILE_USER_NETWORK_SUGGESTIONS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserStoreFileId { }
+
+    /**
+     * Mapping of Store file Id to Store file names.
+     *
+     * NOTE: This is the default path for the files on AOSP devices. If the OEM has modified
+     * the path or renamed the files, please edit this appropriately.
+     */
+    private static final SparseArray<String> STORE_ID_TO_FILE_NAME =
+            new SparseArray<String>() {{
+                put(STORE_FILE_SHARED_GENERAL, "WifiConfigStore.xml");
+                put(STORE_FILE_SHARED_SOFTAP, "WifiConfigStoreSoftAp.xml");
+                put(STORE_FILE_USER_GENERAL, "WifiConfigStore.xml");
+                put(STORE_FILE_USER_NETWORK_SUGGESTIONS, "WifiConfigStoreNetworkSuggestions.xml");
+            }};
+
+    /**
+     * Pre-apex wifi shared folder.
+     */
+    private static File getLegacyWifiSharedDirectory() {
+        return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME);
+    }
+
+    /**
+     * Pre-apex wifi user folder.
+     */
+    private static File getLegacyWifiUserDirectory(int userId) {
+        return new File(getDataMiscCeDirectory(userId), LEGACY_WIFI_STORE_DIRECTORY_NAME);
+    }
+
+    /**
+     * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure
+     * data integrity.
+     */
+    private static AtomicFile getSharedAtomicFile(@SharedStoreFileId int storeFileId) {
+        return new AtomicFile(new File(
+                getLegacyWifiSharedDirectory(),
+                STORE_ID_TO_FILE_NAME.get(storeFileId)));
+    }
+
+    /**
+     * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure
+     * data integrity.
+     */
+    private static AtomicFile getUserAtomicFile(@UserStoreFileId  int storeFileId, int userId) {
+        return new AtomicFile(new File(
+                getLegacyWifiUserDirectory(userId),
+                STORE_ID_TO_FILE_NAME.get(storeFileId)));
+    }
+
+    private WifiMigration() { }
+
+    /**
+     * Load data from legacy shared wifi config store file.
+     * <p>
+     * Expected AOSP format is available in the sample files under {@code /frameworks/base/wifi/
+     * java/android/net/wifi/migration_samples}.
+     * </p>
+     * <p>
+     * Note:
+     * <li>OEMs need to change the implementation of
+     * {@link #convertAndRetrieveSharedConfigStoreFile(int)} only if their existing config store
+     * format or file locations differs from the vanilla AOSP implementation.</li>
+     * <li>The wifi apex will invoke
+     * {@link #convertAndRetrieveSharedConfigStoreFile(int)}
+     * method on every bootup, it is the responsibility of the OEM implementation to ensure that
+     * they perform the necessary in place conversion of their config store file to conform to the
+     * AOSP format. The OEM should ensure that the method should only return the
+     * {@link InputStream} stream for the data to be migrated only on the first bootup.</li>
+     * <li>Once the migration is done, the apex will invoke
+     * {@link #removeSharedConfigStoreFile(int)} to delete the store file.</li>
+     * <li>The only relevant invocation of {@link #convertAndRetrieveSharedConfigStoreFile(int)}
+     * occurs when a previously released device upgrades to the wifi apex from an OEM
+     * implementation of the wifi stack.
+     * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file
+     * permissions, etc). Since the wifi service continues to run inside system_server process, this
+     * method will be called from the same context (so ideally the file should still be accessible).
+     * </li>
+     *
+     * @param storeFileId Identifier for the config store file. One of
+     * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL}
+     * @return Instance of {@link InputStream} for migrating data, null if no migration is
+     * necessary.
+     * @throws IllegalArgumentException on invalid storeFileId.
+     */
+    @Nullable
+    public static InputStream convertAndRetrieveSharedConfigStoreFile(
+            @SharedStoreFileId int storeFileId) {
+        if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId !=  STORE_FILE_SHARED_SOFTAP) {
+            throw new IllegalArgumentException("Invalid shared store file id");
+        }
+        try {
+            // OEMs should do conversions necessary here before returning the stream.
+            return getSharedAtomicFile(storeFileId).openRead();
+        } catch (FileNotFoundException e) {
+            // Special handling for softap.conf.
+            // Note: OEM devices upgrading from Q -> R will only have the softap.conf file.
+            // Test devices running previous R builds however may have already migrated to the
+            // XML format. So, check for that above before falling back to check for legacy file.
+            if (storeFileId == STORE_FILE_SHARED_SOFTAP) {
+                return SoftApConfToXmlMigrationUtil.convert();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Remove the legacy shared wifi config store file.
+     *
+     * @param storeFileId Identifier for the config store file. One of
+     * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL}
+     * @throws IllegalArgumentException on invalid storeFileId.
+     */
+    public static void removeSharedConfigStoreFile(@SharedStoreFileId int storeFileId) {
+        if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId !=  STORE_FILE_SHARED_SOFTAP) {
+            throw new IllegalArgumentException("Invalid shared store file id");
+        }
+        AtomicFile file = getSharedAtomicFile(storeFileId);
+        if (file.exists()) {
+            file.delete();
+            return;
+        }
+        // Special handling for softap.conf.
+        // Note: OEM devices upgrading from Q -> R will only have the softap.conf file.
+        // Test devices running previous R builds however may have already migrated to the
+        // XML format. So, check for that above before falling back to check for legacy file.
+        if (storeFileId == STORE_FILE_SHARED_SOFTAP) {
+            SoftApConfToXmlMigrationUtil.remove();
+        }
+    }
+
+    /**
+     * Load data from legacy user wifi config store file.
+     * <p>
+     * Expected AOSP format is available in the sample files under {@code /frameworks/base/wifi/
+     * java/android/net/wifi/migration_samples}.
+     * </p>
+     * <p>
+     * Note:
+     * <li>OEMs need to change the implementation of
+     * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} only if their existing config
+     * store format or file locations differs from the vanilla AOSP implementation.</li>
+     * <li>The wifi apex will invoke
+     * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)}
+     * method on every bootup, it is the responsibility of the OEM implementation to ensure that
+     * they perform the necessary in place conversion of their config store file to conform to the
+     * AOSP format. The OEM should ensure that the method should only return the
+     * {@link InputStream} stream for the data to be migrated only on the first bootup.</li>
+     * <li>Once the migration is done, the apex will invoke
+     * {@link #removeUserConfigStoreFile(int, UserHandle)} to delete the store file.</li>
+     * <li>The only relevant invocation of
+     * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} occurs when a previously
+     * released device upgrades to the wifi apex from an OEM implementation of the wifi
+     * stack.
+     * </li>
+     * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file
+     * permissions, etc). Since the wifi service continues to run inside system_server process, this
+     * method will be called from the same context (so ideally the file should still be accessible).
+     * </li>
+     *
+     * @param storeFileId Identifier for the config store file. One of
+     * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS}
+     * @param userHandle User handle.
+     * @return Instance of {@link InputStream} for migrating data, null if no migration is
+     * necessary.
+     * @throws IllegalArgumentException on invalid storeFileId or userHandle.
+     */
+    @Nullable
+    public static InputStream convertAndRetrieveUserConfigStoreFile(
+            @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) {
+        if (storeFileId != STORE_FILE_USER_GENERAL
+                && storeFileId !=  STORE_FILE_USER_NETWORK_SUGGESTIONS) {
+            throw new IllegalArgumentException("Invalid user store file id");
+        }
+        Objects.requireNonNull(userHandle);
+        try {
+            // OEMs should do conversions necessary here before returning the stream.
+            return getUserAtomicFile(storeFileId, userHandle.getIdentifier()).openRead();
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Remove the legacy user wifi config store file.
+     *
+     * @param storeFileId Identifier for the config store file. One of
+     * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS}
+     * @param userHandle User handle.
+     * @throws IllegalArgumentException on invalid storeFileId or userHandle.
+    */
+    public static void removeUserConfigStoreFile(
+            @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) {
+        if (storeFileId != STORE_FILE_USER_GENERAL
+                && storeFileId !=  STORE_FILE_USER_NETWORK_SUGGESTIONS) {
+            throw new IllegalArgumentException("Invalid user store file id");
+        }
+        Objects.requireNonNull(userHandle);
+        AtomicFile file = getUserAtomicFile(storeFileId, userHandle.getIdentifier());
+        if (file.exists()) {
+            file.delete();
+        }
+    }
+
+    /**
+     * Container for all the wifi settings data to migrate.
+     */
+    public static final class SettingsMigrationData implements Parcelable {
+        private final boolean mScanAlwaysAvailable;
+        private final boolean mP2pFactoryResetPending;
+        private final String mP2pDeviceName;
+        private final boolean mSoftApTimeoutEnabled;
+        private final boolean mWakeupEnabled;
+        private final boolean mScanThrottleEnabled;
+        private final boolean mVerboseLoggingEnabled;
+
+        private SettingsMigrationData(boolean scanAlwaysAvailable, boolean p2pFactoryResetPending,
+                @Nullable String p2pDeviceName, boolean softApTimeoutEnabled, boolean wakeupEnabled,
+                boolean scanThrottleEnabled, boolean verboseLoggingEnabled) {
+            mScanAlwaysAvailable = scanAlwaysAvailable;
+            mP2pFactoryResetPending = p2pFactoryResetPending;
+            mP2pDeviceName = p2pDeviceName;
+            mSoftApTimeoutEnabled = softApTimeoutEnabled;
+            mWakeupEnabled = wakeupEnabled;
+            mScanThrottleEnabled = scanThrottleEnabled;
+            mVerboseLoggingEnabled = verboseLoggingEnabled;
+        }
+
+        public static final @NonNull Parcelable.Creator<SettingsMigrationData> CREATOR =
+                new Parcelable.Creator<SettingsMigrationData>() {
+                    @Override
+                    public SettingsMigrationData createFromParcel(Parcel in) {
+                        boolean scanAlwaysAvailable = in.readBoolean();
+                        boolean p2pFactoryResetPending = in.readBoolean();
+                        String p2pDeviceName = in.readString();
+                        boolean softApTimeoutEnabled = in.readBoolean();
+                        boolean wakeupEnabled = in.readBoolean();
+                        boolean scanThrottleEnabled = in.readBoolean();
+                        boolean verboseLoggingEnabled = in.readBoolean();
+                        return new SettingsMigrationData(
+                                scanAlwaysAvailable, p2pFactoryResetPending,
+                                p2pDeviceName, softApTimeoutEnabled, wakeupEnabled,
+                                scanThrottleEnabled, verboseLoggingEnabled);
+                    }
+
+                    @Override
+                    public SettingsMigrationData[] newArray(int size) {
+                        return new SettingsMigrationData[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeBoolean(mScanAlwaysAvailable);
+            dest.writeBoolean(mP2pFactoryResetPending);
+            dest.writeString(mP2pDeviceName);
+            dest.writeBoolean(mSoftApTimeoutEnabled);
+            dest.writeBoolean(mWakeupEnabled);
+            dest.writeBoolean(mScanThrottleEnabled);
+            dest.writeBoolean(mVerboseLoggingEnabled);
+        }
+
+        /**
+         * @return True if scans are allowed even when wifi is toggled off, false otherwise.
+         */
+        public boolean isScanAlwaysAvailable() {
+            return mScanAlwaysAvailable;
+        }
+
+        /**
+         * @return indicate whether factory reset request is pending.
+         */
+        public boolean isP2pFactoryResetPending() {
+            return mP2pFactoryResetPending;
+        }
+
+        /**
+         * @return the Wi-Fi peer-to-peer device name
+         */
+        public @Nullable String getP2pDeviceName() {
+            return mP2pDeviceName;
+        }
+
+        /**
+         * @return Whether soft AP will shut down after a timeout period when no devices are
+         * connected.
+         */
+        public boolean isSoftApTimeoutEnabled() {
+            return mSoftApTimeoutEnabled;
+        }
+
+        /**
+         * @return whether Wi-Fi Wakeup feature is enabled.
+         */
+        public boolean isWakeUpEnabled() {
+            return mWakeupEnabled;
+        }
+
+        /**
+         * @return Whether wifi scan throttle is enabled or not.
+         */
+        public boolean isScanThrottleEnabled() {
+            return mScanThrottleEnabled;
+        }
+
+        /**
+         * @return Whether to enable verbose logging in Wi-Fi.
+         */
+        public boolean isVerboseLoggingEnabled() {
+            return mVerboseLoggingEnabled;
+        }
+
+        /**
+         * Builder to create instance of {@link SettingsMigrationData}.
+         */
+        public static final class Builder {
+            private boolean mScanAlwaysAvailable;
+            private boolean mP2pFactoryResetPending;
+            private String mP2pDeviceName;
+            private boolean mSoftApTimeoutEnabled;
+            private boolean mWakeupEnabled;
+            private boolean mScanThrottleEnabled;
+            private boolean mVerboseLoggingEnabled;
+
+            public Builder() {
+            }
+
+            /**
+             * Setting to allow scans even when wifi is toggled off.
+             *
+             * @param available true if available, false otherwise.
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setScanAlwaysAvailable(boolean available) {
+                mScanAlwaysAvailable = available;
+                return this;
+            }
+
+            /**
+             * Indicate whether factory reset request is pending.
+             *
+             * @param pending true if pending, false otherwise.
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setP2pFactoryResetPending(boolean pending) {
+                mP2pFactoryResetPending = pending;
+                return this;
+            }
+
+            /**
+             * The Wi-Fi peer-to-peer device name
+             *
+             * @param name Name if set, null otherwise.
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setP2pDeviceName(@Nullable String name) {
+                mP2pDeviceName = name;
+                return this;
+            }
+
+            /**
+             * Whether soft AP will shut down after a timeout period when no devices are connected.
+             *
+             * @param enabled true if enabled, false otherwise.
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setSoftApTimeoutEnabled(boolean enabled) {
+                mSoftApTimeoutEnabled = enabled;
+                return this;
+            }
+
+            /**
+             * Value to specify if Wi-Fi Wakeup feature is enabled.
+             *
+             * @param enabled true if enabled, false otherwise.
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setWakeUpEnabled(boolean enabled) {
+                mWakeupEnabled = enabled;
+                return this;
+            }
+
+            /**
+             * Whether wifi scan throttle is enabled or not.
+             *
+             * @param enabled true if enabled, false otherwise.
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setScanThrottleEnabled(boolean enabled) {
+                mScanThrottleEnabled = enabled;
+                return this;
+            }
+
+            /**
+             * Setting to enable verbose logging in Wi-Fi.
+             *
+             * @param enabled true if enabled, false otherwise.
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setVerboseLoggingEnabled(boolean enabled) {
+                mVerboseLoggingEnabled = enabled;
+                return this;
+            }
+
+            /**
+             * Build an instance of {@link SettingsMigrationData}.
+             *
+             * @return Instance of {@link SettingsMigrationData}.
+             */
+            public @NonNull SettingsMigrationData build() {
+                return new SettingsMigrationData(mScanAlwaysAvailable, mP2pFactoryResetPending,
+                        mP2pDeviceName, mSoftApTimeoutEnabled, mWakeupEnabled, mScanThrottleEnabled,
+                        mVerboseLoggingEnabled);
+            }
+        }
+    }
+
+    /**
+     * Load data from Settings.Global values.
+     *
+     * <p>
+     * Note:
+     * <li> This is method is invoked once on the first bootup. OEM can safely delete these settings
+     * once the migration is complete. The first & only relevant invocation of
+     * {@link #loadFromSettings(Context)} ()} occurs when a previously released
+     * device upgrades to the wifi apex from an OEM implementation of the wifi stack.
+     * </li>
+     *
+     * @param context Context to use for loading the settings provider.
+     * @return Instance of {@link SettingsMigrationData} for migrating data.
+     */
+    @NonNull
+    public static SettingsMigrationData loadFromSettings(@NonNull Context context) {
+        if (Settings.Global.getInt(
+                context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 0) == 1) {
+            // migration already complete, ignore.
+            return null;
+        }
+        SettingsMigrationData data = new SettingsMigrationData.Builder()
+                .setScanAlwaysAvailable(
+                        Settings.Global.getInt(context.getContentResolver(),
+                                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1)
+                .setP2pFactoryResetPending(
+                        Settings.Global.getInt(context.getContentResolver(),
+                                Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, 0) == 1)
+                .setP2pDeviceName(
+                        Settings.Global.getString(context.getContentResolver(),
+                                Settings.Global.WIFI_P2P_DEVICE_NAME))
+                .setSoftApTimeoutEnabled(
+                        Settings.Global.getInt(context.getContentResolver(),
+                                Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1)
+                .setWakeUpEnabled(
+                        Settings.Global.getInt(context.getContentResolver(),
+                                Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1)
+                .setScanThrottleEnabled(
+                        Settings.Global.getInt(context.getContentResolver(),
+                                Settings.Global.WIFI_SCAN_THROTTLE_ENABLED, 1) == 1)
+                .setVerboseLoggingEnabled(
+                        Settings.Global.getInt(context.getContentResolver(),
+                                Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0) == 1)
+                .build();
+        Settings.Global.putInt(
+                context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 1);
+        return data;
+
+    }
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/WifiNetworkScoreCache.java b/wifi/non-updatable/java/src/android/net/wifi/WifiNetworkScoreCache.java
new file mode 100755
index 0000000..3903658
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/WifiNetworkScoreCache.java
@@ -0,0 +1,316 @@
+/*
+ * 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 android.net.wifi;
+
+import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.INetworkScoreCache;
+import android.net.NetworkKey;
+import android.net.ScoredNetwork;
+import android.os.Handler;
+import android.os.Process;
+import android.util.Log;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * {@link INetworkScoreCache} implementation for Wifi Networks.
+ *
+ * TODO: This should not be part of wifi mainline module.
+ * @hide
+ */
+public class WifiNetworkScoreCache extends INetworkScoreCache.Stub {
+    private static final String TAG = "WifiNetworkScoreCache";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // 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;
+
+    /** Default number entries to be stored in the {@link LruCache}. */
+    private static final int DEFAULT_MAX_CACHE_SIZE = 100;
+
+    // See {@link #CacheListener}.
+    @Nullable
+    @GuardedBy("mLock")
+    private CacheListener mListener;
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+
+    // The key is of the form "<ssid>"<bssid>
+    // TODO: What about SSIDs that can't be encoded as UTF-8?
+    @GuardedBy("mLock")
+    private final LruCache<String, ScoredNetwork> mCache;
+
+    public WifiNetworkScoreCache(Context context) {
+        this(context, null /* listener */);
+    }
+
+    /**
+     * Instantiates a WifiNetworkScoreCache.
+     *
+     * @param context Application context
+     * @param listener CacheListener for cache updates
+     */
+    public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) {
+        this(context, listener, DEFAULT_MAX_CACHE_SIZE);
+    }
+
+    public WifiNetworkScoreCache(
+            Context context, @Nullable CacheListener listener, int maxCacheSize) {
+        mContext = context.getApplicationContext();
+        mListener = listener;
+        mCache = new LruCache<>(maxCacheSize);
+    }
+
+    @Override public final void updateScores(List<ScoredNetwork> networks) {
+        if (networks == null || networks.isEmpty()) {
+            return;
+        }
+        if (DBG) {
+            Log.d(TAG, "updateScores list size=" + networks.size());
+        }
+
+        boolean changed = false;
+
+        synchronized (mLock) {
+            for (ScoredNetwork network : networks) {
+                String networkKey = buildNetworkKey(network);
+                if (networkKey == null) {
+                    if (DBG) {
+                        Log.d(TAG, "Failed to build network key for ScoredNetwork" + network);
+                    }
+                    continue;
+                }
+                mCache.put(networkKey, network);
+                changed = true;
+            }
+
+            if (mListener != null && changed) {
+                mListener.post(networks);
+            }
+        }
+    }
+
+    @Override public final void clearScores() {
+        synchronized (mLock) {
+            mCache.evictAll();
+        }
+    }
+
+    /**
+     * 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.d(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.d(TAG, "getNetworkScore found scored network " + network.networkKey
+                        + " score " + Integer.toString(score)
+                        + " RSSI " + result.level
+                        + " isActiveNetwork " + isActiveNetwork);
+            }
+        }
+        return score;
+    }
+
+    @Nullable
+    public ScoredNetwork getScoredNetwork(ScanResult result) {
+        String key = buildNetworkKey(result);
+        if (key == null) return null;
+
+        synchronized (mLock) {
+            ScoredNetwork network = mCache.get(key);
+            return network;
+        }
+    }
+
+    /** Returns the ScoredNetwork for the given key. */
+    @Nullable
+    public ScoredNetwork getScoredNetwork(NetworkKey networkKey) {
+        String key = buildNetworkKey(networkKey);
+        if (key == null) {
+            if (DBG) {
+                Log.d(TAG, "Could not build key string for Network Key: " + networkKey);
+            }
+            return null;
+        }
+        synchronized (mLock) {
+            return mCache.get(key);
+        }
+    }
+
+    private String buildNetworkKey(ScoredNetwork network) {
+        if (network == null) {
+            return null;
+        }
+        return buildNetworkKey(network.networkKey);
+    }
+
+    private String buildNetworkKey(NetworkKey networkKey) {
+        if (networkKey == null) {
+            return null;
+        }
+        if (networkKey.wifiKey == null) return null;
+        if (networkKey.type == NetworkKey.TYPE_WIFI) {
+            String key = networkKey.wifiKey.ssid;
+            if (key == null) return null;
+            if (networkKey.wifiKey.bssid != null) {
+                key = key + 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);
+        String header = String.format("WifiNetworkScoreCache (%s/%d)",
+                mContext.getPackageName(), Process.myUid());
+        writer.println(header);
+        writer.println("  All score curves:");
+        synchronized (mLock) {
+            for (ScoredNetwork score : mCache.snapshot().values()) {
+                writer.println("    " + score);
+            }
+            writer.println("  Network scores for latest ScanResults:");
+            WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+            for (ScanResult scanResult : wifiManager.getScanResults()) {
+                writer.println(
+                        "    " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
+            }
+        }
+    }
+
+    /** Registers a CacheListener instance, replacing the previous listener if it existed. */
+    public void registerListener(CacheListener listener) {
+        synchronized (mLock) {
+            mListener = listener;
+        }
+    }
+
+    /** Removes the registered CacheListener. */
+    public void unregisterListener() {
+        synchronized (mLock) {
+            mListener = null;
+        }
+    }
+
+    /** Listener for updates to the cache inside WifiNetworkScoreCache. */
+    public abstract static class CacheListener {
+        private Handler mHandler;
+
+        /**
+         * Constructor for CacheListener.
+         *
+         * @param handler the Handler on which to invoke the {@link #networkCacheUpdated} method.
+         *          This cannot be null.
+         */
+        public CacheListener(@NonNull Handler handler) {
+            Objects.requireNonNull(handler);
+            mHandler = handler;
+        }
+
+        /** Invokes the {@link #networkCacheUpdated(List<ScoredNetwork>)} method on the handler. */
+        void post(List<ScoredNetwork> updatedNetworks) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    networkCacheUpdated(updatedNetworks);
+                }
+            });
+        }
+
+        /**
+         * Invoked whenever the cache is updated.
+         *
+         * <p>Clearing the cache does not invoke this method.
+         *
+         * @param updatedNetworks the networks that were updated
+         */
+        public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks);
+    }
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/ChannelSettings.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/ChannelSettings.java
new file mode 100644
index 0000000..4c14fd4
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/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 android.net.wifi.nl80211;
+
+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/wifi/non-updatable/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
new file mode 100644
index 0000000..bb0cc97
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/DeviceWiphyCapabilities.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.nl80211;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiAnnotations.ChannelWidth;
+import android.net.wifi.WifiAnnotations.WifiStandard;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * DeviceWiphyCapabilities for wificond
+ *
+ * Contains the WiFi physical layer attributes and capabilities of the device.
+ * It is used to collect these attributes from the device driver via wificond.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DeviceWiphyCapabilities implements Parcelable {
+    private static final String TAG = "DeviceWiphyCapabilities";
+
+    private boolean m80211nSupported;
+    private boolean m80211acSupported;
+    private boolean m80211axSupported;
+    private boolean mChannelWidth160MhzSupported;
+    private boolean mChannelWidth80p80MhzSupported;
+    private int mMaxNumberTxSpatialStreams;
+    private int mMaxNumberRxSpatialStreams;
+
+
+    /** public constructor */
+    public DeviceWiphyCapabilities() {
+        m80211nSupported = false;
+        m80211acSupported = false;
+        m80211axSupported = false;
+        mChannelWidth160MhzSupported = false;
+        mChannelWidth80p80MhzSupported = false;
+        mMaxNumberTxSpatialStreams = 1;
+        mMaxNumberRxSpatialStreams = 1;
+    }
+
+    /**
+     * Get the IEEE 802.11 standard support
+     *
+     * @param standard the IEEE 802.11 standard to check on its support.
+     *        valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean isWifiStandardSupported(@WifiStandard int standard) {
+        switch (standard) {
+            case ScanResult.WIFI_STANDARD_LEGACY:
+                return true;
+            case ScanResult.WIFI_STANDARD_11N:
+                return m80211nSupported;
+            case ScanResult.WIFI_STANDARD_11AC:
+                return m80211acSupported;
+            case ScanResult.WIFI_STANDARD_11AX:
+                return m80211axSupported;
+            default:
+                Log.e(TAG, "isWifiStandardSupported called with invalid standard: " + standard);
+                return false;
+        }
+    }
+
+    /**
+     * Set the IEEE 802.11 standard support
+     *
+     * @param standard the IEEE 802.11 standard to set its support.
+     *        valid values from {@link ScanResult}'s {@code WIFI_STANDARD_}
+     * @param support {@code true} if supported, {@code false} otherwise.
+     */
+    public void setWifiStandardSupport(@WifiStandard int standard, boolean support) {
+        switch (standard) {
+            case ScanResult.WIFI_STANDARD_11N:
+                m80211nSupported = support;
+                break;
+            case ScanResult.WIFI_STANDARD_11AC:
+                m80211acSupported = support;
+                break;
+            case ScanResult.WIFI_STANDARD_11AX:
+                m80211axSupported = support;
+                break;
+            default:
+                Log.e(TAG, "setWifiStandardSupport called with invalid standard: " + standard);
+        }
+    }
+
+    /**
+     * Get the support for channel bandwidth
+     *
+     * @param chWidth valid values from {@link ScanResult}'s {@code CHANNEL_WIDTH_}
+     *
+     * @return {@code true} if supported, {@code false} otherwise.
+     */
+    public boolean isChannelWidthSupported(@ChannelWidth int chWidth) {
+        switch (chWidth) {
+            case ScanResult.CHANNEL_WIDTH_20MHZ:
+                return true;
+            case ScanResult.CHANNEL_WIDTH_40MHZ:
+                return (m80211nSupported || m80211acSupported || m80211axSupported);
+            case ScanResult.CHANNEL_WIDTH_80MHZ:
+                return (m80211acSupported || m80211axSupported);
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                return mChannelWidth160MhzSupported;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return mChannelWidth80p80MhzSupported;
+            default:
+                Log.e(TAG, "isChannelWidthSupported called with invalid channel width: " + chWidth);
+        }
+        return false;
+    }
+
+    /**
+     * Set support for channel bandwidth
+     *
+     * @param chWidth valid values are {@link ScanResult#CHANNEL_WIDTH_160MHZ} and
+     *        {@link ScanResult#CHANNEL_WIDTH_80MHZ_PLUS_MHZ}
+     * @param support {@code true} if supported, {@code false} otherwise.
+     *
+     * @hide
+     */
+    public void setChannelWidthSupported(@ChannelWidth int chWidth, boolean support) {
+        switch (chWidth) {
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                mChannelWidth160MhzSupported = support;
+                break;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                mChannelWidth80p80MhzSupported = support;
+                break;
+            default:
+                Log.e(TAG, "setChannelWidthSupported called with Invalid channel width: "
+                        + chWidth);
+        }
+    }
+
+    /**
+     * Get maximum number of transmit spatial streams
+     *
+     * @return number of spatial streams
+     */
+    public int getMaxNumberTxSpatialStreams() {
+        return mMaxNumberTxSpatialStreams;
+    }
+
+    /**
+     * Set maximum number of transmit spatial streams
+     *
+     * @param streams number of spatial streams
+     *
+     * @hide
+     */
+    public void setMaxNumberTxSpatialStreams(int streams) {
+        mMaxNumberTxSpatialStreams = streams;
+    }
+
+    /**
+     * Get maximum number of receive spatial streams
+     *
+     * @return number of streams
+     */
+    public int getMaxNumberRxSpatialStreams() {
+        return mMaxNumberRxSpatialStreams;
+    }
+
+    /**
+     * Set maximum number of receive spatial streams
+     *
+     * @param streams number of streams
+     *
+     * @hide
+     */
+    public void setMaxNumberRxSpatialStreams(int streams) {
+        mMaxNumberRxSpatialStreams = streams;
+    }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof DeviceWiphyCapabilities)) {
+            return false;
+        }
+        DeviceWiphyCapabilities capa = (DeviceWiphyCapabilities) rhs;
+
+        return m80211nSupported == capa.m80211nSupported
+                && m80211acSupported == capa.m80211acSupported
+                && m80211axSupported == capa.m80211axSupported
+                && mChannelWidth160MhzSupported == capa.mChannelWidth160MhzSupported
+                && mChannelWidth80p80MhzSupported == capa.mChannelWidth80p80MhzSupported
+                && mMaxNumberTxSpatialStreams == capa.mMaxNumberTxSpatialStreams
+                && mMaxNumberRxSpatialStreams == capa.mMaxNumberRxSpatialStreams;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(m80211nSupported, m80211acSupported, m80211axSupported,
+                mChannelWidth160MhzSupported, mChannelWidth80p80MhzSupported,
+                mMaxNumberTxSpatialStreams, mMaxNumberRxSpatialStreams);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeBoolean(m80211nSupported);
+        out.writeBoolean(m80211acSupported);
+        out.writeBoolean(m80211axSupported);
+        out.writeBoolean(mChannelWidth160MhzSupported);
+        out.writeBoolean(mChannelWidth80p80MhzSupported);
+        out.writeInt(mMaxNumberTxSpatialStreams);
+        out.writeInt(mMaxNumberRxSpatialStreams);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("m80211nSupported:").append(m80211nSupported ? "Yes" : "No");
+        sb.append("m80211acSupported:").append(m80211acSupported ? "Yes" : "No");
+        sb.append("m80211axSupported:").append(m80211axSupported ? "Yes" : "No");
+        sb.append("mChannelWidth160MhzSupported: ")
+                .append(mChannelWidth160MhzSupported ? "Yes" : "No");
+        sb.append("mChannelWidth80p80MhzSupported: ")
+                .append(mChannelWidth80p80MhzSupported ? "Yes" : "No");
+        sb.append("mMaxNumberTxSpatialStreams: ").append(mMaxNumberTxSpatialStreams);
+        sb.append("mMaxNumberRxSpatialStreams: ").append(mMaxNumberRxSpatialStreams);
+
+        return sb.toString();
+    }
+
+    /** implement Parcelable interface */
+    public static final @NonNull Parcelable.Creator<DeviceWiphyCapabilities> CREATOR =
+            new Parcelable.Creator<DeviceWiphyCapabilities>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public DeviceWiphyCapabilities createFromParcel(Parcel in) {
+            DeviceWiphyCapabilities capabilities = new DeviceWiphyCapabilities();
+            capabilities.m80211nSupported = in.readBoolean();
+            capabilities.m80211acSupported = in.readBoolean();
+            capabilities.m80211axSupported = in.readBoolean();
+            capabilities.mChannelWidth160MhzSupported = in.readBoolean();
+            capabilities.mChannelWidth80p80MhzSupported = in.readBoolean();
+            capabilities.mMaxNumberTxSpatialStreams = in.readInt();
+            capabilities.mMaxNumberRxSpatialStreams = in.readInt();
+            return capabilities;
+        }
+
+        @Override
+        public DeviceWiphyCapabilities[] newArray(int size) {
+            return new DeviceWiphyCapabilities[size];
+        }
+    };
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/HiddenNetwork.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/HiddenNetwork.java
new file mode 100644
index 0000000..b1475b2
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/HiddenNetwork.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 android.net.wifi.nl80211;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * 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 Arrays.equals(ssid, network.ssid);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(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();
+            return result;
+        }
+
+        @Override
+        public HiddenNetwork[] newArray(int size) {
+            return new HiddenNetwork[size];
+        }
+    };
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/NativeScanResult.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/NativeScanResult.java
new file mode 100644
index 0000000..7a9b34b
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/NativeScanResult.java
@@ -0,0 +1,321 @@
+/*
+ * 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.nl80211;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Raw scan result data from the wificond daemon.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NativeScanResult implements Parcelable {
+    private static final String TAG = "NativeScanResult";
+
+    /** @hide */
+    @VisibleForTesting
+    public byte[] ssid;
+    /** @hide */
+    @VisibleForTesting
+    public byte[] bssid;
+    /** @hide */
+    @VisibleForTesting
+    public byte[] infoElement;
+    /** @hide */
+    @VisibleForTesting
+    public int frequency;
+    /** @hide */
+    @VisibleForTesting
+    public int signalMbm;
+    /** @hide */
+    @VisibleForTesting
+    public long tsf;
+    /** @hide */
+    @VisibleForTesting
+    @BssCapabilityBits public int capability;
+    /** @hide */
+    @VisibleForTesting
+    public boolean associated;
+    /** @hide */
+    @VisibleForTesting
+    public List<RadioChainInfo> radioChainInfos;
+
+    /**
+     * Returns the SSID raw byte array of the AP represented by this scan result.
+     *
+     * @return A byte array.
+     */
+    @NonNull public byte[] getSsid() {
+        return ssid;
+    }
+
+    /**
+     * Returns the MAC address (BSSID) of the AP represented by this scan result.
+     *
+     * @return a MacAddress or null on error.
+     */
+    @Nullable public MacAddress getBssid() {
+        try {
+            return MacAddress.fromBytes(bssid);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Illegal argument " + Arrays.toString(bssid), e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the raw bytes of the information element advertised by the AP represented by this
+     * scan result.
+     *
+     * @return A byte array, possibly null or containing an invalid TLV configuration.
+     */
+    @NonNull public byte[] getInformationElements() {
+        return infoElement;
+    }
+
+    /**
+     * Returns the frequency (in MHz) on which the AP represented by this scan result was observed.
+     *
+     * @return The frequency in MHz.
+     */
+    public int getFrequencyMhz() {
+        return frequency;
+    }
+
+    /**
+     * Return the signal strength of probe response/beacon in (100 * dBm).
+     *
+     * @return Signal strenght in (100 * dBm).
+     */
+    public int getSignalMbm() {
+        return signalMbm;
+    }
+
+    /**
+     * Return the TSF (Timing Synchronization Function) of the received probe response/beacon.
+     * @return
+     */
+    public long getTsf() {
+        return tsf;
+    }
+
+    /**
+     * Return a boolean indicating whether or not we're associated to the AP represented by this
+     * scan result.
+     *
+     * @return A boolean indicating association.
+     */
+    public boolean isAssociated() {
+        return associated;
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"BSS_CAPABILITY_"},
+            value = {BSS_CAPABILITY_ESS,
+                    BSS_CAPABILITY_IBSS,
+                    BSS_CAPABILITY_CF_POLLABLE,
+                    BSS_CAPABILITY_CF_POLL_REQUEST,
+                    BSS_CAPABILITY_PRIVACY,
+                    BSS_CAPABILITY_SHORT_PREAMBLE,
+                    BSS_CAPABILITY_PBCC,
+                    BSS_CAPABILITY_CHANNEL_AGILITY,
+                    BSS_CAPABILITY_SPECTRUM_MANAGEMENT,
+                    BSS_CAPABILITY_QOS,
+                    BSS_CAPABILITY_SHORT_SLOT_TIME,
+                    BSS_CAPABILITY_APSD,
+                    BSS_CAPABILITY_RADIO_MANAGEMENT,
+                    BSS_CAPABILITY_DSSS_OFDM,
+                    BSS_CAPABILITY_DELAYED_BLOCK_ACK,
+                    BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK,
+                    BSS_CAPABILITY_DMG_ESS,
+                    BSS_CAPABILITY_DMG_IBSS
+            })
+    public @interface BssCapabilityBits { }
+
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): ESS.
+     */
+    public static final int BSS_CAPABILITY_ESS = 0x1 << 0;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): IBSS.
+     */
+    public static final int BSS_CAPABILITY_IBSS = 0x1 << 1;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF Pollable.
+     */
+    public static final int BSS_CAPABILITY_CF_POLLABLE = 0x1 << 2;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF-Poll Request.
+     */
+    public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 0x1 << 3;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Privacy.
+     */
+    public static final int BSS_CAPABILITY_PRIVACY = 0x1 << 4;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Preamble.
+     */
+    public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 0x1 << 5;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): PBCC.
+     */
+    public static final int BSS_CAPABILITY_PBCC = 0x1 << 6;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Channel Agility.
+     */
+    public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 0x1 << 7;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Spectrum Management.
+     */
+    public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 0x1 << 8;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): QoS.
+     */
+    public static final int BSS_CAPABILITY_QOS = 0x1 << 9;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Slot Time.
+     */
+    public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 0x1 << 10;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): APSD.
+     */
+    public static final int BSS_CAPABILITY_APSD = 0x1 << 11;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Radio Management.
+     */
+    public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 0x1 << 12;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DSSS-OFDM.
+     */
+    public static final int BSS_CAPABILITY_DSSS_OFDM = 0x1 << 13;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Delayed Block Ack.
+     */
+    public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 0x1 << 14;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Immediate Block Ack.
+     */
+    public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 0x1 << 15;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DMG ESS.
+     * In DMG bits 0 and 1 are parsed together, where ESS=0x3 and IBSS=0x1
+     */
+    public static final int BSS_CAPABILITY_DMG_ESS = 0x3;
+    /**
+     * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DMG IBSS.
+     */
+    public static final int BSS_CAPABILITY_DMG_IBSS = 0x1;
+
+    /**
+     *  Returns the capabilities of the AP repseresented by this scan result as advertised in the
+     *  received probe response or beacon.
+     *
+     *  This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: one
+     *  of the {@code BSS_CAPABILITY_*} flags.
+     *
+     * @return a bit mask of capabilities.
+     */
+    @BssCapabilityBits public int getCapabilities() {
+        return capability;
+    }
+
+    /**
+     * Returns details of the signal received on each radio chain for the AP represented by this
+     * scan result in a list of {@link RadioChainInfo} elements.
+     *
+     * @return A list of {@link RadioChainInfo} - possibly empty in case of error.
+     */
+    @NonNull public List<RadioChainInfo> getRadioChainInfos() {
+        return radioChainInfos;
+    }
+
+    /**
+     * Construct an empty native scan result.
+     */
+    public NativeScanResult() { }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeByteArray(ssid);
+        out.writeByteArray(bssid);
+        out.writeByteArray(infoElement);
+        out.writeInt(frequency);
+        out.writeInt(signalMbm);
+        out.writeLong(tsf);
+        out.writeInt(capability);
+        out.writeInt(associated ? 1 : 0);
+        out.writeTypedList(radioChainInfos);
+    }
+
+    /** implement Parcelable interface */
+    @NonNull 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();
+            if (result.ssid == null) {
+                result.ssid = new byte[0];
+            }
+            result.bssid = in.createByteArray();
+            if (result.bssid == null) {
+                result.bssid = new byte[0];
+            }
+            result.infoElement = in.createByteArray();
+            if (result.infoElement == null) {
+                result.infoElement = new byte[0];
+            }
+            result.frequency = in.readInt();
+            result.signalMbm = in.readInt();
+            result.tsf = in.readLong();
+            result.capability = in.readInt();
+            result.associated = (in.readInt() != 0);
+            result.radioChainInfos = new ArrayList<>();
+            in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR);
+            return result;
+        }
+
+        @Override
+        public NativeScanResult[] newArray(int size) {
+            return new NativeScanResult[size];
+        }
+    };
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/NativeWifiClient.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/NativeWifiClient.java
new file mode 100644
index 0000000..984d7d0
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/NativeWifiClient.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.nl80211;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.MacAddress;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Structure providing information about clients (STAs) associated with a SoftAp.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NativeWifiClient implements Parcelable {
+    private final MacAddress mMacAddress;
+
+    /**
+     * The MAC address of the client (STA) represented by this object. The MAC address may be null
+     * in case of an error.
+     */
+    @Nullable public MacAddress getMacAddress() {
+        return mMacAddress;
+    }
+
+    /**
+     * Construct a native Wi-Fi client.
+     */
+    public NativeWifiClient(@Nullable MacAddress macAddress) {
+        this.mMacAddress = macAddress;
+    }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof NativeWifiClient)) {
+            return false;
+        }
+        NativeWifiClient other = (NativeWifiClient) rhs;
+        return Objects.equals(mMacAddress, other.mMacAddress);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return mMacAddress.hashCode();
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeByteArray(mMacAddress.toByteArray());
+    }
+
+    /** implement Parcelable interface */
+    @NonNull public static final Parcelable.Creator<NativeWifiClient> CREATOR =
+            new Parcelable.Creator<NativeWifiClient>() {
+                @Override
+                public NativeWifiClient createFromParcel(Parcel in) {
+                    MacAddress macAddress;
+                    try {
+                        macAddress = MacAddress.fromBytes(in.createByteArray());
+                    } catch (IllegalArgumentException e) {
+                        macAddress = null;
+                    }
+                    return new NativeWifiClient(macAddress);
+                }
+
+                @Override
+                public NativeWifiClient[] newArray(int size) {
+                    return new NativeWifiClient[size];
+                }
+            };
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/PnoNetwork.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/PnoNetwork.java
new file mode 100644
index 0000000..e8eff09
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/PnoNetwork.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 android.net.wifi.nl80211;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Configuration for a PNO (preferred network offload) network used in {@link PnoSettings}. A PNO
+ * network allows configuration of a specific network to search for.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PnoNetwork implements Parcelable {
+    private boolean mIsHidden;
+    private byte[] mSsid;
+    private int[] mFrequencies;
+
+    /**
+     * Indicates whether the PNO network configuration is for a hidden SSID - i.e. a network which
+     * does not broadcast its SSID and must be queried explicitly.
+     *
+     * @return True if the configuration is for a hidden network, false otherwise.
+     */
+    public boolean isHidden() {
+        return mIsHidden;
+    }
+
+    /**
+     * Configure whether the PNO network configuration is for a hidden SSID - i.e. a network which
+     * does not broadcast its SSID and must be queried explicitly.
+     *
+     * @param isHidden True if the configuration is for a hidden network, false otherwise.
+     */
+    public void setHidden(boolean isHidden) {
+        mIsHidden = isHidden;
+    }
+
+    /**
+     * Get the raw bytes for the SSID of the PNO network being scanned for.
+     *
+     * @return A byte array.
+     */
+    @NonNull public byte[] getSsid() {
+        return mSsid;
+    }
+
+    /**
+     * Set the raw bytes for the SSID of the PNO network being scanned for.
+     *
+     * @param ssid A byte array.
+     */
+    public void setSsid(@NonNull byte[] ssid) {
+        if (ssid == null) {
+            throw new IllegalArgumentException("null argument");
+        }
+        this.mSsid = ssid;
+    }
+
+    /**
+     * Get the frequencies (in MHz) on which to PNO scan for the current network is being searched
+     * for. A null return (i.e. no frequencies configured) indicates that the network is search for
+     * on all supported frequencies.
+     *
+     * @return A array of frequencies (in MHz), a null indicates no configured frequencies.
+     */
+    @NonNull public int[] getFrequenciesMhz() {
+        return mFrequencies;
+    }
+
+    /**
+     * Set the frequencies (in MHz) on which to PNO scan for the current network is being searched
+     * for. A null configuration (i.e. no frequencies configured) indicates that the network is
+     * search for on all supported frequencies.
+     *
+     * @param frequenciesMhz an array of frequencies (in MHz), null indicating no configured
+     *                       frequencies.
+     */
+    public void setFrequenciesMhz(@NonNull int[] frequenciesMhz) {
+        if (frequenciesMhz == null) {
+            throw new IllegalArgumentException("null argument");
+        }
+        this.mFrequencies = frequenciesMhz;
+    }
+
+    /** Construct an uninitialized PnoNetwork object */
+    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 Arrays.equals(mSsid, network.mSsid)
+                && Arrays.equals(mFrequencies, network.mFrequencies)
+                && mIsHidden == network.mIsHidden;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mIsHidden,
+                Arrays.hashCode(mSsid),
+                Arrays.hashCode(mFrequencies));
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mIsHidden ? 1 : 0);
+        out.writeByteArray(mSsid);
+        out.writeIntArray(mFrequencies);
+    }
+
+    /** implement Parcelable interface */
+    @NonNull public static final Parcelable.Creator<PnoNetwork> CREATOR =
+            new Parcelable.Creator<PnoNetwork>() {
+        @Override
+        public PnoNetwork createFromParcel(Parcel in) {
+            PnoNetwork result = new PnoNetwork();
+            result.mIsHidden = in.readInt() != 0 ? true : false;
+            result.mSsid = in.createByteArray();
+            if (result.mSsid == null) {
+                result.mSsid = new byte[0];
+            }
+            result.mFrequencies = in.createIntArray();
+            if (result.mFrequencies == null) {
+                result.mFrequencies = new int[0];
+            }
+            return result;
+        }
+
+        @Override
+        public PnoNetwork[] newArray(int size) {
+            return new PnoNetwork[size];
+        }
+    };
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/PnoSettings.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/PnoSettings.java
new file mode 100644
index 0000000..00ebe62
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/PnoSettings.java
@@ -0,0 +1,209 @@
+/*
+ * 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.nl80211;
+
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Configuration for a PNO (preferred network offload). A mechanism by which scans are offloaded
+ * from the host device to the Wi-Fi chip.
+ *
+ * @hide
+ */
+@SystemApi
+public final class PnoSettings implements Parcelable {
+    private long mIntervalMs;
+    private int mMin2gRssi;
+    private int mMin5gRssi;
+    private int mMin6gRssi;
+    private List<PnoNetwork> mPnoNetworks;
+
+    /** Construct an uninitialized PnoSettings object */
+    public PnoSettings() { }
+
+    /**
+     * Get the requested PNO scan interval in milliseconds.
+     *
+     * @return An interval in milliseconds.
+     */
+    public @DurationMillisLong long getIntervalMillis() {
+        return mIntervalMs;
+    }
+
+    /**
+     * Set the requested PNO scan interval in milliseconds.
+     *
+     * @param intervalMillis An interval in milliseconds.
+     */
+    public void setIntervalMillis(@DurationMillisLong long intervalMillis) {
+        this.mIntervalMs = intervalMillis;
+    }
+
+    /**
+     * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the
+     * 2.4GHz band.
+     *
+     * @return An RSSI value in dBm.
+     */
+    public int getMin2gRssiDbm() {
+        return mMin2gRssi;
+    }
+
+    /**
+     * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in
+     * the 2.4GHz band.
+     *
+     * @param min2gRssiDbm An RSSI value in dBm.
+     */
+    public void setMin2gRssiDbm(int min2gRssiDbm) {
+        this.mMin2gRssi = min2gRssiDbm;
+    }
+
+    /**
+     * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the
+     * 5GHz band.
+     *
+     * @return An RSSI value in dBm.
+     */
+    public int getMin5gRssiDbm() {
+        return mMin5gRssi;
+    }
+
+    /**
+     * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in
+     * the 5GHz band.
+     *
+     * @param min5gRssiDbm An RSSI value in dBm.
+     */
+    public void setMin5gRssiDbm(int min5gRssiDbm) {
+        this.mMin5gRssi = min5gRssiDbm;
+    }
+
+    /**
+     * Get the requested minimum RSSI threshold (in dBm) for APs to report in scan results in the
+     * 6GHz band.
+     *
+     * @return An RSSI value in dBm.
+     */
+    public int getMin6gRssiDbm() {
+        return mMin6gRssi;
+    }
+
+    /**
+     * Set the requested minimum RSSI threshold (in dBm) for APs to report in scan scan results in
+     * the 6GHz band.
+     *
+     * @param min6gRssiDbm An RSSI value in dBm.
+     */
+    public void setMin6gRssiDbm(int min6gRssiDbm) {
+        this.mMin6gRssi = min6gRssiDbm;
+    }
+
+    /**
+     * Return the configured list of specific networks to search for in a PNO scan.
+     *
+     * @return A list of {@link PnoNetwork} objects, possibly empty if non configured.
+     */
+    @NonNull public List<PnoNetwork> getPnoNetworks() {
+        return mPnoNetworks;
+    }
+
+    /**
+     * Set the list of specified networks to scan for in a PNO scan. The networks (APs) are
+     * specified using {@link PnoNetwork}s. An empty list indicates that all networks are scanned
+     * for.
+     *
+     * @param pnoNetworks A (possibly empty) list of {@link PnoNetwork} objects.
+     */
+    public void setPnoNetworks(@NonNull List<PnoNetwork> pnoNetworks) {
+        this.mPnoNetworks = pnoNetworks;
+    }
+
+    /** 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 mIntervalMs == settings.mIntervalMs
+                && mMin2gRssi == settings.mMin2gRssi
+                && mMin5gRssi == settings.mMin5gRssi
+                && mMin6gRssi == settings.mMin6gRssi
+                && mPnoNetworks.equals(settings.mPnoNetworks);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIntervalMs, mMin2gRssi, mMin5gRssi, mMin6gRssi, mPnoNetworks);
+    }
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flag| is ignored.
+     **/
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeLong(mIntervalMs);
+        out.writeInt(mMin2gRssi);
+        out.writeInt(mMin5gRssi);
+        out.writeInt(mMin6gRssi);
+        out.writeTypedList(mPnoNetworks);
+    }
+
+    /** implement Parcelable interface */
+    @NonNull public static final Parcelable.Creator<PnoSettings> CREATOR =
+            new Parcelable.Creator<PnoSettings>() {
+        @Override
+        public PnoSettings createFromParcel(Parcel in) {
+            PnoSettings result = new PnoSettings();
+            result.mIntervalMs = in.readLong();
+            result.mMin2gRssi = in.readInt();
+            result.mMin5gRssi = in.readInt();
+            result.mMin6gRssi = in.readInt();
+
+            result.mPnoNetworks = new ArrayList<>();
+            in.readTypedList(result.mPnoNetworks, PnoNetwork.CREATOR);
+
+            return result;
+        }
+
+        @Override
+        public PnoSettings[] newArray(int size) {
+            return new PnoSettings[size];
+        }
+    };
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/RadioChainInfo.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/RadioChainInfo.java
new file mode 100644
index 0000000..2c12163
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/RadioChainInfo.java
@@ -0,0 +1,125 @@
+/*
+ * 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.nl80211;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * A class representing the radio chains of the Wi-Fi modems. Use to provide raw information about
+ * signals received on different radio chains.
+ *
+ * @hide
+ */
+@SystemApi
+public final class RadioChainInfo implements Parcelable {
+    private static final String TAG = "RadioChainInfo";
+
+    /** @hide */
+    @VisibleForTesting
+    public int chainId;
+    /** @hide */
+    @VisibleForTesting
+    public int level;
+
+    /**
+     * Return an identifier for this radio chain. This is an arbitrary ID which is consistent for
+     * the same device.
+     *
+     * @return The radio chain ID.
+     */
+    public int getChainId() {
+        return chainId;
+    }
+
+    /**
+     * Returns the detected signal level on this radio chain in dBm (aka RSSI).
+     *
+     * @return A signal level in dBm.
+     */
+    public int getLevelDbm() {
+        return level;
+    }
+
+    /**
+     * Construct a RadioChainInfo.
+     */
+    public RadioChainInfo(int chainId, int level) {
+        this.chainId = chainId;
+        this.level = level;
+    }
+
+    /** override comparator */
+    @Override
+    public boolean equals(Object rhs) {
+        if (this == rhs) return true;
+        if (!(rhs instanceof RadioChainInfo)) {
+            return false;
+        }
+        RadioChainInfo chainInfo = (RadioChainInfo) rhs;
+        if (chainInfo == null) {
+            return false;
+        }
+        return chainId == chainInfo.chainId && level == chainInfo.level;
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(chainId, level);
+    }
+
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(chainId);
+        out.writeInt(level);
+    }
+
+    /** implement Parcelable interface */
+    @NonNull public static final Parcelable.Creator<RadioChainInfo> CREATOR =
+            new Parcelable.Creator<RadioChainInfo>() {
+        /**
+         * Caller is responsible for providing a valid parcel.
+         */
+        @Override
+        public RadioChainInfo createFromParcel(Parcel in) {
+            return new RadioChainInfo(in.readInt(), in.readInt());
+        }
+
+        @Override
+        public RadioChainInfo[] newArray(int size) {
+            return new RadioChainInfo[size];
+        }
+    };
+}
diff --git a/wifi/non-updatable/java/src/android/net/wifi/nl80211/SingleScanSettings.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/SingleScanSettings.java
new file mode 100644
index 0000000..24b1854
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/SingleScanSettings.java
@@ -0,0 +1,118 @@
+/*
+ * 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.nl80211;
+
+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 int scanType;
+    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 scanType == settings.scanType
+                && channelSettings.equals(settings.channelSettings)
+                && hiddenNetworks.equals(settings.hiddenNetworks);
+    }
+
+    /** override hash code */
+    @Override
+    public int hashCode() {
+        return Objects.hash(scanType, channelSettings, hiddenNetworks);
+    }
+
+
+    /** implement Parcelable interface */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private static boolean isValidScanType(int scanType) {
+        return scanType == IWifiScannerImpl.SCAN_TYPE_LOW_SPAN
+                || scanType == IWifiScannerImpl.SCAN_TYPE_LOW_POWER
+                || scanType == IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+    }
+
+    /**
+     * implement Parcelable interface
+     * |flags| is ignored.
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (!isValidScanType(scanType)) {
+            Log.wtf(TAG, "Invalid scan type " + scanType);
+        }
+        out.writeInt(scanType);
+        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.scanType = in.readInt();
+            if (!isValidScanType(result.scanType)) {
+                Log.wtf(TAG, "Invalid scan type " + result.scanType);
+            }
+            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/wifi/non-updatable/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/non-updatable/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
new file mode 100644
index 0000000..db2eb99
--- /dev/null
+++ b/wifi/non-updatable/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -0,0 +1,1336 @@
+/*
+ * 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 android.net.wifi.nl80211;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.net.wifi.SoftApInfo;
+import android.net.wifi.WifiAnnotations;
+import android.net.wifi.WifiScanner;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework - used
+ * to encapsulate the Wi-Fi 80211nl management interface. The
+ * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.WIFI_NL80211_SERVICE)
+public class WifiNl80211Manager {
+    private static final String TAG = "WifiNl80211Manager";
+    private boolean mVerboseLoggingEnabled = false;
+
+    /**
+     * The {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
+     * timeout, in milliseconds, after which
+     * {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason
+     * {@link #SEND_MGMT_FRAME_ERROR_TIMEOUT}.
+     */
+    private static final int SEND_MGMT_FRAME_TIMEOUT_MS = 1000;
+
+    private static final String TIMEOUT_ALARM_TAG = TAG + " Send Management Frame Timeout";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SCAN_TYPE_"},
+            value = {SCAN_TYPE_SINGLE_SCAN,
+                    SCAN_TYPE_PNO_SCAN})
+    public @interface ScanResultType {}
+
+    /**
+     * Specifies a scan type: single scan initiated by the framework. Can be used in
+     * {@link #getScanResults(String, int)} to specify the type of scan result to fetch.
+     */
+    public static final int SCAN_TYPE_SINGLE_SCAN = 0;
+
+    /**
+     * Specifies a scan type: PNO scan. Can be used in {@link #getScanResults(String, int)} to
+     * specify the type of scan result to fetch.
+     */
+    public static final int SCAN_TYPE_PNO_SCAN = 1;
+
+    private AlarmManager mAlarmManager;
+    private Handler mEventHandler;
+
+    // Cached wificond binder handlers.
+    private IWificond mWificond;
+    private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
+    private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
+    private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
+    private HashMap<String, IScanEvent> mScanEventHandlers = new HashMap<>();
+    private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
+    private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
+    private Runnable mDeathEventHandler;
+    /**
+     * Ensures that no more than one sendMgmtFrame operation runs concurrently.
+     */
+    private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
+
+    /**
+     * Interface used when waiting for scans to be completed (with results).
+     */
+    public interface ScanEventCallback {
+        /**
+         * Called when scan results are available. Scans results should then be obtained from
+         * {@link #getScanResults(String, int)}.
+         */
+        void onScanResultReady();
+
+        /**
+         * Called when a scan has failed.
+         */
+        void onScanFailed();
+    }
+
+    /**
+     * Interface for a callback to provide information about PNO scan request requested with
+     * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}. Note that the
+     * callback are for the status of the request - not the scan itself. The results of the scan
+     * are returned with {@link ScanEventCallback}.
+     */
+    public interface PnoScanRequestCallback {
+        /**
+         * Called when a PNO scan request has been successfully submitted.
+         */
+        void onPnoRequestSucceeded();
+
+        /**
+         * Called when a PNO scan request fails.
+         */
+        void onPnoRequestFailed();
+    }
+
+    private class ScanEventHandler extends IScanEvent.Stub {
+        private Executor mExecutor;
+        private ScanEventCallback mCallback;
+
+        ScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void OnScanResultReady() {
+            Log.d(TAG, "Scan result ready event");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onScanResultReady());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void OnScanFailed() {
+            Log.d(TAG, "Scan failed event");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onScanFailed());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    /**
+     * Result of a signal poll requested using {@link #signalPoll(String)}.
+     */
+    public static class SignalPollResult {
+        /** @hide */
+        public SignalPollResult(int currentRssiDbm, int txBitrateMbps, int rxBitrateMbps,
+                int associationFrequencyMHz) {
+            this.currentRssiDbm = currentRssiDbm;
+            this.txBitrateMbps = txBitrateMbps;
+            this.rxBitrateMbps = rxBitrateMbps;
+            this.associationFrequencyMHz = associationFrequencyMHz;
+        }
+
+        /**
+         * RSSI value in dBM.
+         */
+        public final int currentRssiDbm;
+
+        /**
+         * Transmission bit rate in Mbps.
+         */
+        public final int txBitrateMbps;
+
+        /**
+         * Last received packet bit rate in Mbps.
+         */
+        public final int rxBitrateMbps;
+
+        /**
+         * Association frequency in MHz.
+         */
+        public final int associationFrequencyMHz;
+    }
+
+    /**
+     * Transmission counters obtained using {@link #getTxPacketCounters(String)}.
+     */
+    public static class TxPacketCounters {
+        /** @hide */
+        public TxPacketCounters(int txPacketSucceeded, int txPacketFailed) {
+            this.txPacketSucceeded = txPacketSucceeded;
+            this.txPacketFailed = txPacketFailed;
+        }
+
+        /**
+         * Number of successfully transmitted packets.
+         */
+        public final int txPacketSucceeded;
+
+        /**
+         * Number of packet transmission failures.
+         */
+        public final int txPacketFailed;
+    }
+
+    /**
+     * Callbacks for SoftAp interface registered using
+     * {@link #registerApCallback(String, Executor, SoftApCallback)}.
+     *
+     * @deprecated The usage is replaced by vendor HAL
+     * {@code android.hardware.wifi.hostapd.V1_3.IHostapdCallback}.
+     */
+    @Deprecated
+    public interface SoftApCallback {
+        /**
+         * Invoked when there is a fatal failure and the SoftAp is shutdown.
+         */
+        void onFailure();
+
+        /**
+         * Invoked when there is a change in the associated station (STA).
+         * @param client Information about the client whose status has changed.
+         * @param isConnected Indication as to whether the client is connected (true), or
+         *                    disconnected (false).
+         */
+        void onConnectedClientsChanged(@NonNull NativeWifiClient client, boolean isConnected);
+
+        /**
+         * Invoked when a channel switch event happens - i.e. the SoftAp is moved to a different
+         * channel. Also called on initial registration.
+         * @param frequencyMhz The new frequency of the SoftAp. A value of 0 is invalid and is an
+         *                     indication that the SoftAp is not enabled.
+         * @param bandwidth The new bandwidth of the SoftAp.
+         */
+        void onSoftApChannelSwitched(int frequencyMhz, @WifiAnnotations.Bandwidth int bandwidth);
+    }
+
+    /**
+     * Callback to notify the results of a
+     * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} call.
+     * Note: no callbacks will be triggered if the interface dies while sending a frame.
+     */
+    public interface SendMgmtFrameCallback {
+        /**
+         * Called when the management frame was successfully sent and ACKed by the recipient.
+         * @param elapsedTimeMs The elapsed time between when the management frame was sent and when
+         *                      the ACK was processed, in milliseconds, as measured by wificond.
+         *                      This includes the time that the send frame spent queuing before it
+         *                      was sent, any firmware retries, and the time the received ACK spent
+         *                      queuing before it was processed.
+         */
+        void onAck(int elapsedTimeMs);
+
+        /**
+         * Called when the send failed.
+         * @param reason The error code for the failure.
+         */
+        void onFailure(@SendMgmtFrameError int reason);
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"},
+            value = {SEND_MGMT_FRAME_ERROR_UNKNOWN,
+                    SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED,
+                    SEND_MGMT_FRAME_ERROR_NO_ACK,
+                    SEND_MGMT_FRAME_ERROR_TIMEOUT,
+                    SEND_MGMT_FRAME_ERROR_ALREADY_STARTED})
+    public @interface SendMgmtFrameError {}
+
+    // Send management frame error codes
+
+    /**
+     * Unknown error occurred during call to
+     * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1;
+
+    /**
+     * Specifying the MCS rate in
+     * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)} is not
+     * supported by this device.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2;
+
+    /**
+     * Driver reported that no ACK was received for the frame transmitted using
+     * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3;
+
+    /**
+     * Error code for when the driver fails to report on the status of the frame sent by
+     * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
+     * after {@link #SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4;
+
+    /**
+     * An existing call to
+     * {@link #sendMgmtFrame(String, byte[], int, Executor, SendMgmtFrameCallback)}
+     * is in progress. Another frame cannot be sent until the first call completes.
+     */
+    public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
+
+    /** @hide */
+    public WifiNl80211Manager(Context context) {
+        mAlarmManager = context.getSystemService(AlarmManager.class);
+        mEventHandler = new Handler(context.getMainLooper());
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public WifiNl80211Manager(Context context, IWificond wificond) {
+        this(context);
+        mWificond = wificond;
+    }
+
+    private class PnoScanEventHandler extends IPnoScanEvent.Stub {
+        private Executor mExecutor;
+        private ScanEventCallback mCallback;
+
+        PnoScanEventHandler(@NonNull Executor executor, @NonNull ScanEventCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void OnPnoNetworkFound() {
+            Log.d(TAG, "Pno scan result event");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onScanResultReady());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void OnPnoScanFailed() {
+            Log.d(TAG, "Pno Scan failed event");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onScanFailed());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    /**
+     * Listener for AP Interface events.
+     */
+    private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+        private Executor mExecutor;
+        private SoftApCallback mSoftApListener;
+
+        ApInterfaceEventCallback(Executor executor, SoftApCallback listener) {
+            mExecutor = executor;
+            mSoftApListener = listener;
+        }
+
+        @Override
+        public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "onConnectedClientsChanged called with "
+                        + client.getMacAddress() + " isConnected: " + isConnected);
+            }
+
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(
+                        () -> mSoftApListener.onConnectedClientsChanged(client, isConnected));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onSoftApChannelSwitched(int frequency, int bandwidth) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mSoftApListener.onSoftApChannelSwitched(frequency,
+                        toFrameworkBandwidth(bandwidth)));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        private @WifiAnnotations.Bandwidth int toFrameworkBandwidth(int bandwidth) {
+            switch(bandwidth) {
+                case IApInterfaceEventCallback.BANDWIDTH_INVALID:
+                    return SoftApInfo.CHANNEL_WIDTH_INVALID;
+                case IApInterfaceEventCallback.BANDWIDTH_20_NOHT:
+                    return SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT;
+                case IApInterfaceEventCallback.BANDWIDTH_20:
+                    return SoftApInfo.CHANNEL_WIDTH_20MHZ;
+                case IApInterfaceEventCallback.BANDWIDTH_40:
+                    return SoftApInfo.CHANNEL_WIDTH_40MHZ;
+                case IApInterfaceEventCallback.BANDWIDTH_80:
+                    return SoftApInfo.CHANNEL_WIDTH_80MHZ;
+                case IApInterfaceEventCallback.BANDWIDTH_80P80:
+                    return SoftApInfo.CHANNEL_WIDTH_80MHZ_PLUS_MHZ;
+                case IApInterfaceEventCallback.BANDWIDTH_160:
+                    return SoftApInfo.CHANNEL_WIDTH_160MHZ;
+                default:
+                    return SoftApInfo.CHANNEL_WIDTH_INVALID;
+            }
+        }
+    }
+
+    /**
+     * Callback triggered by wificond.
+     */
+    private class SendMgmtFrameEvent extends ISendMgmtFrameEvent.Stub {
+        private Executor mExecutor;
+        private SendMgmtFrameCallback mCallback;
+        private AlarmManager.OnAlarmListener mTimeoutCallback;
+        /**
+         * ensures that mCallback is only called once
+         */
+        private boolean mWasCalled;
+
+        private void runIfFirstCall(Runnable r) {
+            if (mWasCalled) return;
+            mWasCalled = true;
+
+            mSendMgmtFrameInProgress.set(false);
+            r.run();
+        }
+
+        SendMgmtFrameEvent(@NonNull Executor executor, @NonNull SendMgmtFrameCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+            // called in main thread
+            mTimeoutCallback = () -> runIfFirstCall(() -> {
+                if (mVerboseLoggingEnabled) {
+                    Log.e(TAG, "Timed out waiting for ACK");
+                }
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            });
+            mWasCalled = false;
+
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime() + SEND_MGMT_FRAME_TIMEOUT_MS,
+                    TIMEOUT_ALARM_TAG, mTimeoutCallback, mEventHandler);
+        }
+
+        // called in binder thread
+        @Override
+        public void OnAck(int elapsedTimeMs) {
+            // post to main thread
+            mEventHandler.post(() -> runIfFirstCall(() -> {
+                mAlarmManager.cancel(mTimeoutCallback);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onAck(elapsedTimeMs));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }));
+        }
+
+        // called in binder thread
+        @Override
+        public void OnFailure(int reason) {
+            // post to main thread
+            mEventHandler.post(() -> runIfFirstCall(() -> {
+                mAlarmManager.cancel(mTimeoutCallback);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> mCallback.onFailure(reason));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }));
+        }
+    }
+
+    /**
+     * Called by the binder subsystem upon remote object death.
+     * Invoke all the register death handlers and clear state.
+     * @hide
+     */
+    @VisibleForTesting
+    public void binderDied() {
+        mEventHandler.post(() -> {
+            Log.e(TAG, "Wificond died!");
+            clearState();
+            // Invalidate the global wificond handle on death. Will be refreshed
+            // on the next setup call.
+            mWificond = null;
+            if (mDeathEventHandler != null) {
+                mDeathEventHandler.run();
+            }
+        });
+    }
+
+    /**
+     * Enable or disable verbose logging of the WifiNl80211Manager module.
+     * @param enable True to enable verbose logging. False to disable verbose logging.
+     */
+    public void enableVerboseLogging(boolean enable) {
+        mVerboseLoggingEnabled = enable;
+    }
+
+    /**
+     * Register a death notification for the WifiNl80211Manager which acts as a proxy for the
+     * wificond daemon (i.e. the death listener will be called when and if the wificond daemon
+     * dies).
+     *
+     * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies.
+     */
+    public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) {
+        if (mDeathEventHandler != null) {
+            Log.e(TAG, "Death handler already present");
+        }
+        mDeathEventHandler = deathEventHandler;
+    }
+
+    /**
+     * Helper method to retrieve the global wificond handle and register for
+     * death notifications.
+     */
+    private boolean retrieveWificondAndRegisterForDeath() {
+        if (mWificond != null) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Wificond handle already retrieved");
+            }
+            // We already have a wificond handle.
+            return true;
+        }
+        IBinder binder = ServiceManager.getService(Context.WIFI_NL80211_SERVICE);
+        mWificond = IWificond.Stub.asInterface(binder);
+        if (mWificond == null) {
+            Log.e(TAG, "Failed to get reference to wificond");
+            return false;
+        }
+        try {
+            mWificond.asBinder().linkToDeath(() -> binderDied(), 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register death notification for wificond");
+            // The remote has already died.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Set up an interface for client (STA) mode.
+     *
+     * @param ifaceName Name of the interface to configure.
+     * @param executor The Executor on which to execute the callbacks.
+     * @param scanCallback A callback for framework initiated scans.
+     * @param pnoScanCallback A callback for PNO (offloaded) scans.
+     * @return true on success.
+     */
+    public boolean setupInterfaceForClientMode(@NonNull String ifaceName,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) {
+        Log.d(TAG, "Setting up interface for client mode: " + ifaceName);
+        if (!retrieveWificondAndRegisterForDeath()) {
+            return false;
+        }
+
+        if (scanCallback == null || pnoScanCallback == null || executor == null) {
+            Log.e(TAG, "setupInterfaceForClientMode invoked with null callbacks");
+            return false;
+        }
+
+        IClientInterface clientInterface = null;
+        try {
+            clientInterface = mWificond.createClientInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to get IClientInterface due to remote exception");
+            return false;
+        }
+
+        if (clientInterface == null) {
+            Log.e(TAG, "Could not get IClientInterface instance from wificond");
+            return false;
+        }
+        Binder.allowBlocking(clientInterface.asBinder());
+
+        // Refresh Handlers
+        mClientInterfaces.put(ifaceName, clientInterface);
+        try {
+            IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
+            if (wificondScanner == null) {
+                Log.e(TAG, "Failed to get WificondScannerImpl");
+                return false;
+            }
+            mWificondScanners.put(ifaceName, wificondScanner);
+            Binder.allowBlocking(wificondScanner.asBinder());
+            ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback);
+            mScanEventHandlers.put(ifaceName,  scanEventHandler);
+            wificondScanner.subscribeScanEvents(scanEventHandler);
+            PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor,
+                    pnoScanCallback);
+            mPnoScanEventHandlers.put(ifaceName,  pnoScanEventHandler);
+            wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
+        }
+
+        return true;
+    }
+
+    /**
+     * Tear down a specific client (STA) interface configured using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
+     *
+     * @param ifaceName Name of the interface to tear down.
+     * @return Returns true on success, false on failure (e.g. when called before an interface was
+     * set up).
+     */
+    public boolean tearDownClientInterface(@NonNull String ifaceName) {
+        if (getClientInterface(ifaceName) == null) {
+            Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName);
+            return false;
+        }
+        try {
+            IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName);
+            if (scannerImpl != null) {
+                scannerImpl.unsubscribeScanEvents();
+                scannerImpl.unsubscribePnoScanEvents();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception");
+            return false;
+        }
+
+        if (mWificond == null) {
+            Log.e(TAG, "tearDownClientInterface: mWificond binder is null! Did wificond die?");
+            return false;
+        }
+
+        boolean success;
+        try {
+            success = mWificond.tearDownClientInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to teardown client interface due to remote exception");
+            return false;
+        }
+        if (!success) {
+            Log.e(TAG, "Failed to teardown client interface");
+            return false;
+        }
+
+        mClientInterfaces.remove(ifaceName);
+        mWificondScanners.remove(ifaceName);
+        mScanEventHandlers.remove(ifaceName);
+        mPnoScanEventHandlers.remove(ifaceName);
+        return true;
+    }
+
+    /**
+     * Set up interface as a Soft AP.
+     *
+     * @param ifaceName Name of the interface to configure.
+     * @return true on success.
+     */
+    public boolean setupInterfaceForSoftApMode(@NonNull String ifaceName) {
+        Log.d(TAG, "Setting up interface for soft ap mode for iface=" + ifaceName);
+        if (!retrieveWificondAndRegisterForDeath()) {
+            return false;
+        }
+
+        IApInterface apInterface = null;
+        try {
+            apInterface = mWificond.createApInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to get IApInterface due to remote exception");
+            return false;
+        }
+
+        if (apInterface == null) {
+            Log.e(TAG, "Could not get IApInterface instance from wificond");
+            return false;
+        }
+        Binder.allowBlocking(apInterface.asBinder());
+
+        // Refresh Handlers
+        mApInterfaces.put(ifaceName, apInterface);
+        return true;
+    }
+
+    /**
+     * Tear down a Soft AP interface configured using
+     * {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @param ifaceName Name of the interface to tear down.
+     * @return Returns true on success, false on failure (e.g. when called before an interface was
+     * set up).
+     */
+    public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
+        if (getApInterface(ifaceName) == null) {
+            Log.e(TAG, "No valid wificond ap interface handler for iface=" + ifaceName);
+            return false;
+        }
+
+        if (mWificond == null) {
+            Log.e(TAG, "tearDownSoftApInterface: mWificond binder is null! Did wificond die?");
+            return false;
+        }
+
+        boolean success;
+        try {
+            success = mWificond.tearDownApInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to teardown AP interface due to remote exception");
+            return false;
+        }
+        if (!success) {
+            Log.e(TAG, "Failed to teardown AP interface");
+            return false;
+        }
+        mApInterfaces.remove(ifaceName);
+        mApInterfaceListeners.remove(ifaceName);
+        return true;
+    }
+
+    /**
+    * Tear down all interfaces, whether clients (STA) or Soft AP.
+     *
+    * @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.
+        if (!retrieveWificondAndRegisterForDeath()) {
+            return false;
+        }
+
+        try {
+            for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+                entry.getValue().unsubscribeScanEvents();
+                entry.getValue().unsubscribePnoScanEvents();
+            }
+            mWificond.tearDownInterfaces();
+            clearState();
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+        }
+
+        return false;
+    }
+
+    /** Helper function to look up the interface handle using name */
+    private IClientInterface getClientInterface(@NonNull String ifaceName) {
+        return mClientInterfaces.get(ifaceName);
+    }
+
+    /**
+     * Request signal polling.
+     *
+     * @param ifaceName Name of the interface on which to poll. The interface must have been
+     *                  already set up using
+     *{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     *                  or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @return A {@link SignalPollResult} object containing interface statistics, or a null on
+     * error (e.g. the interface hasn't been set up yet).
+     */
+    @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) {
+        IClientInterface iface = getClientInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName);
+            return null;
+        }
+
+        int[] resultArray;
+        try {
+            resultArray = iface.signalPoll();
+            if (resultArray == null || resultArray.length != 4) {
+                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;
+        }
+        return new SignalPollResult(resultArray[0], resultArray[1], resultArray[3], resultArray[2]);
+    }
+
+    /**
+     * Get current transmit (Tx) packet counters of the specified interface. The interface must
+     * have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @param ifaceName Name of the interface.
+     * @return {@link TxPacketCounters} of the current interface or null on error (e.g. when
+     * called before the interface has been set up).
+     */
+    @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
+        IClientInterface iface = getClientInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName);
+            return null;
+        }
+
+        int[] resultArray;
+        try {
+            resultArray = iface.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;
+        }
+        return new TxPacketCounters(resultArray[0], resultArray[1]);
+    }
+
+    /** Helper function to look up the scanner impl handle using name */
+    private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
+        return mWificondScanners.get(ifaceName);
+    }
+
+    /**
+     * Fetch the latest scan results of the indicated type for the specified interface. Note that
+     * this method fetches the latest results - it does not initiate a scan. Initiating a scan can
+     * be done using {@link #startScan(String, int, Set, List)} or
+     * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @param ifaceName Name of the interface.
+     * @param scanType The type of scan result to be returned, can be
+     * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.
+     * @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when
+     * called before the interface has been set up).
+     */
+    @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName,
+            @ScanResultType int scanType) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
+            return new ArrayList<>();
+        }
+        List<NativeScanResult> results = null;
+        try {
+            if (scanType == SCAN_TYPE_SINGLE_SCAN) {
+                results = Arrays.asList(scannerImpl.getScanResults());
+            } else {
+                results = Arrays.asList(scannerImpl.getPnoScanResults());
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to create ScanDetail ArrayList");
+        }
+        if (results == null) {
+            results = new ArrayList<>();
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "get " + results.size() + " scan results from wificond");
+        }
+
+        return results;
+    }
+
+    /**
+     * Return scan type for the parcelable {@link SingleScanSettings}
+     */
+    private static int getScanType(@WifiAnnotations.ScanType int scanType) {
+        switch (scanType) {
+            case WifiScanner.SCAN_TYPE_LOW_LATENCY:
+                return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN;
+            case WifiScanner.SCAN_TYPE_LOW_POWER:
+                return IWifiScannerImpl.SCAN_TYPE_LOW_POWER;
+            case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
+                return IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+            default:
+                throw new IllegalArgumentException("Invalid scan type " + scanType);
+        }
+    }
+
+    /**
+     * Start a scan using the specified parameters. A scan is an asynchronous operation. The
+     * result of the operation is returned in the {@link ScanEventCallback} registered when
+     * setting up an interface using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
+     * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a
+     * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @param ifaceName Name of the interface on which to initiate the scan.
+     * @param scanType Type of scan to perform, can be any of
+     * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or
+     * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}.
+     * @param freqs list of frequencies to scan for, if null scan all supported channels.
+     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that
+     *                           no hidden frequencies will be scanned for.
+     * @return Returns true on success, false on failure (e.g. when called before the interface
+     * has been set up).
+     */
+    public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
+            @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
+            return false;
+        }
+        SingleScanSettings settings = new SingleScanSettings();
+        try {
+            settings.scanType = getScanType(scanType);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Invalid scan type ", e);
+            return false;
+        }
+        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 (byte[] ssid : hiddenNetworkSSIDs) {
+                HiddenNetwork network = new HiddenNetwork();
+                network.ssid = ssid;
+
+                // settings.hiddenNetworks is expected to be very small, so this shouldn't cause
+                // any performance issues.
+                if (!settings.hiddenNetworks.contains(network)) {
+                    settings.hiddenNetworks.add(network);
+                }
+            }
+        }
+
+        try {
+            return scannerImpl.scan(settings);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Request a PNO (Preferred Network Offload). The offload request and the scans are asynchronous
+     * operations. The result of the request are returned in the {@code callback} parameter which
+     * is an {@link PnoScanRequestCallback}. The scan results are are return in the
+     * {@link ScanEventCallback} which is registered when setting up an interface using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
+     * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the
+     * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @param ifaceName Name of the interface on which to request a PNO.
+     * @param pnoSettings PNO scan configuration.
+     * @param executor The Executor on which to execute the callback.
+     * @param callback Callback for the results of the offload request.
+     * @return true on success, false on failure (e.g. when called before the interface has been set
+     * up).
+     */
+    public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull PnoScanRequestCallback callback) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
+            return false;
+        }
+
+        if (callback == null || executor == null) {
+            Log.e(TAG, "startPnoScan called with a null callback");
+            return false;
+        }
+
+        try {
+            boolean success = scannerImpl.startPnoScan(pnoSettings);
+            if (success) {
+                executor.execute(callback::onPnoRequestSucceeded);
+            } else {
+                executor.execute(callback::onPnoRequestFailed);
+            }
+            return success;
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to start pno scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Stop PNO scan configured with
+     * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @param ifaceName Name of the interface on which the PNO scan was configured.
+     * @return true on success, false on failure (e.g. when called before the interface has been
+     * set up).
+     */
+    public boolean stopPnoScan(@NonNull String ifaceName) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
+            return false;
+        }
+        try {
+            return scannerImpl.stopPnoScan();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to stop pno scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
+     * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure
+     * callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then
+     * this method has no impact.
+     *
+     * @param ifaceName Name of the interface on which the scan was started.
+     */
+    public void abortScan(@NonNull String ifaceName) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
+            return;
+        }
+        try {
+            scannerImpl.abortScan();
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request abortScan due to remote exception");
+        }
+    }
+
+    /**
+     * Query the list of valid frequencies (in MHz) 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.
+     * The following bands are supported:
+     * {@link WifiScanner#WIFI_BAND_24_GHZ},
+     * {@link WifiScanner#WIFI_BAND_5_GHZ},
+     * {@link WifiScanner#WIFI_BAND_5_GHZ_DFS_ONLY},
+     * {@link WifiScanner#WIFI_BAND_6_GHZ}
+     * {@link WifiScanner.WIFI_BAND_60_GHZ}
+     * @return frequencies vector of valid frequencies (MHz), or an empty array for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
+    public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) {
+        if (mWificond == null) {
+            Log.e(TAG, "getChannelsMhzForBand: mWificond binder is null! Did wificond die?");
+            return new int[0];
+        }
+        int[] result = null;
+        try {
+            switch (band) {
+                case WifiScanner.WIFI_BAND_24_GHZ:
+                    result = mWificond.getAvailable2gChannels();
+                    break;
+                case WifiScanner.WIFI_BAND_5_GHZ:
+                    result = mWificond.getAvailable5gNonDFSChannels();
+                    break;
+                case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+                    result = mWificond.getAvailableDFSChannels();
+                    break;
+                case WifiScanner.WIFI_BAND_6_GHZ:
+                    result = mWificond.getAvailable6gChannels();
+                    break;
+                case WifiScanner.WIFI_BAND_60_GHZ:
+                    result = mWificond.getAvailable60gChannels();
+                    break;
+                default:
+                    throw new IllegalArgumentException("unsupported band " + band);
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+        }
+        if (result == null) {
+            result = new int[0];
+        }
+        return result;
+    }
+
+    /** Helper function to look up the interface handle using name */
+    private IApInterface getApInterface(@NonNull String ifaceName) {
+        return mApInterfaces.get(ifaceName);
+    }
+
+    /**
+     * Get the device phy capabilities for a given interface.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has
+     * not been set up).
+     */
+    @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
+        if (mWificond == null) {
+            Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! Did wificond die?");
+            return null;
+        }
+
+        try {
+            return mWificond.getDeviceWiphyCapabilities(ifaceName);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Register the provided callback handler for SoftAp events. The interface must first be created
+     * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
+     * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
+     * method is provided).
+     * <p>
+     * Note that only one callback can be registered at a time - any registration overrides previous
+     * registrations.
+     *
+     * @param ifaceName Name of the interface on which to register the callback.
+     * @param executor The Executor on which to execute the callbacks.
+     * @param callback Callback for AP events.
+     * @return true on success, false on failure (e.g. when called on an interface which has not
+     * been set up).
+     *
+     * @deprecated The usage is replaced by vendor HAL
+     * {@code android.hardware.wifi.hostapd.V1_3.IHostapdCallback}.
+     */
+    @Deprecated
+    public boolean registerApCallback(@NonNull String ifaceName,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SoftApCallback callback) {
+        IApInterface iface = getApInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid ap interface handler for iface=" + ifaceName);
+            return false;
+        }
+
+        if (callback == null || executor == null) {
+            Log.e(TAG, "registerApCallback called with a null callback");
+            return false;
+        }
+
+        try {
+            IApInterfaceEventCallback wificondCallback = new ApInterfaceEventCallback(executor,
+                    callback);
+            mApInterfaceListeners.put(ifaceName, wificondCallback);
+            boolean success = iface.registerCallback(wificondCallback);
+            if (!success) {
+                Log.e(TAG, "Failed to register ap callback.");
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in registering AP callback: " + e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Send a management frame on the specified interface at the specified rate. Useful for probing
+     * the link with arbitrary frames.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @param ifaceName The interface on which to send the frame.
+     * @param frame The raw byte array of the management frame to tramit.
+     * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the
+     *            frame. Specified per IEEE 802.11.
+     * @param executor The Executor on which to execute the callbacks.
+     * @param callback A {@link SendMgmtFrameCallback} callback for results of the operation.
+     */
+    public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame, int mcs,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SendMgmtFrameCallback callback) {
+
+        if (callback == null || executor == null) {
+            Log.e(TAG, "callback cannot be null!");
+            return;
+        }
+
+        if (frame == null) {
+            Log.e(TAG, "frame cannot be null!");
+            executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN));
+            return;
+        }
+
+        // TODO (b/112029045) validate mcs
+        IClientInterface clientInterface = getClientInterface(ifaceName);
+        if (clientInterface == null) {
+            Log.e(TAG, "No valid wificond client interface handler for iface=" + ifaceName);
+            executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN));
+            return;
+        }
+
+        if (!mSendMgmtFrameInProgress.compareAndSet(false, true)) {
+            Log.e(TAG, "An existing management frame transmission is in progress!");
+            executor.execute(() -> callback.onFailure(SEND_MGMT_FRAME_ERROR_ALREADY_STARTED));
+            return;
+        }
+
+        SendMgmtFrameEvent sendMgmtFrameEvent = new SendMgmtFrameEvent(executor, callback);
+        try {
+            clientInterface.SendMgmtFrame(frame, sendMgmtFrameEvent, mcs);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while starting link probe: " + e);
+            // Call sendMgmtFrameEvent.OnFailure() instead of callback.onFailure() so that
+            // sendMgmtFrameEvent can clean up internal state, such as cancelling the timer.
+            sendMgmtFrameEvent.OnFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        }
+    }
+
+    /**
+     * Clear all internal handles.
+     */
+    private void clearState() {
+        // Refresh handlers
+        mClientInterfaces.clear();
+        mWificondScanners.clear();
+        mPnoScanEventHandlers.clear();
+        mScanEventHandlers.clear();
+        mApInterfaces.clear();
+        mApInterfaceListeners.clear();
+        mSendMgmtFrameInProgress.set(false);
+    }
+
+    /**
+     * OEM parsed security type
+     */
+    public static class OemSecurityType {
+        /** The protocol defined in {@link android.net.wifi.WifiAnnotations.Protocol}. */
+        public final @WifiAnnotations.Protocol int protocol;
+        /**
+         * Supported key management types defined
+         * in {@link android.net.wifi.WifiAnnotations.KeyMgmt}.
+         */
+        @NonNull public final List<Integer> keyManagement;
+        /**
+         * Supported pairwise cipher types defined
+         * in {@link android.net.wifi.WifiAnnotations.Cipher}.
+         */
+        @NonNull public final List<Integer> pairwiseCipher;
+        /** The group cipher type defined in {@link android.net.wifi.WifiAnnotations.Cipher}. */
+        public final @WifiAnnotations.Cipher int groupCipher;
+        /**
+         * Default constructor for OemSecurityType
+         *
+         * @param protocol The protocol defined in
+         *                 {@link android.net.wifi.WifiAnnotations.Protocol}.
+         * @param keyManagement Supported key management types defined
+         *                      in {@link android.net.wifi.WifiAnnotations.KeyMgmt}.
+         * @param pairwiseCipher Supported pairwise cipher types defined
+         *                       in {@link android.net.wifi.WifiAnnotations.Cipher}.
+         * @param groupCipher The group cipher type defined
+         *                    in {@link android.net.wifi.WifiAnnotations.Cipher}.
+         */
+        public OemSecurityType(
+                @WifiAnnotations.Protocol int protocol,
+                @NonNull List<Integer> keyManagement,
+                @NonNull List<Integer> pairwiseCipher,
+                @WifiAnnotations.Cipher int groupCipher) {
+            this.protocol = protocol;
+            this.keyManagement = (keyManagement != null)
+                ? keyManagement : new ArrayList<Integer>();
+            this.pairwiseCipher = (pairwiseCipher != null)
+                ? pairwiseCipher : new ArrayList<Integer>();
+            this.groupCipher = groupCipher;
+        }
+    }
+
+    /**
+     * OEM information element parser for security types not parsed by the framework.
+     *
+     * The OEM method should use the method inputs {@code id}, {@code idExt}, and {@code bytes}
+     * to perform the parsing. The method should place the results in an OemSecurityType objct.
+     *
+     * @param id The information element id.
+     * @param idExt The information element extension id. This is valid only when id is
+     *        the extension id, {@code 255}.
+     * @param bytes The raw bytes of information element data, 'Element ID' and 'Length' are
+     *              stripped off already.
+     * @return an OemSecurityType object if this IE is parsed successfully, null otherwise.
+     */
+    @Nullable public static OemSecurityType parseOemSecurityTypeElement(
+            int id,
+            int idExt,
+            @NonNull byte[] bytes) {
+        return null;
+    }
+}
diff --git a/wifi/non-updatable/tests/Android.bp b/wifi/non-updatable/tests/Android.bp
new file mode 100644
index 0000000..3f5cacf
--- /dev/null
+++ b/wifi/non-updatable/tests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "FrameworksWifiNonUpdatableApiTests",
+
+    defaults: ["framework-wifi-test-defaults"],
+
+    srcs: ["src/**/*.java"],
+
+    jacoco: {
+        include_filter: ["android.net.wifi.*"],
+        // TODO(b/147521214) need to exclude test classes
+        exclude_filter: [],
+    },
+
+    static_libs: [
+        "androidx.test.rules",
+        "frameworks-base-testutils",
+        "guava",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+
+    test_suites: [
+        "general-tests",
+    ],
+}
diff --git a/wifi/non-updatable/tests/AndroidManifest.xml b/wifi/non-updatable/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b4b6b2d
--- /dev/null
+++ b/wifi/non-updatable/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.net.wifi.test">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <activity android:label="WifiTestDummyLabel"
+             android:name="WifiTestDummyName"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.net.wifi.test"
+         android:label="Frameworks Wifi Non-updatable API Tests">
+    </instrumentation>
+
+</manifest>
diff --git a/wifi/non-updatable/tests/AndroidTest.xml b/wifi/non-updatable/tests/AndroidTest.xml
new file mode 100644
index 0000000..5f3fdd4
--- /dev/null
+++ b/wifi/non-updatable/tests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Frameworks Wifi Non-updatable API Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="test-file-name" value="FrameworksWifiNonUpdatableApiTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="FrameworksWifiNonUpdatableApiTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.net.wifi.test" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/wifi/non-updatable/tests/README.md b/wifi/non-updatable/tests/README.md
new file mode 100644
index 0000000..ad535f4
--- /dev/null
+++ b/wifi/non-updatable/tests/README.md
@@ -0,0 +1,32 @@
+# Wifi Non-Updatable Framework Unit Tests
+This package contains unit tests for the non-updatable part (i.e. outside the Wifi module) of the
+Android Wifi framework APIs based on the
+[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
+The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
+libraries.
+
+## Running Tests
+The easiest way to run tests is simply run
+
+```
+atest android.net.wifi
+```
+
+To pick up changes in framework/base, you will need to:
+1. rebuild the framework library 'make -j32'
+2. sync over the updated library to the device 'adb sync'
+3. restart framework on the device 'adb shell stop' then 'adb shell start'
+
+To enable syncing data to the device for first time after clean reflash:
+1. adb disable-verity
+2. adb reboot
+3. adb remount
+
+## Adding Tests
+Tests can be added by adding classes to the src directory. JUnit4 style test cases can
+be written by simply annotating test methods with `org.junit.Test`.
+
+## Debugging Tests
+If you are trying to debug why tests are not doing what you expected, you can add android log
+statements and use logcat to view them. The beginning and end of every tests is automatically logged
+with the tag `TestRunner`.
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java b/wifi/non-updatable/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java
new file mode 100644
index 0000000..f49f387
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.assertNotNull;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit tests for {@link android.net.wifi.SoftApConfToXmlMigrationUtilTest}.
+ */
+@SmallTest
+public class SoftApConfToXmlMigrationUtilTest {
+    private static final String TEST_SSID = "SSID";
+    private static final String TEST_PASSPHRASE = "TestPassphrase";
+    private static final int TEST_CHANNEL = 0;
+    private static final boolean TEST_HIDDEN = false;
+    private static final int TEST_BAND = SoftApConfiguration.BAND_5GHZ;
+    private static final int TEST_SECURITY = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK;
+
+    private static final String TEST_EXPECTED_XML_STRING =
+            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<WifiConfigStoreData>\n"
+                    + "<int name=\"Version\" value=\"3\" />\n"
+                    + "<SoftAp>\n"
+                    + "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
+                    + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
+                    + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
+                    + "<boolean name=\"HiddenSSID\" value=\"" + TEST_HIDDEN + "\" />\n"
+                    + "<int name=\"SecurityType\" value=\"" + TEST_SECURITY + "\" />\n"
+                    + "<string name=\"Passphrase\">" + TEST_PASSPHRASE + "</string>\n"
+                    + "<int name=\"MaxNumberOfClients\" value=\"0\" />\n"
+                    + "<boolean name=\"ClientControlByUser\" value=\"false\" />\n"
+                    + "<boolean name=\"AutoShutdownEnabled\" value=\"true\" />\n"
+                    + "<long name=\"ShutdownTimeoutMillis\" value=\"0\" />\n"
+                    + "<BlockedClientList />\n"
+                    + "<AllowedClientList />\n"
+                    + "</SoftAp>\n"
+                    + "</WifiConfigStoreData>\n";
+
+    private byte[] createLegacyApConfFile(WifiConfiguration config) throws Exception {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        DataOutputStream out = new DataOutputStream(outputStream);
+        out.writeInt(3);
+        out.writeUTF(config.SSID);
+        out.writeInt(config.apBand);
+        out.writeInt(config.apChannel);
+        out.writeBoolean(config.hiddenSSID);
+        int authType = config.getAuthType();
+        out.writeInt(authType);
+        if (authType != WifiConfiguration.KeyMgmt.NONE) {
+            out.writeUTF(config.preSharedKey);
+        }
+        out.close();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Generate a SoftApConfiguration based on the specified parameters.
+     */
+    private SoftApConfiguration setupApConfig(
+            String ssid, String preSharedKey, int keyManagement, int band, int channel,
+            boolean hiddenSSID) {
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setSsid(ssid);
+        configBuilder.setPassphrase(preSharedKey, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+        if (channel == 0) {
+            configBuilder.setBand(band);
+        } else {
+            configBuilder.setChannel(channel, band);
+        }
+        configBuilder.setHiddenSsid(hiddenSSID);
+        return configBuilder.build();
+    }
+
+    /**
+     * Generate a WifiConfiguration based on the specified parameters.
+     */
+    private WifiConfiguration setupWifiConfigurationApConfig(
+            String ssid, String preSharedKey, int keyManagement, int band, int channel,
+            boolean hiddenSSID) {
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = ssid;
+        config.preSharedKey = preSharedKey;
+        config.allowedKeyManagement.set(keyManagement);
+        config.apBand = band;
+        config.apChannel = channel;
+        config.hiddenSSID = hiddenSSID;
+        return config;
+    }
+
+    /**
+     * Asserts that the WifiConfigurations equal to SoftApConfiguration.
+     * This only compares the elements saved
+     * for softAp used.
+     */
+    public static void assertWifiConfigurationEqualSoftApConfiguration(
+            WifiConfiguration backup, SoftApConfiguration restore) {
+        assertEquals(backup.SSID, restore.getSsid());
+        assertEquals(backup.BSSID, restore.getBssid());
+        assertEquals(SoftApConfToXmlMigrationUtil.convertWifiConfigBandToSoftApConfigBand(
+                backup.apBand),
+                restore.getBand());
+        assertEquals(backup.apChannel, restore.getChannel());
+        assertEquals(backup.preSharedKey, restore.getPassphrase());
+        if (backup.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK) {
+            assertEquals(SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, restore.getSecurityType());
+        } else {
+            assertEquals(SoftApConfiguration.SECURITY_TYPE_OPEN, restore.getSecurityType());
+        }
+        assertEquals(backup.hiddenSSID, restore.isHiddenSsid());
+    }
+
+    /**
+     * Note: This is a copy of {@link AtomicFile#readFully()} modified to use the passed in
+     * {@link InputStream} which was returned using {@link AtomicFile#openRead()}.
+     */
+    private static byte[] readFully(InputStream stream) throws IOException {
+        try {
+            int pos = 0;
+            int avail = stream.available();
+            byte[] data = new byte[avail];
+            while (true) {
+                int amt = stream.read(data, pos, data.length - pos);
+                if (amt <= 0) {
+                    return data;
+                }
+                pos += amt;
+                avail = stream.available();
+                if (avail > data.length - pos) {
+                    byte[] newData = new byte[pos + avail];
+                    System.arraycopy(data, 0, newData, 0, pos);
+                    data = newData;
+                }
+            }
+        } finally {
+            stream.close();
+        }
+    }
+
+    /**
+     * Tests conversion from legacy .conf file to XML file format.
+     */
+    @Test
+    public void testConversion() throws Exception {
+        WifiConfiguration backupConfig = setupWifiConfigurationApConfig(
+                TEST_SSID,    /* SSID */
+                TEST_PASSPHRASE,       /* preshared key */
+                WifiConfiguration.KeyMgmt.WPA2_PSK,   /* key management */
+                1,                 /* AP band (5GHz) */
+                TEST_CHANNEL,                /* AP channel */
+                TEST_HIDDEN            /* Hidden SSID */);
+        SoftApConfiguration expectedConfig = setupApConfig(
+                TEST_SSID,           /* SSID */
+                TEST_PASSPHRASE,              /* preshared key */
+                SoftApConfiguration.SECURITY_TYPE_WPA2_PSK,   /* security type */
+                SoftApConfiguration.BAND_5GHZ, /* AP band (5GHz) */
+                TEST_CHANNEL,                       /* AP channel */
+                TEST_HIDDEN            /* Hidden SSID */);
+
+        assertWifiConfigurationEqualSoftApConfiguration(backupConfig, expectedConfig);
+
+        byte[] confBytes = createLegacyApConfFile(backupConfig);
+        assertNotNull(confBytes);
+
+        InputStream xmlStream = SoftApConfToXmlMigrationUtil.convert(
+                new ByteArrayInputStream(confBytes));
+
+        byte[] xmlBytes = readFully(xmlStream);
+        assertNotNull(xmlBytes);
+
+        assertEquals(TEST_EXPECTED_XML_STRING, new String(xmlBytes));
+    }
+
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/non-updatable/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
new file mode 100644
index 0000000..c4967eb
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.net.wifi.WifiNetworkScoreCache.CacheListener;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Unit tests for {@link WifiNetworkScoreCache}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WifiNetworkScoreCacheTest {
+
+    public static final String SSID = "ssid";
+    public static final String SSID2 = "ssid2";
+    public static final String SSID3 = "ssid3";
+    public static final String FORMATTED_SSID = "\"" + SSID + "\"";
+    public static final String FORMATTED_SSID2 = "\"" + SSID2 + "\"";
+    public static final String FORMATTED_SSID3 = "\"" + SSID3 + "\"";
+    public static final String BSSID = "AA:AA:AA:AA:AA:AA";
+
+    public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
+
+    public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID);
+
+    @Mock private Context mockApplicationContext;
+    @Mock private Context mockContext; // isn't used, can be null
+    @Mock private RssiCurve mockRssiCurve;
+
+
+    private CacheListener mCacheListener;
+    private CountDownLatch mLatch;
+    private Handler mHandler;
+    private List<ScoredNetwork> mUpdatedNetworksCaptor;
+    private ScoredNetwork mValidScoredNetwork;
+    private WifiNetworkScoreCache mScoreCache;
+
+    private static ScanResult buildScanResult(String ssid, String bssid) {
+        return new ScanResult(
+                WifiSsid.createFromAsciiEncoded(ssid),
+                bssid,
+                "" /* caps */,
+                0 /* level */,
+                0 /* frequency */,
+                0 /* tsf */,
+                0 /* distCm */,
+                0 /* distSdCm*/);
+    }
+
+    private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) {
+        return new ScoredNetwork(new NetworkKey(key), curve);
+    }
+
+    // Called from setup
+    private void initializeCacheWithValidScoredNetwork() {
+        mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork));
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mockContext.getApplicationContext()).thenReturn(mockApplicationContext);
+
+        mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve);
+        mScoreCache = new WifiNetworkScoreCache(mockContext);
+        initializeCacheWithValidScoredNetwork();
+
+        HandlerThread thread = new HandlerThread("WifiNetworkScoreCacheTest Handler Thread");
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
+        mLatch = new CountDownLatch(1);
+        mCacheListener = new CacheListener(mHandler) {
+            @Override
+            public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) {
+                mUpdatedNetworksCaptor = updatedNetworks;
+                mLatch.countDown();
+            }
+        };
+    }
+
+
+    @Test
+    public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
+        assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isTrue();
+    }
+
+    @Test
+    public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
+        mScoreCache.clearScores();
+        assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isFalse();
+    }
+
+    @Test
+    public void updateScoresShouldAddNewNetwork() {
+        WifiKey key2 = new WifiKey("\"ssid2\"", BSSID);
+        ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve);
+        ScanResult result2 = buildScanResult("ssid2", BSSID);
+
+        mScoreCache.updateScores(ImmutableList.of(network2));
+
+        assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isTrue();
+        assertThat(mScoreCache.isScoredNetwork(result2)).isTrue();
+    }
+
+    @Test
+    public void hasScoreCurveShouldReturnTrue() {
+        assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isTrue();
+    }
+
+    @Test
+    public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
+        ScanResult unscored = buildScanResult("fake", BSSID);
+        assertThat(mScoreCache.hasScoreCurve(unscored)).isFalse();
+    }
+
+    @Test
+    public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() {
+        ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
+        mScoreCache.updateScores(ImmutableList.of(noCurve));
+
+        assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isFalse();
+    }
+
+    @Test
+    public void getNetworkScoreShouldReturnScore() {
+        final byte score = 50;
+        final int rssi = -70;
+        ScanResult result = new ScanResult(VALID_SCAN_RESULT);
+        result.level = rssi;
+
+        when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
+
+        assertThat(mScoreCache.getNetworkScore(result)).isEqualTo(score);
+    }
+
+    @Test
+    public void getMeteredHintShouldReturnFalse() {
+        assertThat(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)).isFalse();
+    }
+
+    @Test
+    public void getMeteredHintShouldReturnTrue() {
+        ScoredNetwork network =
+                new ScoredNetwork(
+                    new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
+        mScoreCache.updateScores(ImmutableList.of(network));
+
+        assertThat(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)).isTrue();
+    }
+
+    @Test
+    public void updateScoresShouldInvokeCacheListener_networkCacheUpdated() {
+        mScoreCache = new WifiNetworkScoreCache(mockContext, mCacheListener);
+        initializeCacheWithValidScoredNetwork();
+
+        try {
+            mLatch.await(1, TimeUnit.SECONDS); // wait for listener to be executed
+        } catch (InterruptedException e) {
+            fail("Interrupted Exception while waiting for listener to be invoked.");
+        }
+        // One network should be updated.
+        assertThat(mUpdatedNetworksCaptor.size()).isEqualTo(1);
+        assertThat(mUpdatedNetworksCaptor.get(0)).isEqualTo(mValidScoredNetwork);
+    }
+
+    @Test
+    public void leastRecentlyUsedScore_shouldBeEvictedFromCache() {
+        mScoreCache = new WifiNetworkScoreCache(mockContext, mCacheListener, 2 /* maxCacheSize */);
+
+        ScoredNetwork network1 = mValidScoredNetwork;
+        ScoredNetwork network2 = buildScoredNetwork(
+                new WifiKey(FORMATTED_SSID2, BSSID), mockRssiCurve);
+        ScoredNetwork network3 = buildScoredNetwork(
+                new WifiKey(FORMATTED_SSID3, BSSID), mockRssiCurve);
+        mScoreCache.updateScores(ImmutableList.of(network1));
+        mScoreCache.updateScores(ImmutableList.of(network2));
+
+        // First score should be evicted because max cache size has been reached.
+        mScoreCache.updateScores(ImmutableList.of(network3));
+
+        assertThat(mScoreCache.hasScoreCurve(buildScanResult(SSID2, BSSID))).isTrue();
+        assertThat(mScoreCache.hasScoreCurve(buildScanResult(SSID3, BSSID))).isTrue();
+        assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isFalse();
+    }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
new file mode 100644
index 0000000..7b900fe
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.nl80211;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.ScanResult;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.DeviceWiphyCapabilities}.
+ */
+@SmallTest
+public class DeviceWiphyCapabilitiesTest {
+    @Before
+    public void setUp() {}
+
+    /**
+     *  DeviceWiphyCapabilities object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        DeviceWiphyCapabilities capa = new DeviceWiphyCapabilities();
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+        capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ, true);
+        capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
+        capa.setMaxNumberTxSpatialStreams(2);
+        capa.setMaxNumberRxSpatialStreams(1);
+
+        Parcel parcel = Parcel.obtain();
+        capa.writeToParcel(parcel, 0);
+        // Rewind the pointer to the head of the parcel.
+        parcel.setDataPosition(0);
+        DeviceWiphyCapabilities capaDeserialized =
+                DeviceWiphyCapabilities.CREATOR.createFromParcel(parcel);
+
+        assertEquals(capa, capaDeserialized);
+        assertEquals(capa.hashCode(), capaDeserialized.hashCode());
+    }
+
+    /**
+     * Test mapping wifi standard support into channel width support
+     */
+    @Test
+    public void testMappingWifiStandardIntoChannelWidthSupport() {
+        DeviceWiphyCapabilities capa = new DeviceWiphyCapabilities();
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, false);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, false);
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+        assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+        assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+        assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+
+        capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+        assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+    }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java
new file mode 100644
index 0000000..8ddd189
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.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 int TEST_CAPABILITY = (0x1 << 2) | (0x1 << 5);
+    private static final boolean TEST_ASSOCIATED = true;
+    private static final int[] RADIO_CHAIN_IDS = { 0, 1 };
+    private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 };
+
+    /**
+     *  NativeScanResult object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        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;
+        scanResult.radioChainInfos = new ArrayList<>(Arrays.asList(
+                new RadioChainInfo(RADIO_CHAIN_IDS[0], RADIO_CHAIN_LEVELS[0]),
+                new RadioChainInfo(RADIO_CHAIN_IDS[1], RADIO_CHAIN_LEVELS[1])));
+        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);
+        assertEquals(scanResult.capability, scanResultDeserialized.capability);
+        assertEquals(scanResult.associated, scanResultDeserialized.associated);
+        assertTrue(scanResult.radioChainInfos.containsAll(scanResultDeserialized.radioChainInfos));
+    }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java
new file mode 100644
index 0000000..dec1db8
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.PnoSettings}.
+ */
+@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_FREQUENCIES_1 = {};
+    private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+    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;
+    private static final int TEST_VALUE = 42;
+
+    private PnoNetwork mPnoNetwork1;
+    private PnoNetwork mPnoNetwork2;
+
+    @Before
+    public void setUp() {
+        mPnoNetwork1 = new PnoNetwork();
+        mPnoNetwork1.setSsid(TEST_SSID_1);
+        mPnoNetwork1.setHidden(true);
+        mPnoNetwork1.setFrequenciesMhz(TEST_FREQUENCIES_1);
+
+        mPnoNetwork2 = new PnoNetwork();
+        mPnoNetwork2.setSsid(TEST_SSID_2);
+        mPnoNetwork2.setHidden(false);
+        mPnoNetwork2.setFrequenciesMhz(TEST_FREQUENCIES_2);
+    }
+
+    /**
+     *  PnoSettings object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        PnoSettings pnoSettings = new PnoSettings();
+        pnoSettings.setIntervalMillis(TEST_INTERVAL_MS);
+        pnoSettings.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+        pnoSettings.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+        pnoSettings.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+        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);
+
+        assertEquals(pnoSettings, pnoSettingsDeserialized);
+        assertEquals(pnoSettings.hashCode(), pnoSettingsDeserialized.hashCode());
+    }
+
+    /**
+     * Tests usage of {@link PnoSettings} as a HashMap key type.
+     */
+    @Test
+    public void testAsHashMapKey() {
+        PnoSettings pnoSettings1 = new PnoSettings();
+        pnoSettings1.setIntervalMillis(TEST_INTERVAL_MS);
+        pnoSettings1.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+        pnoSettings1.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+        pnoSettings1.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+        PnoSettings pnoSettings2 = new PnoSettings();
+        pnoSettings2.setIntervalMillis(TEST_INTERVAL_MS);
+        pnoSettings2.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+        pnoSettings2.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+        pnoSettings2.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+        assertEquals(pnoSettings1, pnoSettings2);
+        assertEquals(pnoSettings1.hashCode(), pnoSettings2.hashCode());
+
+        HashMap<PnoSettings, Integer> map = new HashMap<>();
+        map.put(pnoSettings1, TEST_VALUE);
+
+        assertEquals(TEST_VALUE, map.get(pnoSettings2).intValue());
+    }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
new file mode 100644
index 0000000..9059208
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.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 android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.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;
+    private static final int TEST_VALUE = 42;
+
+    private ChannelSettings mChannelSettings1;
+    private ChannelSettings mChannelSettings2;
+    private HiddenNetwork mHiddenNetwork1;
+    private HiddenNetwork mHiddenNetwork2;
+
+    @Before
+    public void setUp() {
+        mChannelSettings1 = new ChannelSettings();
+        mChannelSettings1.frequency = TEST_FREQUENCY_1;
+        mChannelSettings2 = new ChannelSettings();
+        mChannelSettings2.frequency = TEST_FREQUENCY_2;
+
+        mHiddenNetwork1 = new HiddenNetwork();
+        mHiddenNetwork1.ssid = TEST_SSID_1;
+        mHiddenNetwork2 = new HiddenNetwork();
+        mHiddenNetwork2.ssid = TEST_SSID_2;
+    }
+
+    /**
+     *  SingleScanSettings object can be serialized and deserialized, while keeping the
+     *  values unchanged.
+     */
+    @Test
+    public void canSerializeAndDeserialize() {
+        SingleScanSettings scanSettings = new SingleScanSettings();
+        scanSettings.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+
+        scanSettings.channelSettings =
+                new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+        scanSettings.hiddenNetworks =
+                new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+        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);
+
+        assertEquals(scanSettings, scanSettingsDeserialized);
+        assertEquals(scanSettings.hashCode(), scanSettingsDeserialized.hashCode());
+    }
+
+    /**
+     * Tests usage of {@link SingleScanSettings} as a HashMap key type.
+     */
+    @Test
+    public void testAsHashMapKey() {
+        SingleScanSettings scanSettings1 = new SingleScanSettings();
+        scanSettings1.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+        scanSettings1.channelSettings =
+                new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+        scanSettings1.hiddenNetworks =
+                new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+        SingleScanSettings scanSettings2 = new SingleScanSettings();
+        scanSettings2.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+        scanSettings2.channelSettings =
+                new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+        scanSettings2.hiddenNetworks =
+                new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+        assertEquals(scanSettings1, scanSettings2);
+        assertEquals(scanSettings1.hashCode(), scanSettings2.hashCode());
+
+        HashMap<SingleScanSettings, Integer> map = new HashMap<>();
+        map.put(scanSettings1, TEST_VALUE);
+
+        assertEquals(TEST_VALUE, map.get(scanSettings2).intValue());
+    }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
new file mode 100644
index 0000000..9ee0acbf
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -0,0 +1,1265 @@
+/*
+ * 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 android.net.wifi.nl80211;
+
+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.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+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 static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.net.MacAddress;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SoftApInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.util.HexEncoding;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.AdditionalMatchers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.WifiNl80211Manager}.
+ */
+@SmallTest
+public class WifiNl80211ManagerTest {
+    @Mock
+    private IWificond mWificond;
+    @Mock
+    private IBinder mWifiCondBinder;
+    @Mock
+    private IClientInterface mClientInterface;
+    @Mock
+    private IWifiScannerImpl mWifiScannerImpl;
+    @Mock
+    private IApInterface mApInterface;
+    @Mock
+    private WifiNl80211Manager.SoftApCallback mSoftApListener;
+    @Mock
+    private WifiNl80211Manager.SendMgmtFrameCallback mSendMgmtFrameCallback;
+    @Mock
+    private WifiNl80211Manager.ScanEventCallback mNormalScanCallback;
+    @Mock
+    private WifiNl80211Manager.ScanEventCallback mPnoScanCallback;
+    @Mock
+    private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
+    @Mock
+    private Context mContext;
+    private TestLooper mLooper;
+    private TestAlarmManager mTestAlarmManager;
+    private AlarmManager mAlarmManager;
+    private WifiNl80211Manager mWificondControl;
+    private static final String TEST_INTERFACE_NAME = "test_wlan_if";
+    private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
+    private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+    private static final byte[] TEST_SSID =
+            new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_PSK =
+            new byte[]{'T', 'e', 's', 't'};
+
+    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 int[] TEST_FREQUENCIES_1 = {};
+    private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+    private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
+            new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
+
+    private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
+            new ArrayList<byte[]>() {{
+                add(LocalNativeUtil.byteArrayFromArrayList(
+                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+                add(LocalNativeUtil.byteArrayFromArrayList(
+                        LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+            }};
+
+    private static final PnoSettings TEST_PNO_SETTINGS = new PnoSettings();
+    static {
+        TEST_PNO_SETTINGS.setIntervalMillis(6000);
+        List<PnoNetwork> initPnoNetworks = new ArrayList<>();
+        PnoNetwork network = new PnoNetwork();
+        network.setSsid(LocalNativeUtil.byteArrayFromArrayList(
+                LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+        network.setHidden(true);
+        network.setFrequenciesMhz(TEST_FREQUENCIES_1);
+        initPnoNetworks.add(network);
+        network.setSsid(LocalNativeUtil.byteArrayFromArrayList(
+                LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+        network.setHidden(false);
+        network.setFrequenciesMhz(TEST_FREQUENCIES_2);
+        initPnoNetworks.add(network);
+        TEST_PNO_SETTINGS.setPnoNetworks(initPnoNetworks);
+    }
+
+    private static final int TEST_MCS_RATE = 5;
+    private static final int TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS = 100;
+    private static final byte[] TEST_PROBE_FRAME = {
+            0x40, 0x00, 0x3c, 0x00, (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b,
+            0x33, 0x72, (byte) 0xf4, (byte) 0xf5, (byte) 0xe8, 0x51, (byte) 0x9e, 0x09,
+            (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b, 0x33, 0x72, (byte) 0xb0, 0x66,
+            0x00, 0x00
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        // Setup mocks for successful WificondControl operation. Failure case mocks should be
+        // created in specific tests
+        MockitoAnnotations.initMocks(this);
+
+        mTestAlarmManager = new TestAlarmManager();
+        mAlarmManager = mTestAlarmManager.getAlarmManager();
+        when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+        mLooper = new TestLooper();
+        when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+
+        when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
+        when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+        when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
+        when(mWificond.createApInterface(any())).thenReturn(mApInterface);
+        when(mWificond.tearDownClientInterface(any())).thenReturn(true);
+        when(mWificond.tearDownApInterface(any())).thenReturn(true);
+        when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+        when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+        mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+        assertEquals(true,
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+                        mNormalScanCallback, mPnoScanCallback));
+    }
+
+    /**
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testSetupInterfaceForClientMode() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+        verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls subscribeScanEvents().
+     */
+    @Test
+    public void testSetupInterfaceForClientModeCallsScanEventSubscripiton() throws Exception {
+        verify(mWifiScannerImpl).subscribeScanEvents(any(IScanEvent.class));
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterface() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+        assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterfaceOnInvalidIface() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME1));
+        verify(mWifiScannerImpl, never()).unsubscribeScanEvents();
+        verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+        verify(mWificond, never()).tearDownClientInterface(any());
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterfaceFailDueToExceptionScannerUnsubscribe() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+        doThrow(new RemoteException()).when(mWifiScannerImpl).unsubscribeScanEvents();
+
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+        verify(mWificond, never()).tearDownClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterfaceErrorWhenWificondFailed() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that the client handles are cleared after teardown.
+     */
+    @Test
+    public void testTeardownClientInterfaceClearsHandles() throws Exception {
+        testTeardownClientInterface();
+
+        assertNull(mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+        verify(mClientInterface, never()).signalPoll();
+
+        assertFalse(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl, never()).scan(any());
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) calls wificond.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApMode() throws Exception {
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(mApInterface);
+
+        assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+        verify(mWificond).createApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftAp() returns null when wificond is not started.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+        // Invoke wificond death handler to clear the handle.
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+
+        assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) returns null when wificond
+     * failed to setup AP interface.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApModeErrorWhenWificondFailedToSetupInterface()
+            throws Exception {
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(null);
+
+        assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownSoftApInterface() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+        assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that tearDownSoftapInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceOnInvalidIface() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+        assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+        verify(mWificond, never()).tearDownApInterface(any());
+    }
+
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceErrorWhenWificondFailed() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+        assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that the SoftAp handles are cleared after teardown.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceClearsHandles() throws Exception {
+        testTeardownSoftApInterface();
+
+        assertFalse(mWificondControl.registerApCallback(
+                TEST_INTERFACE_NAME, Runnable::run, mSoftApListener));
+        verify(mApInterface, never()).registerCallback(any());
+    }
+
+    /**
+     * Verifies that we can setup concurrent interfaces.
+     */
+    @Test
+    public void testSetupMultipleInterfaces() throws Exception {
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME1)).thenReturn(mApInterface);
+
+        assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME1));
+
+        verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+        verify(mWificond).createApInterface(TEST_INTERFACE_NAME1);
+    }
+
+    /**
+     * Verifies that we can setup concurrent interfaces.
+     */
+    @Test
+    public void testTeardownMultipleInterfaces() throws Exception {
+        testSetupMultipleInterfaces();
+        assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME1);
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls wificond.
+     */
+    @Test
+    public void testTearDownInterfaces() throws Exception {
+        assertTrue(mWificondControl.tearDownInterfaces());
+        verify(mWificond).tearDownInterfaces();
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() calls unsubscribeScanEvents() when there was
+     * a configured client interface.
+     */
+    @Test
+    public void testTearDownInterfacesRemovesScanEventSubscription() throws Exception {
+        assertTrue(mWificondControl.tearDownInterfaces());
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+    }
+
+    /**
+     * Verifies that tearDownInterfaces() returns false when wificond is not started.
+     */
+    @Test
+    public void testTearDownInterfacesErrorWhenWificondIsNotStarterd() throws Exception {
+        // Invoke wificond death handler to clear the handle.
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+        assertFalse(mWificondControl.tearDownInterfaces());
+    }
+
+    /**
+     * Verifies that signalPoll() calls wificond.
+     */
+    @Test
+    public void testSignalPoll() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+                mNormalScanCallback, mPnoScanCallback);
+        mWificondControl.signalPoll(TEST_INTERFACE_NAME);
+        verify(mClientInterface).signalPoll();
+    }
+
+    /**
+     * Verifies that signalPoll() returns null when there is no configured client interface.
+     */
+    @Test
+    public void testSignalPollErrorWhenNoClientInterfaceConfigured() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        // Configure client interface.
+        assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+                Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Signal poll should fail.
+        assertEquals(null, mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that getTxPacketCounters() calls wificond.
+     */
+    @Test
+    public void testGetTxPacketCounters() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+                mNormalScanCallback, mPnoScanCallback);
+        mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME);
+        verify(mClientInterface).getPacketCounters();
+    }
+
+    /**
+     * Verifies that getTxPacketCounters() returns null when there is no configured client
+     * interface.
+     */
+    @Test
+    public void testGetTxPacketCountersErrorWhenNoClientInterfaceConfigured() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        // Configure client interface.
+        assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+                Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // Signal poll should fail.
+        assertEquals(null, mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME));
+    }
+
+    /**
+     * Verifies that getScanResults() returns null when there is no configured client
+     * interface.
+     */
+    @Test
+    public void testGetScanResultsErrorWhenNoClientInterfaceConfigured() throws Exception {
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+        // Configure client interface.
+        assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+                Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+        // Tear down interfaces.
+        assertTrue(mWificondControl.tearDownInterfaces());
+
+        // getScanResults should fail.
+        assertEquals(0,
+                mWificondControl.getScanResults(TEST_INTERFACE_NAME,
+                        WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN).size());
+    }
+
+    /**
+     * Verifies that Scan() can convert input parameters to SingleScanSettings correctly.
+     */
+    @Test
+    public void testScan() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+    }
+
+    /**
+     * Verifies that Scan() removes duplicates hiddenSsids passed in from input.
+     */
+    @Test
+    public void testScanWithDuplicateHiddenSsids() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        // Create a list of hiddenSsid that has a duplicate element
+        List<byte[]> hiddenSsidWithDup = new ArrayList<>(SCAN_HIDDEN_NETWORK_SSID_LIST);
+        hiddenSsidWithDup.add(SCAN_HIDDEN_NETWORK_SSID_LIST.get(0));
+        assertEquals(hiddenSsidWithDup.get(0),
+                hiddenSsidWithDup.get(hiddenSsidWithDup.size() - 1));
+        // Pass the List with duplicate elements into scan()
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, hiddenSsidWithDup));
+        // But the argument passed down should have the duplicate removed.
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+    }
+
+    /**
+     * Verifies that Scan() can handle null input parameters correctly.
+     */
+    @Test
+    public void testScanNullParameters() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, null, null));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null)));
+    }
+
+    /**
+     * Verifies that Scan() can handle wificond scan failure.
+     */
+    @Test
+    public void testScanFailure() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(false);
+        assertFalse(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl).scan(any(SingleScanSettings.class));
+    }
+
+    /**
+     * Verifies that Scan() can handle invalid type.
+     */
+    @Test
+    public void testScanFailureDueToInvalidType() throws Exception {
+        assertFalse(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, 100,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mWifiScannerImpl, never()).scan(any(SingleScanSettings.class));
+    }
+
+    /**
+     * Verifies that startPnoScan() can convert input parameters to PnoSettings correctly.
+     */
+    @Test
+    public void testStartPnoScan() throws Exception {
+        when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(true);
+        assertTrue(
+                mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS, Runnable::run,
+                        mPnoScanRequestCallback));
+        verify(mWifiScannerImpl).startPnoScan(eq(TEST_PNO_SETTINGS));
+        verify(mPnoScanRequestCallback).onPnoRequestSucceeded();
+    }
+
+    /**
+     * Verifies that stopPnoScan() calls underlying wificond.
+     */
+    @Test
+    public void testStopPnoScan() throws Exception {
+        when(mWifiScannerImpl.stopPnoScan()).thenReturn(true);
+        assertTrue(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).stopPnoScan();
+    }
+
+    /**
+     * Verifies that stopPnoScan() can handle wificond failure.
+     */
+    @Test
+    public void testStopPnoScanFailure() throws Exception {
+
+        when(mWifiScannerImpl.stopPnoScan()).thenReturn(false);
+        assertFalse(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).stopPnoScan();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+     * result event.
+     */
+    @Test
+    public void testScanResultEvent() throws Exception {
+        ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+        verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+        IScanEvent scanEvent = messageCaptor.getValue();
+        assertNotNull(scanEvent);
+        scanEvent.OnScanResultReady();
+
+        verify(mNormalScanCallback).onScanResultReady();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+     * failed event.
+     */
+    @Test
+    public void testScanFailedEvent() throws Exception {
+        ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+        verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+        IScanEvent scanEvent = messageCaptor.getValue();
+        assertNotNull(scanEvent);
+        scanEvent.OnScanFailed();
+
+        verify(mNormalScanCallback).onScanFailed();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon pno scan
+     * result event.
+     */
+    @Test
+    public void testPnoScanResultEvent() throws Exception {
+        ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+        verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+        IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+        assertNotNull(pnoScanEvent);
+        pnoScanEvent.OnPnoNetworkFound();
+        verify(mPnoScanCallback).onScanResultReady();
+    }
+
+    /**
+     * Verifies that WificondControl can invoke WifiMetrics pno scan count methods upon pno event.
+     */
+    @Test
+    public void testPnoScanEventsForMetrics() throws Exception {
+        ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+        verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+        IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+        assertNotNull(pnoScanEvent);
+
+        pnoScanEvent.OnPnoNetworkFound();
+        verify(mPnoScanCallback).onScanResultReady();
+
+        pnoScanEvent.OnPnoScanFailed();
+        verify(mPnoScanCallback).onScanFailed();
+    }
+
+    /**
+     * Verifies that startPnoScan() can invoke WifiMetrics pno scan count methods correctly.
+     */
+    @Test
+    public void testStartPnoScanForMetrics() throws Exception {
+        when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(false);
+
+        assertFalse(
+                mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS, Runnable::run,
+                        mPnoScanRequestCallback));
+        verify(mPnoScanRequestCallback).onPnoRequestFailed();
+    }
+
+    /**
+     * Verifies that abortScan() calls underlying wificond.
+     */
+    @Test
+    public void testAbortScan() throws Exception {
+        mWificondControl.abortScan(TEST_INTERFACE_NAME);
+        verify(mWifiScannerImpl).abortScan();
+    }
+
+    /**
+     * Ensures that the Ap interface callbacks are forwarded to the
+     * SoftApListener used for starting soft AP.
+     */
+    @Test
+    public void testSoftApListenerInvocation() throws Exception {
+        testSetupInterfaceForSoftApMode();
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+        when(mApInterface.registerCallback(any())).thenReturn(true);
+
+        final ArgumentCaptor<IApInterfaceEventCallback> apInterfaceCallbackCaptor =
+                ArgumentCaptor.forClass(IApInterfaceEventCallback.class);
+
+        assertTrue(mWificondControl.registerApCallback(
+                TEST_INTERFACE_NAME, Runnable::run, mSoftApListener));
+        verify(mApInterface).registerCallback(apInterfaceCallbackCaptor.capture());
+
+        final NativeWifiClient testClient = new NativeWifiClient(TEST_RAW_MAC_BYTES);
+        apInterfaceCallbackCaptor.getValue().onConnectedClientsChanged(testClient, true);
+        verify(mSoftApListener).onConnectedClientsChanged(eq(testClient), eq(true));
+
+        int channelFrequency = 2437;
+        int channelBandwidth = IApInterfaceEventCallback.BANDWIDTH_20;
+        apInterfaceCallbackCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
+                channelBandwidth);
+        verify(mSoftApListener).onSoftApChannelSwitched(eq(channelFrequency),
+                eq(SoftApInfo.CHANNEL_WIDTH_20MHZ));
+    }
+
+    /**
+     * Verifies registration and invocation of wificond death handler.
+     */
+    @Test
+    public void testRegisterDeathHandler() throws Exception {
+        Runnable deathHandler = mock(Runnable.class);
+        mWificondControl.setOnServiceDeadCallback(deathHandler);
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+        verify(deathHandler).run();
+    }
+
+    /**
+     * Verifies handling of wificond death and ensures that all internal state is cleared and
+     * handlers are invoked.
+     */
+    @Test
+    public void testDeathHandling() throws Exception {
+        Runnable deathHandler = mock(Runnable.class);
+        mWificondControl.setOnServiceDeadCallback(deathHandler);
+
+        testSetupInterfaceForClientMode();
+
+        mWificondControl.binderDied();
+        mLooper.dispatchAll();
+        verify(deathHandler).run();
+
+        // The handles should be cleared after death.
+        assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).length);
+        verify(mWificond, never()).getAvailable5gNonDFSChannels();
+    }
+
+    /**
+     * sendMgmtFrame() should fail if a null callback is passed in.
+     */
+    @Test
+    public void testSendMgmtFrameNullCallback() throws Exception {
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, null);
+
+        verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+    }
+
+    /**
+     * sendMgmtFrame() should fail if a null frame is passed in.
+     */
+    @Test
+    public void testSendMgmtFrameNullFrame() throws Exception {
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, null, TEST_MCS_RATE, Runnable::run,
+                mSendMgmtFrameCallback);
+
+        verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+        verify(mSendMgmtFrameCallback).onFailure(anyInt());
+    }
+
+    /**
+     * sendMgmtFrame() should fail if an interface name that does not exist is passed in.
+     */
+    @Test
+    public void testSendMgmtFrameInvalidInterfaceName() throws Exception {
+        mWificondControl.sendMgmtFrame(TEST_INVALID_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, mSendMgmtFrameCallback);
+
+        verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+        verify(mSendMgmtFrameCallback).onFailure(anyInt());
+    }
+
+    /**
+     * sendMgmtFrame() should fail if it is called a second time before the first call completed.
+     */
+    @Test
+    public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception {
+        WifiNl80211Manager.SendMgmtFrameCallback cb1 = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
+        WifiNl80211Manager.SendMgmtFrameCallback cb2 = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, cb1);
+        verify(cb1, never()).onFailure(anyInt());
+        verify(mClientInterface, times(1))
+                .SendMgmtFrame(AdditionalMatchers.aryEq(TEST_PROBE_FRAME),
+                        any(), eq(TEST_MCS_RATE));
+
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, cb2);
+        verify(cb2).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+        // verify SendMgmtFrame() still was only called once i.e. not called again
+        verify(mClientInterface, times(1))
+                .SendMgmtFrame(any(), any(), anyInt());
+    }
+
+    /**
+     * Tests that when a RemoteException is triggered on AIDL call, onFailure() is called only once.
+     */
+    @Test
+    public void testSendMgmtFrameThrowsException() throws Exception {
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+
+        doThrow(new RemoteException()).when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, cb);
+        mLooper.dispatchAll();
+
+        verify(cb).onFailure(anyInt());
+        verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+        sendMgmtFrameEventCaptor.getValue().OnFailure(
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        mLooper.dispatchAll();
+
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+
+        verifyNoMoreInteractions(cb);
+    }
+
+    /**
+     * Tests that the onAck() callback is triggered correctly.
+     */
+    @Test
+    public void testSendMgmtFrameSuccess() throws Exception {
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, cb);
+
+        sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+        mLooper.dispatchAll();
+        verify(cb).onAck(eq(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS));
+        verify(cb, never()).onFailure(anyInt());
+        verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+        // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+        // triggered again
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+        verify(cb, times(1)).onAck(anyInt());
+        verify(cb, never()).onFailure(anyInt());
+    }
+
+    /**
+     * Tests that the onFailure() callback is triggered correctly.
+     */
+    @Test
+    public void testSendMgmtFrameFailure() throws Exception {
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, cb);
+
+        sendMgmtFrameEventCaptor.getValue().OnFailure(
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+        // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+        // triggered again
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb, times(1)).onFailure(anyInt());
+    }
+
+    /**
+     * Tests that the onTimeout() callback is triggered correctly.
+     */
+    @Test
+    public void testSendMgmtFrameTimeout() throws Exception {
+        WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+                WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, cb);
+
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+
+        // verify that even if onAck() callback is triggered after timeout,
+        // SendMgmtFrameCallback is not triggered again
+        sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+        mLooper.dispatchAll();
+        verify(cb, never()).onAck(anyInt());
+        verify(cb, times(1)).onFailure(anyInt());
+    }
+
+    /**
+     * Tests every possible test outcome followed by every other test outcome to ensure that the
+     * internal state is reset correctly between calls.
+     * i.e. (success, success), (success, failure), (success, timeout),
+     * (failure, failure), (failure, success), (failure, timeout),
+     * (timeout, timeout), (timeout, success), (timeout, failure)
+     *
+     * Also tests that internal state is reset correctly after a transient AIDL RemoteException.
+     */
+    @Test
+    public void testSendMgmtFrameMixed() throws Exception {
+        testSendMgmtFrameThrowsException();
+        testSendMgmtFrameSuccess();
+        testSendMgmtFrameSuccess();
+        testSendMgmtFrameFailure();
+        testSendMgmtFrameFailure();
+        testSendMgmtFrameTimeout();
+        testSendMgmtFrameTimeout();
+        testSendMgmtFrameSuccess();
+        testSendMgmtFrameTimeout();
+        testSendMgmtFrameFailure();
+        testSendMgmtFrameSuccess();
+    }
+
+    /**
+     * Tests that OnAck() does not perform any non-thread-safe operations on the binder thread.
+     *
+     * The sequence of instructions are:
+     * 1. post onAlarm() onto main thread
+     * 2. OnAck()
+     * 3. mLooper.dispatchAll()
+     *
+     * The actual order of execution is:
+     * 1. binder thread portion of OnAck()
+     * 2. onAlarm() (which purely executes on the main thread)
+     * 3. main thread portion of OnAck()
+     *
+     * If the binder thread portion of OnAck() is not thread-safe, it can possibly mess up
+     * onAlarm(). Tests that this does not occur.
+     */
+    @Test
+    public void testSendMgmtFrameTimeoutAckThreadSafe() throws Exception {
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, mSendMgmtFrameCallback);
+
+        // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+        // triggering onAlarm() ourselves during the test, manually post onto handler
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        // OnAck posts to the handler
+        sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+        mLooper.dispatchAll();
+        verify(mSendMgmtFrameCallback, never()).onAck(anyInt());
+        verify(mSendMgmtFrameCallback).onFailure(
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+    }
+
+    /**
+     * See {@link #testSendMgmtFrameTimeoutAckThreadSafe()}. This test replaces OnAck() with
+     * OnFailure().
+     */
+    @Test
+    public void testSendMgmtFrameTimeoutFailureThreadSafe() throws Exception {
+        final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+                ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+        doNothing().when(mClientInterface)
+                .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), handlerCaptor.capture());
+        mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+                Runnable::run, mSendMgmtFrameCallback);
+
+        // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+        // triggering onAlarm() ourselves during the test, manually post onto handler
+        handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+        // OnFailure posts to the handler
+        sendMgmtFrameEventCaptor.getValue().OnFailure(
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+        mLooper.dispatchAll();
+        verify(mSendMgmtFrameCallback).onFailure(
+                WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+    }
+
+    /**
+     * Tests getDeviceWiphyCapabililties
+     */
+    @Test
+    public void testGetDeviceWiphyCapabilities() throws Exception {
+        DeviceWiphyCapabilities capaExpected = new DeviceWiphyCapabilities();
+
+        capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+        capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+        capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+        capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ, true);
+        capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
+        capaExpected.setMaxNumberTxSpatialStreams(2);
+        capaExpected.setMaxNumberRxSpatialStreams(1);
+
+        when(mWificond.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME))
+                .thenReturn(capaExpected);
+
+        DeviceWiphyCapabilities capaActual =
+                mWificondControl.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME);
+        assertEquals(capaExpected, capaActual);
+    }
+
+    // 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> {
+        int mExpectedScanType;
+        private final Set<Integer> mExpectedFreqs;
+        private final List<byte[]> mExpectedSsids;
+
+        ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids) {
+            this.mExpectedScanType = expectedScanType;
+            this.mExpectedFreqs = expectedFreqs;
+            this.mExpectedSsids = expectedSsids;
+        }
+
+        @Override
+        public boolean matches(SingleScanSettings settings) {
+            if (settings.scanType != mExpectedScanType) {
+                return false;
+            }
+            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) {
+                List<byte[]> ssidSet = new ArrayList<>();
+                for (HiddenNetwork network : hiddenNetworks) {
+                    ssidSet.add(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 + '}';
+        }
+    }
+
+    private static class LocalNativeUtil {
+        private static final int SSID_BYTES_MAX_LEN = 32;
+
+        /**
+         * Converts an ArrayList<Byte> of UTF_8 byte values to string.
+         * The string will either be:
+         * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non
+         * null),
+         * or
+         * b) Hex string with no delimiters.
+         *
+         * @param bytes List of bytes for ssid.
+         * @throws IllegalArgumentException for null bytes.
+         */
+        public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) {
+            if (bytes == null) {
+                throw new IllegalArgumentException("null ssid bytes");
+            }
+            byte[] byteArray = byteArrayFromArrayList(bytes);
+            // Check for 0's in the byte stream in which case we cannot convert this into a string.
+            if (!bytes.contains(Byte.valueOf((byte) 0))) {
+                CharsetDecoder decoder = StandardCharsets.UTF_8.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) UTF-8 String encapsulated in quotes, or
+         * b) Hex string with no delimiters.
+         *
+         * @param ssidStr String to be converted.
+         * @throws IllegalArgumentException for null string.
+         */
+        public static ArrayList<Byte> decodeSsid(String ssidStr) {
+            ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr);
+            if (ssidBytes.size() > SSID_BYTES_MAX_LEN) {
+                throw new IllegalArgumentException(
+                        "ssid bytes size out of range: " + ssidBytes.size());
+            }
+            return ssidBytes;
+        }
+
+        /**
+         * 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 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();
+        }
+
+        /**
+         * Converts an string to an arraylist of UTF_8 byte values.
+         * These forms are acceptable:
+         * a) UTF-8 String encapsulated in quotes, or
+         * b) Hex string with no delimiters.
+         *
+         * @param str String to be converted.
+         * @throws IllegalArgumentException for null string.
+         */
+        public static ArrayList<Byte> hexOrQuotedStringToBytes(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));
+            }
+        }
+
+        /**
+         * Convert the string to byte array list.
+         *
+         * @return the UTF_8 char byte values of str, as an ArrayList.
+         * @throws IllegalArgumentException if a null or unencodable string is sent.
+         */
+        public static ArrayList<Byte> stringToByteArrayList(String str) {
+            if (str == null) {
+                throw new IllegalArgumentException("null string");
+            }
+            // Ensure that the provided string is UTF_8 encoded.
+            CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+            try {
+                ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str));
+                byte[] byteArray = new byte[encoded.remaining()];
+                encoded.get(byteArray);
+                return byteArrayToArrayList(byteArray);
+            } catch (CharacterCodingException cce) {
+                throw new IllegalArgumentException("cannot be utf-8 encoded", cce);
+            }
+        }
+
+        /**
+         * 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;
+        }
+
+        /**
+         * 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);
+        }
+    }
+}