Merge branch 'dev/10/fp2/security-aosp-qt-release' into int/10/fp2

* dev/10/fp2/security-aosp-qt-release:
  wifi: Reset to default SAP configuration when doing factory reset

Change-Id: I0861761cf147e2e8890645d1ac3510621f011be3
diff --git a/service/java/com/android/server/wifi/BaseWifiDiagnostics.java b/service/java/com/android/server/wifi/BaseWifiDiagnostics.java
index 2090cac..3abf510 100644
--- a/service/java/com/android/server/wifi/BaseWifiDiagnostics.java
+++ b/service/java/com/android/server/wifi/BaseWifiDiagnostics.java
@@ -1,6 +1,8 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
@@ -23,7 +25,11 @@
         mWifiNative = wifiNative;
     }
 
-    public synchronized void startLogging(boolean verboseEnabled) {
+    /**
+     * start wifi HAL dependent logging features
+     * @param ifaceName requesting to start logging
+     */
+    public synchronized void startLogging(@NonNull String ifaceName) {
         mFirmwareVersion = mWifiNative.getFirmwareVersion();
         mDriverVersion = mWifiNative.getDriverVersion();
         mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet();
@@ -33,7 +39,11 @@
 
     public synchronized void stopPacketLog() { }
 
-    public synchronized void stopLogging() { }
+    /**
+     * stop wifi HAL dependent logging features
+     * @param ifaceName requesting to stop logging
+     */
+    public synchronized void stopLogging(@NonNull String ifaceName) { }
 
     /**
      * Inform the diagnostics module of a connection event.
@@ -64,4 +74,11 @@
         pw.println("Driver Version is: " + mDriverVersion);
         pw.println("Supported Feature set: " + mSupportedFeatureSet);
     }
-}
\ No newline at end of file
+
+    /** enables/disables wifi verbose logging */
+    public synchronized void enableVerboseLogging(boolean verboseEnabled) { }
+
+    /** enables packet fate monitoring */
+    public void startPktFateMonitoring(@NonNull String ifaceName) {}
+
+}
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 9cf1d69..eb7a1fd 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -163,7 +163,7 @@
     private static final String EXTRA_UID = "uid";
     private static final String EXTRA_PACKAGE_NAME = "PackageName";
     private static final String EXTRA_PASSPOINT_CONFIGURATION = "PasspointConfiguration";
-    private static final int IPCLIENT_TIMEOUT_MS = 10_000;
+    private static final int IPCLIENT_TIMEOUT_MS = 60_000;
 
     private boolean mVerboseLoggingEnabled = false;
     private final WifiPermissionsWrapper mWifiPermissionsWrapper;
@@ -763,8 +763,14 @@
     private WifiStateTracker mWifiStateTracker;
     private final BackupManagerProxy mBackupManagerProxy;
     private final WrongPasswordNotifier mWrongPasswordNotifier;
+    private final ConnectionFailureNotifier mConnectionFailureNotifier;
     private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     private boolean mConnectedMacRandomzationSupported;
+    // Maximum duration to continue to log Wifi usability stats after a data stall is triggered.
+    @VisibleForTesting
+    public static final long DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS = 30 * 1000;
+    private long mDataStallTriggerTimeMs = -1;
+    private int mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN;
 
     public ClientModeImpl(Context context, FrameworkFacade facade, Looper looper,
                             UserManager userManager, WifiInjector wifiInjector,
@@ -811,7 +817,8 @@
         mSupplicantStateTracker =
                 mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler());
         mWifiConnectivityManager = mWifiInjector.makeWifiConnectivityManager(this);
-
+        mConnectionFailureNotifier = mWifiInjector.makeConnectionFailureNotifier(
+                mWifiConnectivityManager);
 
         mLinkProperties = new LinkProperties();
         mMcastLockManagerFilterController = new McastLockManagerFilterController();
@@ -959,6 +966,8 @@
                 mWifiMetrics.getHandler());
         mWifiMonitor.registerHandler(mInterfaceName, CMD_TARGET_BSSID,
                 mWifiMetrics.getHandler());
+        mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
+                mWifiInjector.getWifiLastResortWatchdog().getHandler());
     }
 
     private void setMulticastFilter(boolean enabled) {
@@ -1106,7 +1115,7 @@
         setSupplicantLogLevel();
         mCountryCode.enableVerboseLogging(verbose);
         mWifiScoreReport.enableVerboseLogging(mVerboseLoggingEnabled);
-        mWifiDiagnostics.startLogging(mVerboseLoggingEnabled);
+        mWifiDiagnostics.enableVerboseLogging(mVerboseLoggingEnabled);
         mWifiMonitor.enableVerboseLogging(verbose);
         mWifiNative.enableVerboseLogging(verbose);
         mWifiConfigManager.enableVerboseLogging(verbose);
@@ -1773,12 +1782,14 @@
      * Remove a Passpoint configuration synchronously.
      *
      * @param channel Channel for communicating with the state machine
+     * @param privileged Whether the caller is a privileged entity
      * @param fqdn The FQDN of the Passpoint configuration to remove
      * @return true on success
      */
-    public boolean syncRemovePasspointConfig(AsyncChannel channel, String fqdn) {
+    public boolean syncRemovePasspointConfig(AsyncChannel channel, boolean privileged,
+            String fqdn) {
         Message resultMsg = channel.sendMessageSynchronously(CMD_REMOVE_PASSPOINT_CONFIG,
-                fqdn);
+                privileged ? 1 : 0, 0, fqdn);
         if (messageIsNull(resultMsg)) return false;
         boolean result = (resultMsg.arg1 == SUCCESS);
         resultMsg.recycle();
@@ -1789,10 +1800,13 @@
      * Get the list of installed Passpoint configurations synchronously.
      *
      * @param channel Channel for communicating with the state machine
+     * @param privileged Whether the caller is a privileged entity
      * @return List of {@link PasspointConfiguration}
      */
-    public List<PasspointConfiguration> syncGetPasspointConfigs(AsyncChannel channel) {
-        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_PASSPOINT_CONFIGS);
+    public List<PasspointConfiguration> syncGetPasspointConfigs(AsyncChannel channel,
+            boolean privileged) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_PASSPOINT_CONFIGS,
+                privileged ? 1 : 0);
         if (messageIsNull(resultMsg)) return null;
         List<PasspointConfiguration> result = (List<PasspointConfiguration>) resultMsg.obj;
         resultMsg.recycle();
@@ -3146,6 +3160,16 @@
             mWifiScoreCard.noteConnectionFailure(mWifiInfo,
                     level2FailureCode, connectivityFailureCode);
         }
+        boolean isAssociationRejection = level2FailureCode
+                == WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION;
+        boolean isAuthenticationFailure = level2FailureCode
+                == WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE
+                && level2FailureReason != WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
+        if ((isAssociationRejection || isAuthenticationFailure)
+                && mWifiConfigManager.isInFlakyRandomizationSsidHotlist(mTargetNetworkId)) {
+            mConnectionFailureNotifier
+                    .showFailedToConnectDueToNoRandomizedMacSupportNotification(mTargetNetworkId);
+        }
         // if connected, this should be non-null.
         WifiConfiguration configuration = getCurrentWifiConfiguration();
         if (configuration == null) {
@@ -3373,12 +3397,14 @@
             Log.e(TAG, "No config to change MAC address to");
             return;
         }
-        MacAddress currentMac = MacAddress.fromString(mWifiNative.getMacAddress(mInterfaceName));
+        String currentMacString = mWifiNative.getMacAddress(mInterfaceName);
+        MacAddress currentMac = currentMacString == null ? null :
+                MacAddress.fromString(currentMacString);
         MacAddress newMac = config.getOrCreateRandomizedMacAddress();
         mWifiConfigManager.setNetworkRandomizedMacAddress(config.networkId, newMac);
         if (!WifiConfiguration.isValidMacAddressForRandomization(newMac)) {
             Log.wtf(TAG, "Config generated an invalid MAC address");
-        } else if (currentMac.equals(newMac)) {
+        } else if (newMac.equals(currentMac)) {
             Log.d(TAG, "No changes in MAC address");
         } else {
             mWifiMetrics.logStaEvent(StaEvent.TYPE_MAC_CHANGE, config);
@@ -3386,7 +3412,7 @@
                     mWifiNative.setMacAddress(mInterfaceName, newMac);
             Log.d(TAG, "ConnectedMacRandomization SSID(" + config.getPrintableSsid()
                     + "). setMacAddress(" + newMac.toString() + ") from "
-                    + currentMac.toString() + " = " + setMacSuccess);
+                    + currentMacString + " = " + setMacSuccess);
         }
     }
 
@@ -3678,11 +3704,13 @@
                     break;
                 case CMD_REMOVE_PASSPOINT_CONFIG:
                     int removeResult = mPasspointManager.removeProvider(
-                            message.sendingUid, (String) message.obj) ? SUCCESS : FAILURE;
+                            message.sendingUid, message.arg1 == 1, (String) message.obj)
+                            ? SUCCESS : FAILURE;
                     replyToMessage(message, message.what, removeResult);
                     break;
                 case CMD_GET_PASSPOINT_CONFIGS:
-                    replyToMessage(message, message.what, mPasspointManager.getProviderConfigs());
+                    replyToMessage(message, message.what, mPasspointManager.getProviderConfigs(
+                            message.sendingUid, message.arg1 == 1));
                     break;
                 case CMD_RESET_SIM_NETWORKS:
                     /* Defer this message until supplicant is started. */
@@ -3765,7 +3793,9 @@
         setRandomMacOui();
         mCountryCode.setReadyForChange(true);
 
-        mWifiDiagnostics.startLogging(mVerboseLoggingEnabled);
+        mWifiDiagnostics.startPktFateMonitoring(mInterfaceName);
+        mWifiDiagnostics.startLogging(mInterfaceName);
+
         mIsRunning = true;
         updateBatteryWorkSource(null);
 
@@ -3803,7 +3833,7 @@
      */
     private void stopClientMode() {
         // exiting supplicant started state is now only applicable to client mode
-        mWifiDiagnostics.stopLogging();
+        mWifiDiagnostics.stopLogging(mInterfaceName);
 
         mIsRunning = false;
         updateBatteryWorkSource(null);
@@ -4287,10 +4317,12 @@
                             && TextUtils.isEmpty(config.enterpriseConfig.getAnonymousIdentity())) {
                         String anonAtRealm = TelephonyUtil.getAnonymousIdentityWith3GppRealm(
                                 getTelephonyManager());
+                        // Use anonymous@<realm> when pseudonym is not available
                         config.enterpriseConfig.setAnonymousIdentity(anonAtRealm);
                     }
 
                     if (mWifiNative.connectToNetwork(mInterfaceName, config)) {
+                        mWifiInjector.getWifiLastResortWatchdog().noteStartConnectTime();
                         mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT, config);
                         mLastConnectAttemptTimestamp = mClock.getWallClockMillis();
                         mTargetWifiConfiguration = config;
@@ -4450,20 +4482,27 @@
                         // We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA'
                         if (config.enterpriseConfig != null
                                 && TelephonyUtil.isSimEapMethod(
-                                        config.enterpriseConfig.getEapMethod())
-                                // if using anonymous@<realm>, do not use pseudonym identity on
-                                // reauthentication. Instead, use full authentication using
-                                // anonymous@<realm> followed by encrypted IMSI every time.
-                                // This is because the encrypted IMSI spec does not specify its
-                                // compatibility with the pseudonym identity specified by EAP-AKA.
-                                && !TelephonyUtil.isAnonymousAtRealmIdentity(
-                                        config.enterpriseConfig.getAnonymousIdentity())) {
+                                        config.enterpriseConfig.getEapMethod())) {
                             String anonymousIdentity =
                                     mWifiNative.getEapAnonymousIdentity(mInterfaceName);
-                            if (mVerboseLoggingEnabled) {
-                                log("EAP Pseudonym: " + anonymousIdentity);
+                            if (!TextUtils.isEmpty(anonymousIdentity)
+                                    && !TelephonyUtil
+                                    .isAnonymousAtRealmIdentity(anonymousIdentity)) {
+                                String decoratedPseudonym = TelephonyUtil
+                                        .decoratePseudonymWith3GppRealm(getTelephonyManager(),
+                                                anonymousIdentity);
+                                if (decoratedPseudonym != null) {
+                                    anonymousIdentity = decoratedPseudonym;
+                                }
+                                if (mVerboseLoggingEnabled) {
+                                    log("EAP Pseudonym: " + anonymousIdentity);
+                                }
+                                // Save the pseudonym only if it is a real one
+                                config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
+                            } else {
+                                // Clear any stored pseudonyms
+                                config.enterpriseConfig.setAnonymousIdentity(null);
                             }
-                            config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
                             mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
                         }
                         sendNetworkStateChangeBroadcast(mLastBssid);
@@ -4517,7 +4556,8 @@
                     break;
                 case CMD_REMOVE_PASSPOINT_CONFIG:
                     String fqdn = (String) message.obj;
-                    if (mPasspointManager.removeProvider(message.sendingUid, fqdn)) {
+                    if (mPasspointManager.removeProvider(
+                            message.sendingUid, message.arg1 == 1, fqdn)) {
                         if (isProviderOwnedNetwork(mTargetNetworkId, fqdn)
                                 || isProviderOwnedNetwork(mLastNetworkId, fqdn)) {
                             logd("Disconnect from current network since its provider is removed");
@@ -5107,11 +5147,24 @@
                             }
                             mWifiScoreReport.noteIpCheck();
                         }
-                        int statusDataStall =
-                                mWifiDataStall.checkForDataStall(mLastLinkLayerStats, stats);
-                        if (statusDataStall != WifiIsUnusableEvent.TYPE_UNKNOWN) {
-                            mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
-                                    convertToUsabilityStatsTriggerType(statusDataStall), -1);
+                        int statusDataStall = mWifiDataStall.checkForDataStall(
+                                mLastLinkLayerStats, stats, mWifiInfo);
+                        if (mDataStallTriggerTimeMs == -1
+                                && statusDataStall != WifiIsUnusableEvent.TYPE_UNKNOWN) {
+                            mDataStallTriggerTimeMs = mClock.getElapsedSinceBootMillis();
+                            mLastStatusDataStall = statusDataStall;
+                        }
+                        if (mDataStallTriggerTimeMs != -1) {
+                            long elapsedTime =  mClock.getElapsedSinceBootMillis()
+                                    - mDataStallTriggerTimeMs;
+                            if (elapsedTime >= DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS) {
+                                mDataStallTriggerTimeMs = -1;
+                                mWifiMetrics.addToWifiUsabilityStatsList(
+                                        WifiUsabilityStats.LABEL_BAD,
+                                        convertToUsabilityStatsTriggerType(mLastStatusDataStall),
+                                        -1);
+                                mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN;
+                            }
                         }
                         mWifiMetrics.incrementWifiLinkLayerUsageStats(stats);
                         mLastLinkLayerStats = stats;
diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java
new file mode 100644
index 0000000..f4f89f0
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectionFailureNotificationBuilder.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.android.server.wifi;
+
+import android.annotation.NonNull;
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+import android.os.Handler;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
+
+/**
+ * Helper class for ConnectionFailureNotifier.
+ */
+public class ConnectionFailureNotificationBuilder {
+    private static final String TAG = "ConnectionFailureNotifier";
+
+    public static final String ACTION_SHOW_SET_RANDOMIZATION_DETAILS =
+            "com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS";
+    public static final String RANDOMIZATION_SETTINGS_NETWORK_ID =
+            "com.android.server.wifi.RANDOMIZATION_SETTINGS_NETWORK_ID";
+    public static final String RANDOMIZATION_SETTINGS_NETWORK_SSID =
+            "com.android.server.wifi.RANDOMIZATION_SETTINGS_NETWORK_SSID";
+
+    private Context mContext;
+    private String mPackageName;
+    private Resources mResources;
+    private FrameworkFacade mFrameworkFacade;
+    private WifiConnectivityManager mWifiConnectivityManager;
+    private NotificationManager mNotificationManager;
+    private Handler mHandler;
+
+    public ConnectionFailureNotificationBuilder(Context context, String packageName,
+            FrameworkFacade framework) {
+        mContext = context;
+        mPackageName = packageName;
+        mResources = context.getResources();
+        mFrameworkFacade = framework;
+    }
+
+    /**
+     * Creates a notification that alerts the user that the connection may be failing due to
+     * MAC randomization.
+     * @param config
+     */
+    public Notification buildNoMacRandomizationSupportNotification(
+            @NonNull WifiConfiguration config) {
+        String ssid = config.SSID;
+        String ssidAndSecurityType = config.getSsidAndSecurityTypeString();
+        String title = mResources.getString(
+                R.string.wifi_cannot_connect_with_randomized_mac_title, ssid);
+        String content = mResources.getString(
+                R.string.wifi_cannot_connect_with_randomized_mac_message);
+
+        Intent showDetailIntent = new Intent(ACTION_SHOW_SET_RANDOMIZATION_DETAILS)
+                .setPackage(mPackageName);
+        showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_ID, config.networkId);
+        showDetailIntent.putExtra(RANDOMIZATION_SETTINGS_NETWORK_SSID, ssidAndSecurityType);
+        PendingIntent pendingShowDetailIntent = mFrameworkFacade.getBroadcast(
+                mContext, 0, showDetailIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        return mFrameworkFacade.makeNotificationBuilder(mContext,
+                SystemNotificationChannels.NETWORK_ALERTS)
+                .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
+                .setTicker(title)
+                .setContentTitle(title)
+                .setContentText(content)
+                .setContentIntent(pendingShowDetailIntent)
+                .setShowWhen(false)
+                .setLocalOnly(true)
+                .setColor(mResources.getColor(R.color.system_notification_accent_color,
+                        mContext.getTheme()))
+                .setAutoCancel(true)
+                .build();
+    }
+
+    /**
+     * Creates an AlertDialog that allows the user to disable MAC randomization for a network.
+     * @param ssid the displayed SSID in the dialog
+     * @param onUserConfirm
+     */
+    public AlertDialog buildChangeMacRandomizationSettingDialog(
+            String ssid, DialogInterface.OnClickListener onUserConfirm) {
+        AlertDialog.Builder builder = mFrameworkFacade.makeAlertDialogBuilder(mContext)
+                .setTitle(mResources.getString(
+                        R.string.wifi_disable_mac_randomization_dialog_title))
+                .setMessage(mResources.getString(
+                        R.string.wifi_disable_mac_randomization_dialog_message, ssid))
+                .setPositiveButton(
+                        mResources.getString(
+                                R.string.wifi_disable_mac_randomization_dialog_confirm_text),
+                        onUserConfirm)
+                // A null listener allows the dialog to be dismissed directly.
+                .setNegativeButton(R.string.no, null);
+        AlertDialog dialog = builder.create();
+        dialog.setCanceledOnTouchOutside(false);
+        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        return dialog;
+    }
+}
diff --git a/service/java/com/android/server/wifi/ConnectionFailureNotifier.java b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java
new file mode 100644
index 0000000..bbef2ff
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectionFailureNotifier.java
@@ -0,0 +1,162 @@
+/*
+ * 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 com.android.server.wifi;
+
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+import android.os.Handler;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.internal.R;
+
+/**
+ * This class may be used to launch notifications when wifi connections fail.
+ */
+public class ConnectionFailureNotifier {
+    private static final String TAG = "ConnectionFailureNotifier";
+    public static final int NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID = 123;
+
+    private Context mContext;
+    private WifiInjector mWifiInjector;
+    private Resources mResources;
+    private FrameworkFacade mFrameworkFacade;
+    private WifiConfigManager mWifiConfigManager;
+    private WifiConnectivityManager mWifiConnectivityManager;
+    private NotificationManager mNotificationManager;
+    private Handler mHandler;
+    private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
+
+    public ConnectionFailureNotifier(
+            Context context, WifiInjector wifiInjector, FrameworkFacade framework,
+            WifiConfigManager wifiConfigManager, WifiConnectivityManager wifiConnectivityManager,
+            Handler handler) {
+        mContext = context;
+        mWifiInjector = wifiInjector;
+        mResources = context.getResources();
+        mFrameworkFacade = framework;
+        mWifiConfigManager = wifiConfigManager;
+        mWifiConnectivityManager = wifiConnectivityManager;
+        mNotificationManager = mWifiInjector.getNotificationManager();
+        mHandler = handler;
+        mConnectionFailureNotificationBuilder =
+                mWifiInjector.getConnectionFailureNotificationBuilder();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectionFailureNotificationBuilder
+                .ACTION_SHOW_SET_RANDOMIZATION_DETAILS);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(ConnectionFailureNotificationBuilder
+                                .ACTION_SHOW_SET_RANDOMIZATION_DETAILS)) {
+                            int networkId = intent.getIntExtra(
+                                    ConnectionFailureNotificationBuilder
+                                            .RANDOMIZATION_SETTINGS_NETWORK_ID,
+                                    WifiConfiguration.INVALID_NETWORK_ID);
+                            String ssidAndSecurityType = intent.getStringExtra(
+                                    ConnectionFailureNotificationBuilder
+                                            .RANDOMIZATION_SETTINGS_NETWORK_SSID);
+                            showRandomizationSettingsDialog(networkId, ssidAndSecurityType);
+                        }
+                    }
+                }, filter);
+    }
+
+    /**
+     * Shows a notification which will bring up a dialog which offers the user an option to disable
+     * MAC randomization on |networkdId|.
+     * @param networkId
+     */
+    public void showFailedToConnectDueToNoRandomizedMacSupportNotification(int networkId) {
+        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId);
+        if (config == null) {
+            return;
+        }
+        Notification notification = mConnectionFailureNotificationBuilder
+                .buildNoMacRandomizationSupportNotification(config);
+        mNotificationManager.notify(NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID, notification);
+    }
+
+    class DisableMacRandomizationListener implements DialogInterface.OnClickListener {
+        private WifiConfiguration mConfig;
+
+        DisableMacRandomizationListener(WifiConfiguration config) {
+            mConfig = config;
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            mHandler.post(() -> {
+                mConfig.macRandomizationSetting =
+                        WifiConfiguration.RANDOMIZATION_NONE;
+                mWifiConfigManager.addOrUpdateNetwork(mConfig, Process.SYSTEM_UID);
+                WifiConfiguration updatedConfig =
+                        mWifiConfigManager.getConfiguredNetwork(mConfig.networkId);
+                if (updatedConfig.macRandomizationSetting
+                        == WifiConfiguration.RANDOMIZATION_NONE) {
+                    String message = mResources.getString(
+                            R.string.wifi_disable_mac_randomization_dialog_success);
+                    mFrameworkFacade.showToast(mContext, message);
+                    mWifiConfigManager.enableNetwork(updatedConfig.networkId, true,
+                            Process.SYSTEM_UID);
+                    mWifiConnectivityManager.forceConnectivityScan(
+                            ClientModeImpl.WIFI_WORK_SOURCE);
+                } else {
+                    // Shouldn't ever fail, but here for completeness
+                    String message = mResources.getString(
+                            R.string.wifi_disable_mac_randomization_dialog_failure);
+                    mFrameworkFacade.showToast(mContext, message);
+                    Log.e(TAG, "Failed to modify mac randomization setting");
+                }
+            });
+        }
+    }
+
+    /**
+     * Class to show a AlertDialog which notifies the user of a network not being privacy
+     * compliant and then suggests an action.
+     */
+    private void showRandomizationSettingsDialog(int networkId, String ssidAndSecurityType) {
+        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId);
+        // Make sure the networkId is still pointing to the correct WifiConfiguration since
+        // there might be a large time gap between when the notification shows and when
+        // it's tapped.
+        if (config == null || ssidAndSecurityType == null
+                || !ssidAndSecurityType.equals(config.getSsidAndSecurityTypeString())) {
+            String message = mResources.getString(
+                    R.string.wifi_disable_mac_randomization_dialog_network_not_found);
+            mFrameworkFacade.showToast(mContext, message);
+            return;
+        }
+
+        AlertDialog dialog = mConnectionFailureNotificationBuilder
+                .buildChangeMacRandomizationSettingDialog(config.SSID,
+                        new DisableMacRandomizationListener(config));
+        dialog.show();
+    }
+}
diff --git a/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java b/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java
index 0c06488..b71d5a0 100644
--- a/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java
+++ b/service/java/com/android/server/wifi/DeletedEphemeralSsidsStoreData.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi;
 
+import android.annotation.Nullable;
+
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -44,7 +47,8 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         if (mSsidToTimeMap != null) {
             XmlUtil.writeNextValue(out, XML_TAG_SSID_LIST, mSsidToTimeMap);
@@ -52,7 +56,9 @@
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
diff --git a/service/java/com/android/server/wifi/DeviceConfigFacade.java b/service/java/com/android/server/wifi/DeviceConfigFacade.java
new file mode 100644
index 0000000..25cc2f7
--- /dev/null
+++ b/service/java/com/android/server/wifi/DeviceConfigFacade.java
@@ -0,0 +1,129 @@
+/*
+ * 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 com.android.server.wifi;
+
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class allows getting all configurable flags from DeviceConfig.
+ */
+public class DeviceConfigFacade {
+    private static final int DEFAULT_ABNORMAL_CONNECTION_DURATION_MS =
+            (int) TimeUnit.SECONDS.toMillis(30);
+    private static final String NAMESPACE = "wifi";
+    // Default duration for evaluating Wifi condition to trigger a data stall
+    // measured in milliseconds
+    public static final int DEFAULT_DATA_STALL_DURATION_MS = 1500;
+    // Default threshold of Tx throughput below which to trigger a data stall measured in Mbps
+    public static final int DEFAULT_DATA_STALL_TX_TPUT_THR_MBPS = 2;
+    // Default threshold of Rx throughput below which to trigger a data stall measured in Mbps
+    public static final int DEFAULT_DATA_STALL_RX_TPUT_THR_MBPS = 2;
+    // Default threshold of Tx packet error rate above which to trigger a data stall in percentage
+    public static final int DEFAULT_DATA_STALL_TX_PER_THR = 90;
+    // Default threshold of CCA level above which to trigger a data stall in percentage
+    public static final int DEFAULT_DATA_STALL_CCA_LEVEL_THR = 100;
+
+    /**
+     * Gets the feature flag for reporting abnormally long connections.
+     */
+    public boolean isAbnormalConnectionBugreportEnabled() {
+        return DeviceConfig.getBoolean(NAMESPACE, "abnormal_connection_bugreport_enabled", false);
+    }
+
+    /**
+     * Gets the threshold for classifying abnormally long connections.
+     */
+    public int getAbnormalConnectionDurationMs() {
+        return DeviceConfig.getInt(NAMESPACE, "abnormal_connection_duration_ms",
+                DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
+    }
+
+    /**
+     * Adds a listener that will be run on the specified executor.
+     * @param executor
+     * @param onPropertiesChangedListener
+     */
+    public void addOnPropertiesChangedListener(Executor executor,
+            DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, executor,
+                onPropertiesChangedListener);
+    }
+
+    /**
+     * Gets the duration of evaluating Wifi condition to trigger a data stall.
+     */
+    public int getDataStallDurationMs() {
+        return DeviceConfig.getInt(NAMESPACE, "data_stall_duration_ms",
+                DEFAULT_DATA_STALL_DURATION_MS);
+    }
+
+    /**
+     * Gets the threshold of Tx throughput below which to trigger a data stall.
+     */
+    public int getDataStallTxTputThrMbps() {
+        return DeviceConfig.getInt(NAMESPACE, "data_stall_tx_tput_thr_mbps",
+                DEFAULT_DATA_STALL_TX_TPUT_THR_MBPS);
+    }
+
+    /**
+     * Gets the threshold of Rx throughput below which to trigger a data stall.
+     */
+    public int getDataStallRxTputThrMbps() {
+        return DeviceConfig.getInt(NAMESPACE, "data_stall_rx_tput_thr_mbps",
+                DEFAULT_DATA_STALL_RX_TPUT_THR_MBPS);
+    }
+
+    /**
+     * Gets the threshold of Tx packet error rate above which to trigger a data stall.
+     */
+    public int getDataStallTxPerThr() {
+        return DeviceConfig.getInt(NAMESPACE, "data_stall_tx_per_thr",
+                DEFAULT_DATA_STALL_TX_PER_THR);
+    }
+
+    /**
+     * Gets the threshold of CCA level above which to trigger a data stall.
+     */
+    public int getDataStallCcaLevelThr() {
+        return DeviceConfig.getInt(NAMESPACE, "data_stall_cca_level_thr",
+                DEFAULT_DATA_STALL_CCA_LEVEL_THR);
+    }
+
+    /**
+     * Gets the Set of SSIDs in the flaky SSID hotlist.
+     */
+    public Set<String> getRandomizationFlakySsidHotlist() {
+        String ssidHotlist = DeviceConfig.getString(NAMESPACE,
+                "randomization_flaky_ssid_hotlist", "");
+        Set<String> result = new ArraySet<String>();
+        String[] ssidHotlistArray = ssidHotlist.split(",");
+        for (int i = 0; i < ssidHotlistArray.length; i++) {
+            String cur = ssidHotlistArray[i];
+            if (cur.length() == 0) {
+                continue;
+            }
+            // Make sure the SSIDs are quoted. Server side should not quote ssids.
+            result.add("\"" + cur + "\"");
+        }
+        return result;
+    }
+}
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index f3c5d4b..fe3027e 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -17,6 +17,7 @@
 package com.android.server.wifi;
 
 import android.app.ActivityManagerInternal;
+import android.app.AlertDialog;
 import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -35,6 +36,7 @@
 import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
+import android.widget.Toast;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.server.LocalServices;
@@ -45,6 +47,11 @@
  */
 public class FrameworkFacade {
     public static final String TAG = "FrameworkFacade";
+    /**
+     * NIAP global settings flag.
+     * Note: This should be added to {@link android.provider.Settings.Global}.
+     */
+    private static final String NIAP_MODE_SETTINGS_NAME = "niap_mode";
 
     private ActivityManagerInternal mActivityManagerInternal;
 
@@ -83,6 +90,13 @@
     }
 
     /**
+     * Returns whether the device is in NIAP mode or not.
+     */
+    public boolean isNiapModeOn(Context context) {
+        return getIntegerSetting(context, NIAP_MODE_SETTINGS_NAME, 0) == 1;
+    }
+
+    /**
      * Helper method for classes to register a ContentObserver
      * {@see ContentResolver#registerContentObserver(Uri,boolean,ContentObserver)}.
      *
@@ -215,4 +229,23 @@
     public Notification.Builder makeNotificationBuilder(Context context, String channelId) {
         return new Notification.Builder(context, channelId);
     }
+
+    /**
+     * Create a new instance of {@link AlertDialog.Builder}.
+     * @param context reference to a Context
+     * @return an instance of AlertDialog.Builder
+     */
+    public AlertDialog.Builder makeAlertDialogBuilder(Context context) {
+        return new AlertDialog.Builder(context);
+    }
+
+    /**
+     * Show a toast message
+     * @param context reference to a Context
+     * @param text the message to display
+     */
+    public void showToast(Context context, String text) {
+        Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
+        toast.show();
+    }
 }
diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java
index bb53a6e..e10234f 100644
--- a/service/java/com/android/server/wifi/HalDeviceManager.java
+++ b/service/java/com/android/server/wifi/HalDeviceManager.java
@@ -37,6 +37,7 @@
 import android.os.Handler;
 import android.os.HidlSupport.Mutable;
 import android.os.HwRemoteBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -73,13 +74,19 @@
     public static final int START_HAL_RETRY_TIMES = 3;
 
     private final Clock mClock;
+    private final Handler mEventHandler;
+    private WifiDeathRecipient mIWifiDeathRecipient;
+    private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
 
     // cache the value for supporting vendor HAL or not
     private boolean mIsVendorHalSupported = false;
 
     // public API
-    public HalDeviceManager(Clock clock) {
+    public HalDeviceManager(Clock clock, Looper looper) {
         mClock = clock;
+        mEventHandler = new Handler(looper);
+        mIWifiDeathRecipient = new WifiDeathRecipient();
+        mServiceManagerDeathRecipient = new ServiceManagerDeathRecipient();
 
         mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashMap<>());
         mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashMap<>());
@@ -631,15 +638,19 @@
         mRttControllerLifecycleCallbacks.clear();
     }
 
-    private final HwRemoteBinder.DeathRecipient mServiceManagerDeathRecipient =
-            cookie -> {
+    private class ServiceManagerDeathRecipient implements HwRemoteBinder.DeathRecipient {
+        @Override
+        public void serviceDied(long cookie) {
+            mEventHandler.post(() -> {
                 Log.wtf(TAG, "IServiceManager died: cookie=" + cookie);
                 synchronized (mLock) {
                     mServiceManager = null;
                     // theoretically can call initServiceManager again here - but
                     // there's no point since most likely system is going to reboot
                 }
-            };
+            });
+        }
+    }
 
     private final IServiceNotification mServiceNotificationCallback =
             new IServiceNotification.Stub() {
@@ -718,8 +729,10 @@
         }
     }
 
-    private final HwRemoteBinder.DeathRecipient mIWifiDeathRecipient =
-            cookie -> {
+    private class WifiDeathRecipient implements HwRemoteBinder.DeathRecipient {
+        @Override
+        public void serviceDied(long cookie) {
+            mEventHandler.post(() -> {
                 Log.e(TAG, "IWifi HAL service died! Have a listener for it ... cookie=" + cookie);
                 synchronized (mLock) { // prevents race condition with surrounding method
                     mWifi = null;
@@ -727,7 +740,9 @@
                     teardownInternal();
                     // don't restart: wait for registration notification
                 }
-            };
+            });
+        }
+    }
 
     /**
      * Initialize IWifi and register death listener and event callback.
@@ -1264,21 +1279,26 @@
     private class WifiEventCallback extends IWifiEventCallback.Stub {
         @Override
         public void onStart() throws RemoteException {
-            if (VDBG) Log.d(TAG, "IWifiEventCallback.onStart");
-            // NOP: only happens in reaction to my calls - will handle directly
+            mEventHandler.post(() -> {
+                if (VDBG) Log.d(TAG, "IWifiEventCallback.onStart");
+                // NOP: only happens in reaction to my calls - will handle directly
+            });
         }
 
         @Override
         public void onStop() throws RemoteException {
-            if (VDBG) Log.d(TAG, "IWifiEventCallback.onStop");
-            // NOP: only happens in reaction to my calls - will handle directly
+            mEventHandler.post(() -> {
+                if (VDBG) Log.d(TAG, "IWifiEventCallback.onStop");
+                // NOP: only happens in reaction to my calls - will handle directly
+            });
         }
 
         @Override
         public void onFailure(WifiStatus status) throws RemoteException {
-            Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status));
-            teardownInternal();
-
+            mEventHandler.post(() -> {
+                Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status));
+                teardownInternal();
+            });
             // No need to do anything else: listeners may (will) re-start Wi-Fi
         }
     }
@@ -1703,9 +1723,11 @@
             int requestedIfaceType, WifiIfaceInfo[][] currentIfaces, int numNecessaryInterfaces) {
         // rule 0: check for any low priority interfaces
         int numAvailableLowPriorityInterfaces = 0;
-        for (InterfaceCacheEntry entry : mInterfaceInfoCache.values()) {
-            if (entry.type == existingIfaceType && entry.isLowPriority) {
-                numAvailableLowPriorityInterfaces++;
+        synchronized (mLock) {
+            for (InterfaceCacheEntry entry : mInterfaceInfoCache.values()) {
+                if (entry.type == existingIfaceType && entry.isLowPriority) {
+                    numAvailableLowPriorityInterfaces++;
+                }
             }
         }
         if (numAvailableLowPriorityInterfaces >= numNecessaryInterfaces) {
@@ -1760,8 +1782,10 @@
         LongSparseArray<WifiIfaceInfo> orderedListLowPriority = new LongSparseArray<>();
         LongSparseArray<WifiIfaceInfo> orderedList = new LongSparseArray<>();
         for (WifiIfaceInfo info : interfaces) {
-            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(
-                    Pair.create(info.name, getType(info.iface)));
+            InterfaceCacheEntry cacheEntry;
+            synchronized (mLock) {
+                cacheEntry = mInterfaceInfoCache.get(Pair.create(info.name, getType(info.iface)));
+            }
             if (cacheEntry == null) {
                 Log.e(TAG,
                         "selectInterfacesToDelete: can't find cache entry with name=" + info.name);
diff --git a/service/java/com/android/server/wifi/HostapdHal.java b/service/java/com/android/server/wifi/HostapdHal.java
index 69c787f..5ac1734 100644
--- a/service/java/com/android/server/wifi/HostapdHal.java
+++ b/service/java/com/android/server/wifi/HostapdHal.java
@@ -33,12 +33,16 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.WifiNative.HostapdDeathEventHandler;
+import com.android.server.wifi.util.ApConfigUtil;
 import com.android.server.wifi.util.NativeUtil;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import javax.annotation.concurrent.ThreadSafe;
 
@@ -51,6 +55,8 @@
     private static final String TAG = "HostapdHal";
     @VisibleForTesting
     public static final String HAL_INSTANCE_NAME = "default";
+    @VisibleForTesting
+    public static final long WAIT_FOR_DEATH_TIMEOUT_MS = 50L;
 
     private final Object mLock = new Object();
     private boolean mVerboseLoggingEnabled = false;
@@ -59,6 +65,8 @@
     private final boolean mEnableIeee80211AC;
     private final List<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange>
             mAcsChannelRanges;
+    private boolean mForceApChannel = false;
+    private int mForcedApChannel;
 
     // Hostapd HAL interface objects
     private IServiceManager mIServiceManager = null;
@@ -230,11 +238,11 @@
      * Link to death for IHostapd object.
      * @return true on success, false otherwise.
      */
-    private boolean linkToHostapdDeath() {
+    private boolean linkToHostapdDeath(HwRemoteBinder.DeathRecipient deathRecipient, long cookie) {
         synchronized (mLock) {
             if (mIHostapd == null) return false;
             try {
-                if (!mIHostapd.linkToDeath(mHostapdDeathRecipient, ++mDeathRecipientCookie)) {
+                if (!mIHostapd.linkToDeath(deathRecipient, cookie)) {
                     Log.wtf(TAG, "Error on linkToDeath on IHostapd");
                     hostapdServiceDiedHandler(mDeathRecipientCookie);
                     return false;
@@ -282,7 +290,7 @@
                 Log.e(TAG, "Got null IHostapd service. Stopping hostapd HIDL startup");
                 return false;
             }
-            if (!linkToHostapdDeath()) {
+            if (!linkToHostapdDeath(mHostapdDeathRecipient, ++mDeathRecipientCookie)) {
                 mIHostapd = null;
                 return false;
             }
@@ -296,6 +304,22 @@
     }
 
     /**
+     * Enable force-soft-AP-channel mode which takes effect when soft AP starts next time
+     * @param forcedApChannel The forced IEEE channel number
+     */
+    void enableForceSoftApChannel(int forcedApChannel) {
+        mForceApChannel = true;
+        mForcedApChannel = forcedApChannel;
+    }
+
+    /**
+     * Disable force-soft-AP-channel mode which take effect when soft AP starts next time
+     */
+    void disableForceSoftApChannel() {
+        mForceApChannel = false;
+    }
+
+    /**
      * Add and start a new access point.
      *
      * @param ifaceName Name of the interface.
@@ -317,7 +341,15 @@
                 Log.e(TAG, "Unrecognized apBand " + config.apBand);
                 return false;
             }
-            if (mEnableAcs) {
+            if (mForceApChannel) {
+                ifaceParams.channelParams.enableAcs = false;
+                ifaceParams.channelParams.channel = mForcedApChannel;
+                if (mForcedApChannel <= ApConfigUtil.HIGHEST_2G_AP_CHANNEL) {
+                    ifaceParams.channelParams.band = IHostapd.Band.BAND_2_4_GHZ;
+                } else {
+                    ifaceParams.channelParams.band = IHostapd.Band.BAND_5_GHZ;
+                }
+            } else if (mEnableAcs) {
                 ifaceParams.channelParams.enableAcs = true;
                 ifaceParams.channelParams.acsShouldExcludeDfs = true;
             } else {
@@ -486,10 +518,19 @@
     }
 
     /**
-     * Terminate the hostapd daemon.
+     * Terminate the hostapd daemon & wait for it's death.
      */
     public void terminate() {
         synchronized (mLock) {
+            // Register for a new death listener to block until hostapd is dead.
+            final long waitForDeathCookie = new Random().nextLong();
+            final CountDownLatch waitForDeathLatch = new CountDownLatch(1);
+            linkToHostapdDeath((cookie) -> {
+                Log.d(TAG, "IHostapd died: cookie=" + cookie);
+                if (cookie != waitForDeathCookie) return;
+                waitForDeathLatch.countDown();
+            }, waitForDeathCookie);
+
             final String methodStr = "terminate";
             if (!checkHostapdAndLogFailure(methodStr)) return;
             try {
@@ -497,6 +538,15 @@
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
             }
+
+            // Now wait for death listener callback to confirm that it's dead.
+            try {
+                if (!waitForDeathLatch.await(WAIT_FOR_DEATH_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    Log.w(TAG, "Timed out waiting for confirmation of hostapd death");
+                }
+            } catch (InterruptedException e) {
+                Log.w(TAG, "Failed to wait for hostapd death");
+            }
         }
     }
 
diff --git a/service/java/com/android/server/wifi/MacAddressUtil.java b/service/java/com/android/server/wifi/MacAddressUtil.java
new file mode 100644
index 0000000..4739b61
--- /dev/null
+++ b/service/java/com/android/server/wifi/MacAddressUtil.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.android.server.wifi;
+
+import android.net.MacAddress;
+import android.net.wifi.WifiConfiguration;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.ProviderException;
+import java.security.UnrecoverableKeyException;
+import java.util.Arrays;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+
+/**
+ * Contains helper methods to support MAC randomization.
+ */
+public class MacAddressUtil {
+    private static final String TAG = "MacAddressUtil";
+    private static final String MAC_RANDOMIZATION_ALIAS = "MacRandSecret";
+    private static final long MAC_ADDRESS_VALID_LONG_MASK = (1L << 48) - 1;
+    private static final long MAC_ADDRESS_LOCALLY_ASSIGNED_MASK = 1L << 41;
+    private static final long MAC_ADDRESS_MULTICAST_MASK = 1L << 40;
+
+    /**
+     * Computes the persistent randomized MAC of the given configuration using the given
+     * hash function.
+     * @param config the WifiConfiguration to compute MAC address for
+     * @param hashFunction the hash function that will perform the MAC address computation.
+     * @return The persistent randomized MAC address or null if inputs are invalid.
+     */
+    public MacAddress calculatePersistentMacForConfiguration(WifiConfiguration config,
+            Mac hashFunction) {
+        if (config == null || hashFunction == null) {
+            return null;
+        }
+        byte[] hashedBytes;
+        try {
+            hashedBytes = hashFunction.doFinal(config.getSsidAndSecurityTypeString()
+                    .getBytes(StandardCharsets.UTF_8));
+        } catch (ProviderException | IllegalStateException e) {
+            Log.e(TAG, "Failure in calculatePersistentMac", e);
+            return null;
+        }
+        ByteBuffer bf = ByteBuffer.wrap(hashedBytes);
+        long longFromSsid = bf.getLong();
+        /**
+         * Masks the generated long so that it represents a valid randomized MAC address.
+         * Specifically, this sets the locally assigned bit to 1, multicast bit to 0
+         */
+        longFromSsid &= MAC_ADDRESS_VALID_LONG_MASK;
+        longFromSsid |= MAC_ADDRESS_LOCALLY_ASSIGNED_MASK;
+        longFromSsid &= ~MAC_ADDRESS_MULTICAST_MASK;
+        bf.clear();
+        bf.putLong(0, longFromSsid);
+
+        // MacAddress.fromBytes requires input of length 6, which is obtained from the
+        // last 6 bytes from the generated long.
+        MacAddress macAddress = MacAddress.fromBytes(Arrays.copyOfRange(bf.array(), 2, 8));
+        return macAddress;
+    }
+
+    /**
+     * Retrieves a Hash function that could be used to calculate the persistent randomized MAC
+     * for a WifiConfiguration.
+     * @param uid the UID of the KeyStore to get the secret of the hash function from.
+     */
+    public Mac obtainMacRandHashFunction(int uid) {
+        try {
+            KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(uid);
+            // tries to retrieve the secret, and generate a new one if it's unavailable.
+            Key key = keyStore.getKey(MAC_RANDOMIZATION_ALIAS, null);
+            if (key == null) {
+                key = generateAndPersistNewMacRandomizationSecret(uid);
+            }
+            if (key == null) {
+                Log.e(TAG, "Failed to generate secret for " + MAC_RANDOMIZATION_ALIAS);
+                return null;
+            }
+            Mac result = Mac.getInstance("HmacSHA256");
+            result.init(key);
+            return result;
+        } catch (KeyStoreException | NoSuchAlgorithmException | InvalidKeyException
+                | UnrecoverableKeyException | NoSuchProviderException e) {
+            Log.e(TAG, "Failure in obtainMacRandHashFunction", e);
+            return null;
+        }
+    }
+
+    /**
+     * Generates and returns a secret key to use for Mac randomization.
+     * Will also persist the generated secret inside KeyStore, accessible in the
+     * future with KeyGenerator#getKey.
+     */
+    private SecretKey generateAndPersistNewMacRandomizationSecret(int uid) {
+        try {
+            KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                    KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
+            keyGenerator.init(
+                    new KeyGenParameterSpec.Builder(MAC_RANDOMIZATION_ALIAS,
+                            KeyProperties.PURPOSE_SIGN)
+                            .setUid(uid)
+                            .build());
+            return keyGenerator.generateKey();
+        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
+                | NoSuchProviderException | ProviderException e) {
+            Log.e(TAG, "Failure in generateMacRandomizationSecret", e);
+            return null;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/NetworkListStoreData.java b/service/java/com/android/server/wifi/NetworkListStoreData.java
index 6966471..52e655b 100644
--- a/service/java/com/android/server/wifi/NetworkListStoreData.java
+++ b/service/java/com/android/server/wifi/NetworkListStoreData.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION;
+
+import android.annotation.Nullable;
 import android.content.Context;
 import android.net.IpConfiguration;
 import android.net.wifi.WifiConfiguration;
@@ -25,6 +28,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
 import com.android.server.wifi.util.XmlUtil.NetworkSelectionStatusXmlUtil;
@@ -66,19 +70,22 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
-        serializeNetworkList(out, mConfigurations);
+        serializeNetworkList(out, mConfigurations, encryptionUtil);
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
             return;
         }
-        mConfigurations = parseNetworkList(in, outerTagDepth);
+        mConfigurations = parseNetworkList(in, outerTagDepth, version, encryptionUtil);
     }
 
     @Override
@@ -118,33 +125,38 @@
      *
      * @param out The output stream to serialize the data to
      * @param networkList The network list to serialize
+     * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil}
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private void serializeNetworkList(XmlSerializer out, List<WifiConfiguration> networkList)
+    private void serializeNetworkList(XmlSerializer out, List<WifiConfiguration> networkList,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         if (networkList == null) {
             return;
         }
         for (WifiConfiguration network : networkList) {
-            serializeNetwork(out, network);
+            serializeNetwork(out, network, encryptionUtil);
         }
     }
 
     /**
      * Serialize a {@link WifiConfiguration} to an output stream in XML format.
-     * @param out
-     * @param config
+     *
+     * @param out The output stream to serialize the data to
+     * @param config The network config to serialize
+     * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil}
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private void serializeNetwork(XmlSerializer out, WifiConfiguration config)
+    private void serializeNetwork(XmlSerializer out, WifiConfiguration config,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
 
         // Serialize WifiConfiguration.
         XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
-        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, config);
+        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, config, encryptionUtil);
         XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
 
         // Serialize network selection status.
@@ -162,7 +174,7 @@
                 && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
             XmlUtil.writeNextSectionStart(
                     out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
-            WifiEnterpriseConfigXmlUtil.writeToXml(out, config.enterpriseConfig);
+            WifiEnterpriseConfigXmlUtil.writeToXml(out, config.enterpriseConfig, encryptionUtil);
             XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
         }
 
@@ -174,11 +186,15 @@
      *
      * @param in The input stream to read from
      * @param outerTagDepth The XML tag depth of the outer XML block
+     * @param version Version of config store file.
+     * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil}
      * @return List of {@link WifiConfiguration}
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private List<WifiConfiguration> parseNetworkList(XmlPullParser in, int outerTagDepth)
+    private List<WifiConfiguration> parseNetworkList(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         List<WifiConfiguration> networkList = new ArrayList<>();
         while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_NETWORK,
@@ -186,7 +202,8 @@
             // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are
             // fatal and should abort the entire loading process.
             try {
-                WifiConfiguration config = parseNetwork(in, outerTagDepth + 1);
+                WifiConfiguration config =
+                        parseNetwork(in, outerTagDepth + 1, version, encryptionUtil);
                 networkList.add(config);
             } catch (RuntimeException e) {
                 // Failed to parse this network, skip it.
@@ -201,11 +218,15 @@
      *
      * @param in The input stream to read from
      * @param outerTagDepth The XML tag depth of the outer XML block
+     * @param version Version of config store file.
+     * @param encryptionUtil Instance of {@link WifiConfigStoreEncryptionUtil}
      * @return {@link WifiConfiguration}
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private WifiConfiguration parseNetwork(XmlPullParser in, int outerTagDepth)
+    private WifiConfiguration parseNetwork(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         Pair<String, WifiConfiguration> parsedConfig = null;
         NetworkSelectionStatus status = null;
@@ -220,7 +241,9 @@
                         throw new XmlPullParserException("Detected duplicate tag for: "
                                 + XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
                     }
-                    parsedConfig = WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                    parsedConfig = WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1,
+                            version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                            encryptionUtil);
                     break;
                 case XML_TAG_SECTION_HEADER_NETWORK_STATUS:
                     if (status != null) {
@@ -242,7 +265,9 @@
                                 + XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
                     }
                     enterpriseConfig =
-                            WifiEnterpriseConfigXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                            WifiEnterpriseConfigXmlUtil.parseFromXml(in, outerTagDepth + 1,
+                            version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                            encryptionUtil);
                     break;
                 default:
                     throw new XmlPullParserException("Unknown tag under "
diff --git a/service/java/com/android/server/wifi/NetworkRequestStoreData.java b/service/java/com/android/server/wifi/NetworkRequestStoreData.java
index b74e644..7457079 100644
--- a/service/java/com/android/server/wifi/NetworkRequestStoreData.java
+++ b/service/java/com/android/server/wifi/NetworkRequestStoreData.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
+import android.annotation.Nullable;
 import android.net.MacAddress;
 import android.util.Log;
 
 import com.android.server.wifi.WifiNetworkFactory.AccessPoint;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
 
@@ -29,7 +31,7 @@
 
 import java.io.IOException;
 import java.util.HashMap;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -87,13 +89,16 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         serializeApprovedAccessPointsMap(out, mDataSource.toSerialize());
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
@@ -213,7 +218,7 @@
      */
     private Set<AccessPoint> parseApprovedAccessPoints(XmlPullParser in, int outerTagDepth)
             throws XmlPullParserException, IOException {
-        Set<AccessPoint> approvedAccessPoints = new HashSet<>();
+        Set<AccessPoint> approvedAccessPoints = new LinkedHashSet<>();
         while (XmlUtil.gotoNextSectionWithNameOrEnd(
                 in, XML_TAG_SECTION_HEADER_ACCESS_POINT, outerTagDepth)) {
             // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are
diff --git a/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
index 9627a9d..e973bdb 100644
--- a/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
+++ b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION;
+
+import android.annotation.Nullable;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiNetworkSuggestion;
@@ -26,6 +29,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.PerAppInfo;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
 
@@ -98,19 +102,23 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
-        serializeNetworkSuggestionsMap(out, mDataSource.toSerialize());
+        serializeNetworkSuggestionsMap(out, mDataSource.toSerialize(), encryptionUtil);
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
             return;
         }
-        mDataSource.fromDeserialized(parseNetworkSuggestionsMap(in, outerTagDepth));
+        mDataSource.fromDeserialized(
+                parseNetworkSuggestionsMap(in, outerTagDepth, version, encryptionUtil));
     }
 
     @Override
@@ -140,7 +148,8 @@
      * @throws IOException
      */
     private void serializeNetworkSuggestionsMap(
-            XmlSerializer out, final Map<String, PerAppInfo> networkSuggestionsMap)
+            XmlSerializer out, final Map<String, PerAppInfo> networkSuggestionsMap,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         if (networkSuggestionsMap == null) {
             return;
@@ -155,7 +164,7 @@
             XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_PACKAGE_NAME, packageName);
             XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_HAS_USER_APPROVED, hasUserApproved);
             XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_MAX_SIZE, maxSize);
-            serializeExtNetworkSuggestions(out, networkSuggestions);
+            serializeExtNetworkSuggestions(out, networkSuggestions, encryptionUtil);
             XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_PER_APP);
         }
     }
@@ -167,10 +176,11 @@
      * @throws IOException
      */
     private void serializeExtNetworkSuggestions(
-            XmlSerializer out, final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)
+            XmlSerializer out, final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) {
-            serializeNetworkSuggestion(out, extNetworkSuggestion.wns);
+            serializeNetworkSuggestion(out, extNetworkSuggestion.wns, encryptionUtil);
         }
     }
 
@@ -181,13 +191,15 @@
      * @throws IOException
      */
     private void serializeNetworkSuggestion(XmlSerializer out,
-                                            final WifiNetworkSuggestion suggestion)
+            final WifiNetworkSuggestion suggestion,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION);
 
         // Serialize WifiConfiguration.
         XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
-        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, suggestion.wifiConfiguration);
+        WifiConfigurationXmlUtil.writeToXmlForConfigStore(
+                out, suggestion.wifiConfiguration, encryptionUtil);
         XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
         // Serialize enterprise configuration for enterprise networks.
         if (suggestion.wifiConfiguration.enterpriseConfig != null
@@ -196,7 +208,7 @@
             XmlUtil.writeNextSectionStart(
                     out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
             XmlUtil.WifiEnterpriseConfigXmlUtil.writeToXml(
-                    out, suggestion.wifiConfiguration.enterpriseConfig);
+                    out, suggestion.wifiConfiguration.enterpriseConfig, encryptionUtil);
             XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
         }
 
@@ -218,7 +230,9 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private Map<String, PerAppInfo> parseNetworkSuggestionsMap(XmlPullParser in, int outerTagDepth)
+    private Map<String, PerAppInfo> parseNetworkSuggestionsMap(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         Map<String, PerAppInfo> networkSuggestionsMap = new HashMap<>();
         while (XmlUtil.gotoNextSectionWithNameOrEnd(
@@ -233,7 +247,8 @@
                 int maxSize = (int) XmlUtil.readNextValueWithName(in, XML_TAG_SUGGESTOR_MAX_SIZE);
                 PerAppInfo perAppInfo = new PerAppInfo(packageName);
                 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
-                        parseExtNetworkSuggestions(in, outerTagDepth + 1, perAppInfo);
+                        parseExtNetworkSuggestions(
+                                in, outerTagDepth + 1, version, encryptionUtil, perAppInfo);
                 perAppInfo.hasUserApproved = hasUserApproved;
                 perAppInfo.maxSize = maxSize;
                 perAppInfo.extNetworkSuggestions.addAll(extNetworkSuggestions);
@@ -253,7 +268,8 @@
      * @throws IOException
      */
     private Set<ExtendedWifiNetworkSuggestion> parseExtNetworkSuggestions(
-            XmlPullParser in, int outerTagDepth, PerAppInfo perAppInfo)
+            XmlPullParser in, int outerTagDepth, @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil, PerAppInfo perAppInfo)
             throws XmlPullParserException, IOException {
         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>();
         while (XmlUtil.gotoNextSectionWithNameOrEnd(
@@ -262,7 +278,7 @@
             // fatal and should abort the entire loading process.
             try {
                 WifiNetworkSuggestion networkSuggestion =
-                        parseNetworkSuggestion(in, outerTagDepth + 1);
+                        parseNetworkSuggestion(in, outerTagDepth + 1, version, encryptionUtil);
                 extNetworkSuggestions.add(ExtendedWifiNetworkSuggestion.fromWns(
                         networkSuggestion, perAppInfo));
             } catch (RuntimeException e) {
@@ -279,7 +295,9 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    private WifiNetworkSuggestion parseNetworkSuggestion(XmlPullParser in, int outerTagDepth)
+    private WifiNetworkSuggestion parseNetworkSuggestion(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         Pair<String, WifiConfiguration> parsedConfig = null;
         WifiEnterpriseConfig enterpriseConfig = null;
@@ -324,7 +342,9 @@
                                     + XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
                         }
                         parsedConfig = WifiConfigurationXmlUtil.parseFromXml(
-                                in, outerTagDepth + 1);
+                                in, outerTagDepth + 1,
+                            version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                            encryptionUtil);
                         break;
                     case XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION:
                         if (enterpriseConfig != null) {
@@ -332,7 +352,9 @@
                                     + XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION);
                         }
                         enterpriseConfig = XmlUtil.WifiEnterpriseConfigXmlUtil.parseFromXml(
-                                in, outerTagDepth + 1);
+                                in, outerTagDepth + 1,
+                            version >= ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                            encryptionUtil);
                         break;
                     default:
                         throw new XmlPullParserException("Unknown tag under "
diff --git a/service/java/com/android/server/wifi/RandomizedMacStoreData.java b/service/java/com/android/server/wifi/RandomizedMacStoreData.java
index 1e4d972..ecbd717 100644
--- a/service/java/com/android/server/wifi/RandomizedMacStoreData.java
+++ b/service/java/com/android/server/wifi/RandomizedMacStoreData.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wifi;
 
+import android.annotation.Nullable;
+
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -40,7 +43,8 @@
     RandomizedMacStoreData() {}
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         if (mMacMapping != null) {
             XmlUtil.writeNextValue(out, XML_TAG_MAC_MAP, mMacMapping);
@@ -48,7 +52,9 @@
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
diff --git a/service/java/com/android/server/wifi/ScanRequestProxy.java b/service/java/com/android/server/wifi/ScanRequestProxy.java
index efbb7b6..a467844 100644
--- a/service/java/com/android/server/wifi/ScanRequestProxy.java
+++ b/service/java/com/android/server/wifi/ScanRequestProxy.java
@@ -484,11 +484,14 @@
         settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
                 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
         if (mScanningForHiddenNetworksEnabled) {
-            // retrieve the list of hidden network SSIDs to scan for, if enabled.
+            // retrieve the list of hidden network SSIDs from saved network to scan for, if enabled.
             List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList =
-                    mWifiConfigManager.retrieveHiddenNetworkList();
+                    new ArrayList<>(mWifiConfigManager.retrieveHiddenNetworkList());
+            // retrieve the list of hidden network SSIDs from Network suggestion to scan for.
+            hiddenNetworkList.addAll(
+                    mWifiInjector.getWifiNetworkSuggestionsManager().retrieveHiddenNetworkList());
             settings.hiddenNetworks = hiddenNetworkList.toArray(
-                    new WifiScanner.ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
+                    new WifiScanner.ScanSettings.HiddenNetwork[0]);
         }
         mWifiScanner.startScan(settings, new ScanRequestProxyScanListener(), workSource);
         return true;
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index b92dab7..79bc46f 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -95,6 +95,8 @@
 
     private long mStartTimestamp = -1;
 
+    private BaseWifiDiagnostics mWifiDiagnostics;
+
     /**
      * Listener for soft AP events.
      */
@@ -127,7 +129,8 @@
                          @NonNull WifiApConfigStore wifiApConfigStore,
                          @NonNull SoftApModeConfiguration apConfig,
                          @NonNull WifiMetrics wifiMetrics,
-                         @NonNull SarManager sarManager) {
+                         @NonNull SarManager sarManager,
+                         @NonNull BaseWifiDiagnostics wifiDiagnostics) {
         mContext = context;
         mFrameworkFacade = framework;
         mWifiNative = wifiNative;
@@ -143,6 +146,7 @@
         }
         mWifiMetrics = wifiMetrics;
         mSarManager = sarManager;
+        mWifiDiagnostics = wifiDiagnostics;
         mStateMachine = new SoftApStateMachine(looper);
     }
 
@@ -287,6 +291,7 @@
             Log.e(TAG, "Soft AP start failed");
             return ERROR_GENERIC;
         }
+        mWifiDiagnostics.startLogging(mApInterfaceName);
         mStartTimestamp = SystemClock.elapsedRealtime();
         Log.d(TAG, "Soft AP is started");
 
@@ -297,6 +302,7 @@
      * Teardown soft AP and teardown the interface.
      */
     private void stopSoftAp() {
+        mWifiDiagnostics.stopLogging(mApInterfaceName);
         mWifiNative.teardownInterface(mApInterfaceName);
         Log.d(TAG, "Soft AP is stopped");
     }
diff --git a/service/java/com/android/server/wifi/SsidSetStoreData.java b/service/java/com/android/server/wifi/SsidSetStoreData.java
index 7d1b993..36b547c 100644
--- a/service/java/com/android/server/wifi/SsidSetStoreData.java
+++ b/service/java/com/android/server/wifi/SsidSetStoreData.java
@@ -16,8 +16,10 @@
 
 package com.android.server.wifi;
 
+import android.annotation.Nullable;
 import android.text.TextUtils;
 
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -74,7 +76,8 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         Set<String> ssidSet = mDataSource.getSsids();
         if (ssidSet != null && !ssidSet.isEmpty()) {
@@ -83,7 +86,9 @@
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
index bed66d9..8db7c90 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -86,6 +86,9 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -108,6 +111,8 @@
     public static final String INIT_STOP_PROPERTY = "ctl.stop";
     @VisibleForTesting
     public static final String INIT_SERVICE_NAME = "wpa_supplicant";
+    @VisibleForTesting
+    public static final long WAIT_FOR_DEATH_TIMEOUT_MS = 50L;
     /**
      * Regex pattern for extracting the wps device type bytes.
      * Matches a strings like the following: "<categ>-<OUI>-<subcateg>";
@@ -262,11 +267,12 @@
         }
     }
 
-    private boolean linkToSupplicantDeath() {
+    private boolean linkToSupplicantDeath(
+            HwRemoteBinder.DeathRecipient deathRecipient, long cookie) {
         synchronized (mLock) {
             if (mISupplicant == null) return false;
             try {
-                if (!mISupplicant.linkToDeath(mSupplicantDeathRecipient, ++mDeathRecipientCookie)) {
+                if (!mISupplicant.linkToDeath(deathRecipient, cookie)) {
                     Log.wtf(TAG, "Error on linkToDeath on ISupplicant");
                     supplicantServiceDiedHandler(mDeathRecipientCookie);
                     return false;
@@ -294,7 +300,7 @@
                 Log.e(TAG, "Got null ISupplicant service. Stopping supplicant HIDL startup");
                 return false;
             }
-            if (!linkToSupplicantDeath()) {
+            if (!linkToSupplicantDeath(mSupplicantDeathRecipient, ++mDeathRecipientCookie)) {
                 return false;
             }
         }
@@ -645,10 +651,19 @@
     }
 
     /**
-     * Terminate the supplicant daemon.
+     * Terminate the supplicant daemon & wait for it's death.
      */
     public void terminate() {
         synchronized (mLock) {
+            // Register for a new death listener to block until supplicant is dead.
+            final long waitForDeathCookie = new Random().nextLong();
+            final CountDownLatch waitForDeathLatch = new CountDownLatch(1);
+            linkToSupplicantDeath((cookie) -> {
+                Log.d(TAG, "ISupplicant died: cookie=" + cookie);
+                if (cookie != waitForDeathCookie) return;
+                waitForDeathLatch.countDown();
+            }, waitForDeathCookie);
+
             if (isV1_1()) {
                 Log.i(TAG, "Terminating supplicant using HIDL");
                 terminate_V1_1();
@@ -656,6 +671,15 @@
                 Log.i(TAG, "Terminating supplicant using init");
                 mPropertyService.set(INIT_STOP_PROPERTY, INIT_SERVICE_NAME);
             }
+
+            // Now wait for death listener callback to confirm that it's dead.
+            try {
+                if (!waitForDeathLatch.await(WAIT_FOR_DEATH_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    Log.w(TAG, "Timed out waiting for confirmation of supplicant death");
+                }
+            } catch (InterruptedException e) {
+                Log.w(TAG, "Failed to wait for supplicant death");
+            }
         }
     }
 
@@ -3050,10 +3074,10 @@
      *  This is a v1.2+ HAL feature.
      *  On error, or if these features are not supported, 0 is returned.
      */
-    public int getAdvancedKeyMgmtCapabilities(@NonNull String ifaceName) {
+    public long getAdvancedKeyMgmtCapabilities(@NonNull String ifaceName) {
         final String methodStr = "getAdvancedKeyMgmtCapabilities";
 
-        int advancedCapabilities = 0;
+        long advancedCapabilities = 0;
         int keyMgmtCapabilities = getKeyMgmtCapabilities(ifaceName);
 
         if ((keyMgmtCapabilities & android.hardware.wifi.supplicant.V1_2.ISupplicantStaNetwork
diff --git a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
index 9255fc2..dd56b5f 100644
--- a/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
@@ -339,19 +339,11 @@
                 Log.e(TAG, config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID);
                 return false;
             }
-            // The logic below is skipping WPA2-Enterprise explicit setting of PMF to disabled
-            // in order to allow connection to networks with PMF required. Skipping means that
-            // wpa_supplicant will use the global setting (optional/capable).
-            // TODO(b/130755779): A permanent fix should convert requirePMF to a tri-state variablbe
-            boolean wpa2EnterpriseSkipPmf = !config.requirePMF
-                    && (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)
-                    || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+
             /** RequirePMF */
-            if (!wpa2EnterpriseSkipPmf) {
-                if (!setRequirePmf(config.requirePMF)) {
-                    Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePMF);
-                    return false;
-                }
+            if (!setRequirePmf(config.requirePMF)) {
+                Log.e(TAG, config.SSID + ": failed to set requirePMF: " + config.requirePMF);
+                return false;
             }
             /** Key Management Scheme */
             if (config.allowedKeyManagement.cardinality() != 0) {
diff --git a/service/java/com/android/server/wifi/WakeupConfigStoreData.java b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
index d191ee3..847d8fb 100644
--- a/service/java/com/android/server/wifi/WakeupConfigStoreData.java
+++ b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
+import android.annotation.Nullable;
 import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.server.wifi.WifiConfigStore.StoreData;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -94,7 +96,8 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         writeFeatureState(out);
 
@@ -141,7 +144,9 @@
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         if (!mHasBeenRead) {
             Log.d(TAG, "WifiWake user data has been read");
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index 2d0b4ed..fce5758 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -274,6 +274,8 @@
     private final WifiPermissionsUtil mWifiPermissionsUtil;
     private final WifiPermissionsWrapper mWifiPermissionsWrapper;
     private final WifiInjector mWifiInjector;
+    private final MacAddressUtil mMacAddressUtil;
+    private boolean mConnectedMacRandomzationSupported;
 
     /**
      * Local log used for debugging any WifiConfigManager issues.
@@ -316,6 +318,7 @@
     private final int mMaxNumActiveChannelsForPartialScans;
 
     private final FrameworkFacade mFrameworkFacade;
+    private final DeviceConfigFacade mDeviceConfigFacade;
 
     /**
      * Verbose logging flag. Toggled by developer options.
@@ -373,6 +376,7 @@
 
     private boolean mPnoFrequencyCullingEnabled = false;
     private boolean mPnoRecencySortingEnabled = false;
+    private Set<String> mRandomizationFlakySsidHotlist;
 
 
 
@@ -390,7 +394,8 @@
             NetworkListUserStoreData networkListUserStoreData,
             DeletedEphemeralSsidsStoreData deletedEphemeralSsidsStoreData,
             RandomizedMacStoreData randomizedMacStoreData,
-            FrameworkFacade frameworkFacade, Looper looper) {
+            FrameworkFacade frameworkFacade, Looper looper,
+            DeviceConfigFacade deviceConfigFacade) {
         mContext = context;
         mClock = clock;
         mUserManager = userManager;
@@ -440,12 +445,23 @@
                     }
                 });
         updatePnoRecencySortingSetting();
+        mConnectedMacRandomzationSupported = mContext.getResources()
+                .getBoolean(R.bool.config_wifi_connected_mac_randomization_supported);
+        mDeviceConfigFacade = deviceConfigFacade;
+        mDeviceConfigFacade.addOnPropertiesChangedListener(
+                command -> new Handler(looper).post(command),
+                properties -> {
+                    mRandomizationFlakySsidHotlist =
+                            mDeviceConfigFacade.getRandomizationFlakySsidHotlist();
+                });
+        mRandomizationFlakySsidHotlist = mDeviceConfigFacade.getRandomizationFlakySsidHotlist();
         try {
             mSystemUiUid = mContext.getPackageManager().getPackageUidAsUser(SYSUI_PACKAGE_NAME,
                     PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Unable to resolve SystemUI's UID.");
         }
+        mMacAddressUtil = mWifiInjector.getMacAddressUtil();
     }
 
     /**
@@ -497,11 +513,11 @@
                 mRandomizedMacAddressMapping.remove(config.getSsidAndSecurityTypeString());
             }
         }
-        MacAddress result = WifiConfigurationUtil.calculatePersistentMacForConfiguration(config,
-                WifiConfigurationUtil.obtainMacRandHashFunction(Process.WIFI_UID));
+        MacAddress result = mMacAddressUtil.calculatePersistentMacForConfiguration(
+                config, mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
         if (result == null) {
-            result = WifiConfigurationUtil.calculatePersistentMacForConfiguration(config,
-                    WifiConfigurationUtil.obtainMacRandHashFunction(Process.WIFI_UID));
+            result = mMacAddressUtil.calculatePersistentMacForConfiguration(
+                    config, mMacAddressUtil.obtainMacRandHashFunction(Process.WIFI_UID));
         }
         if (result == null) {
             Log.wtf(TAG, "Failed to generate MAC address from KeyStore even after retrying. "
@@ -607,6 +623,9 @@
                 && targetUid != configuration.creatorUid) {
             maskRandomizedMacAddressInWifiConfiguration(network);
         }
+        if (!mConnectedMacRandomzationSupported) {
+            network.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+        }
         return network;
     }
 
@@ -1506,10 +1525,29 @@
     }
 
     /**
+     * Check whether a network belong to a known list of networks that may not support randomized
+     * MAC.
+     * @param networkId
+     * @return true if the network is in the hotlist and MAC randomization is enabled.
+     */
+    public boolean isInFlakyRandomizationSsidHotlist(int networkId) {
+        WifiConfiguration config = getConfiguredNetwork(networkId);
+        return config != null
+                && config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT
+                && mRandomizationFlakySsidHotlist.contains(config.SSID);
+    }
+
+    /**
      * Helper method to mark a network enabled for network selection.
      */
     private void setNetworkSelectionEnabled(WifiConfiguration config) {
         NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        if (status.getNetworkSelectionStatus()
+                != NetworkSelectionStatus.NETWORK_SELECTION_ENABLED) {
+            localLog("setNetworkSelectionEnabled: configKey=" + config.configKey()
+                    + " old networkStatus=" + status.getNetworkStatusString()
+                    + " disableReason=" + status.getNetworkDisableReasonString());
+        }
         status.setNetworkSelectionStatus(
                 NetworkSelectionStatus.NETWORK_SELECTION_ENABLED);
         status.setDisableTime(
@@ -2706,7 +2744,7 @@
     }
 
     /**
-     * Retrieves a list of all the saved hidden networks for scans.
+     * Retrieves a list of all the saved hidden networks for scans
      *
      * Hidden network list sent to the firmware has limited size. If there are a lot of saved
      * networks, this list will be truncated and we might end up not sending the networks
@@ -2719,19 +2757,12 @@
     public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList() {
         List<WifiScanner.ScanSettings.HiddenNetwork> hiddenList = new ArrayList<>();
         List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
-        // Remove any permanently disabled networks or non hidden networks.
-        Iterator<WifiConfiguration> iter = networks.iterator();
-        while (iter.hasNext()) {
-            WifiConfiguration config = iter.next();
-            if (!config.hiddenSSID) {
-                iter.remove();
-            }
-        }
-        Collections.sort(networks, sScanListComparator);
+        // Remove any non hidden networks.
+        networks.removeIf(config -> !config.hiddenSSID);
+        networks.sort(sScanListComparator);
         // The most frequently connected network has the highest priority now.
         for (WifiConfiguration config : networks) {
-            hiddenList.add(
-                    new WifiScanner.ScanSettings.HiddenNetwork(config.SSID));
+            hiddenList.add(new WifiScanner.ScanSettings.HiddenNetwork(config.SSID));
         }
         return hiddenList;
     }
@@ -3119,7 +3150,8 @@
         if (mDeferredUserUnlockRead) {
             Log.i(TAG, "Handling user unlock before loading from store.");
             List<WifiConfigStore.StoreFile> userStoreFiles =
-                    WifiConfigStore.createUserFiles(mCurrentUserId);
+                    WifiConfigStore.createUserFiles(
+                            mCurrentUserId, mFrameworkFacade.isNiapModeOn(mContext));
             if (userStoreFiles == null) {
                 Log.wtf(TAG, "Failed to create user store files");
                 return false;
@@ -3129,7 +3161,7 @@
         }
         try {
             mWifiConfigStore.read();
-        } catch (IOException e) {
+        } catch (IOException | IllegalStateException e) {
             Log.wtf(TAG, "Reading from new store failed. All saved networks are lost!", e);
             return false;
         } catch (XmlPullParserException e) {
@@ -3158,18 +3190,19 @@
     private boolean loadFromUserStoreAfterUnlockOrSwitch(int userId) {
         try {
             List<WifiConfigStore.StoreFile> userStoreFiles =
-                    WifiConfigStore.createUserFiles(userId);
+                    WifiConfigStore.createUserFiles(
+                            userId, mFrameworkFacade.isNiapModeOn(mContext));
             if (userStoreFiles == null) {
                 Log.e(TAG, "Failed to create user store files");
                 return false;
             }
             mWifiConfigStore.switchUserStoresAndRead(userStoreFiles);
-        } catch (IOException e) {
+        } catch (IOException | IllegalStateException e) {
             Log.wtf(TAG, "Reading from new store failed. All saved private networks are lost!", e);
             return false;
         } catch (XmlPullParserException e) {
-            Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are" +
-                    "lost!", e);
+            Log.wtf(TAG, "XML deserialization of store failed. All saved private networks are "
+                    + "lost!", e);
             return false;
         }
         loadInternalDataFromUserStore(mNetworkListUserStoreData.getConfigurations(),
@@ -3239,7 +3272,7 @@
 
         try {
             mWifiConfigStore.write(forceWrite);
-        } catch (IOException e) {
+        } catch (IOException | IllegalStateException e) {
             Log.wtf(TAG, "Writing to store failed. Saved networks maybe lost!", e);
             return false;
         } catch (XmlPullParserException e) {
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index a618eb5..42d9f82 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -35,7 +35,8 @@
 import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
-import com.android.server.wifi.util.DataIntegrityChecker;
+import com.android.server.wifi.util.EncryptedData;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -53,7 +54,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
-import java.security.DigestException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -61,6 +61,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * This class provides a mechanism to save data to persistent store files {@link StoreFile}.
@@ -99,15 +100,37 @@
 
     private static final String XML_TAG_DOCUMENT_HEADER = "WifiConfigStoreData";
     private static final String XML_TAG_VERSION = "Version";
+    private static final String XML_TAG_HEADER_INTEGRITY = "Integrity";
     /**
      * Current config store data version. This will be incremented for any additions.
      */
-    private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 1;
+    private static final int CURRENT_CONFIG_STORE_DATA_VERSION = 3;
     /** This list of older versions will be used to restore data from older config store. */
     /**
      * First version of the config store data format.
      */
-    private static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1;
+    public static final int INITIAL_CONFIG_STORE_DATA_VERSION = 1;
+    /**
+     * Second version of the config store data format, introduced:
+     *  - Integrity info.
+     */
+    public static final int INTEGRITY_CONFIG_STORE_DATA_VERSION = 2;
+    /**
+     * Third version of the config store data format,
+     * introduced:
+     *  - Encryption of credentials
+     * removed:
+     *  - Integrity info.
+     */
+    public static final int ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION = 3;
+
+    @IntDef(suffix = { "_VERSION" }, value = {
+            INITIAL_CONFIG_STORE_DATA_VERSION,
+            INTEGRITY_CONFIG_STORE_DATA_VERSION,
+            ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Version { }
 
     /**
      * Alarm tag to use for starting alarms for buffering file writes.
@@ -148,7 +171,6 @@
                 put(STORE_FILE_USER_GENERAL, STORE_FILE_NAME_USER_GENERAL);
                 put(STORE_FILE_USER_NETWORK_SUGGESTIONS, STORE_FILE_NAME_USER_NETWORK_SUGGESTIONS);
             }};
-
     /**
      * Handler instance to post alarm timeouts to
      */
@@ -266,9 +288,11 @@
      * @param storeBaseDir Base directory under which the store file is to be stored. The store file
      *                     will be at <storeBaseDir>/wifi/WifiConfigStore.xml.
      * @param fileId Identifier for the file. See {@link StoreFileId}.
+     * @param shouldEncryptCredentials Whether to encrypt credentials or not.
      * @return new instance of the store file or null if the directory cannot be created.
      */
-    private static @Nullable StoreFile createFile(File storeBaseDir, @StoreFileId int fileId) {
+    private static @Nullable StoreFile createFile(File storeBaseDir, @StoreFileId int fileId,
+            boolean shouldEncryptCredentials) {
         File storeDir = new File(storeBaseDir, STORE_DIRECTORY_NAME);
         if (!storeDir.exists()) {
             if (!storeDir.mkdir()) {
@@ -276,16 +300,23 @@
                 return null;
             }
         }
-        return new StoreFile(new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId)), fileId);
+        File file = new File(storeDir, STORE_ID_TO_FILE_NAME.get(fileId));
+        WifiConfigStoreEncryptionUtil encryptionUtil = null;
+        if (shouldEncryptCredentials) {
+            encryptionUtil = new WifiConfigStoreEncryptionUtil(file.getName());
+        }
+        return new StoreFile(file, fileId, encryptionUtil);
     }
 
     /**
      * Create a new instance of the shared store file.
      *
+     * @param shouldEncryptCredentials Whether to encrypt credentials or not.
      * @return new instance of the store file or null if the directory cannot be created.
      */
-    public static @Nullable StoreFile createSharedFile() {
-        return createFile(Environment.getDataMiscDirectory(), STORE_FILE_SHARED_GENERAL);
+    public static @Nullable StoreFile createSharedFile(boolean shouldEncryptCredentials) {
+        return createFile(Environment.getDataMiscDirectory(), STORE_FILE_SHARED_GENERAL,
+                shouldEncryptCredentials);
     }
 
     /**
@@ -293,14 +324,18 @@
      * The user store file is inside the user's encrypted data directory.
      *
      * @param userId userId corresponding to the currently logged-in user.
+     * @param shouldEncryptCredentials Whether to encrypt credentials or not.
      * @return List of new instances of the store files created or null if the directory cannot be
      * created.
      */
-    public static @Nullable List<StoreFile> createUserFiles(int userId) {
+    public static @Nullable List<StoreFile> createUserFiles(int userId,
+            boolean shouldEncryptCredentials) {
         List<StoreFile> storeFiles = new ArrayList<>();
         for (int fileId : Arrays.asList(
                 STORE_FILE_USER_GENERAL, STORE_FILE_USER_NETWORK_SUGGESTIONS)) {
-            StoreFile storeFile = createFile(Environment.getDataMiscCeDirectory(userId), fileId);
+            StoreFile storeFile =
+                    createFile(Environment.getDataMiscCeDirectory(userId), fileId,
+                            shouldEncryptCredentials);
             if (storeFile == null) {
                 return null;
             }
@@ -395,6 +430,9 @@
      * Serialize all the data from all the {@link StoreData} clients registered for the provided
      * {@link StoreFile}.
      *
+     * This method also computes the integrity of the data being written and serializes the computed
+     * {@link EncryptedData} to the output.
+     *
      * @param storeFile StoreFile that we want to write to.
      * @return byte[] of serialized bytes
      * @throws XmlPullParserException
@@ -408,16 +446,17 @@
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
 
+        // First XML header.
         XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
+        // Next version.
         XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_CONFIG_STORE_DATA_VERSION);
         for (StoreData storeData : storeDataList) {
             String tag = storeData.getName();
             XmlUtil.writeNextSectionStart(out, tag);
-            storeData.serializeData(out);
+            storeData.serializeData(out, storeFile.getEncryptionUtil());
             XmlUtil.writeNextSectionEnd(out, tag);
         }
         XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
-
         return outputStream.toByteArray();
     }
 
@@ -539,16 +578,21 @@
     }
 
     // Inform all the provided store data clients that there is nothing in the store for them.
-    private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet)
+    private void indicateNoDataForStoreDatas(Collection<StoreData> storeDataSet,
+            @Version int version, @NonNull WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         for (StoreData storeData : storeDataSet) {
-            storeData.deserializeData(null, 0);
+            storeData.deserializeData(null, 0, version, encryptionUtil);
         }
     }
 
     /**
      * Deserialize data from a {@link StoreFile} for all {@link StoreData} instances registered.
      *
+     * This method also computes the integrity of the incoming |dataBytes| and compare with
+     * {@link EncryptedData} parsed from |dataBytes|. If the integrity check fails, the data
+     * is discarded.
+     *
      * @param dataBytes The data to parse
      * @param storeFile StoreFile that we read from. Will be used to retrieve the list of clients
      *                  who have data to deserialize from this file.
@@ -560,7 +604,8 @@
             throws XmlPullParserException, IOException {
         List<StoreData> storeDataList = retrieveStoreDataListForStoreFile(storeFile);
         if (dataBytes == null) {
-            indicateNoDataForStoreDatas(storeDataList);
+            indicateNoDataForStoreDatas(storeDataList, -1 /* unknown */,
+                    storeFile.getEncryptionUtil());
             return;
         }
         final XmlPullParser in = Xml.newPullParser();
@@ -569,7 +614,13 @@
 
         // Start parsing the XML stream.
         int rootTagDepth = in.getDepth() + 1;
-        parseDocumentStartAndVersionFromXml(in);
+        XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
+
+        @Version int version = parseVersionFromXml(in);
+        // Version 2 contains the now unused integrity data, parse & then discard the information.
+        if (version == INTEGRITY_CONFIG_STORE_DATA_VERSION) {
+            parseAndDiscardIntegrityDataFromXml(in, rootTagDepth);
+        }
 
         String[] headerName = new String[1];
         Set<StoreData> storeDatasInvoked = new HashSet<>();
@@ -584,26 +635,26 @@
                 throw new XmlPullParserException("Unknown store data: " + headerName[0]
                         + ". List of store data: " + storeDataList);
             }
-            storeData.deserializeData(in, rootTagDepth + 1);
+            storeData.deserializeData(in, rootTagDepth + 1, version,
+                    storeFile.getEncryptionUtil());
             storeDatasInvoked.add(storeData);
         }
         // Inform all the other registered store data clients that there is nothing in the store
         // for them.
         Set<StoreData> storeDatasNotInvoked = new HashSet<>(storeDataList);
         storeDatasNotInvoked.removeAll(storeDatasInvoked);
-        indicateNoDataForStoreDatas(storeDatasNotInvoked);
+        indicateNoDataForStoreDatas(storeDatasNotInvoked, version, storeFile.getEncryptionUtil());
     }
 
     /**
-     * Parse the document start and version from the XML stream.
+     * Parse the version from the XML stream.
      * This is used for both the shared and user config store data.
      *
      * @param in XmlPullParser instance pointing to the XML stream.
      * @return version number retrieved from the Xml stream.
      */
-    private static int parseDocumentStartAndVersionFromXml(XmlPullParser in)
+    private static @Version int parseVersionFromXml(XmlPullParser in)
             throws XmlPullParserException, IOException {
-        XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
         int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
         if (version < INITIAL_CONFIG_STORE_DATA_VERSION
                 || version > CURRENT_CONFIG_STORE_DATA_VERSION) {
@@ -613,10 +664,29 @@
     }
 
     /**
+     * Parse the integrity data structure from the XML stream and discard it.
+     *
+     * @param in XmlPullParser instance pointing to the XML stream.
+     * @param outerTagDepth Outer tag depth.
+     */
+    private static void parseAndDiscardIntegrityDataFromXml(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        XmlUtil.gotoNextSectionWithName(in, XML_TAG_HEADER_INTEGRITY, outerTagDepth);
+        XmlUtil.EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1);
+    }
+
+    /**
      * Dump the local log buffer and other internal state of WifiConfigManager.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Dump of WifiConfigStore");
+        pw.println("WifiConfigStore - Store File Begin ----");
+        Stream.of(Arrays.asList(mSharedStore), mUserStores)
+                .flatMap(List::stream)
+                .forEach((storeFile) -> {
+                    pw.print("Name: " + storeFile.mFileName);
+                    pw.println(", Credentials encrypted: " + storeFile.getEncryptionUtil() != null);
+                });
         pw.println("WifiConfigStore - Store Data Begin ----");
         for (StoreData storeData : mStoreDataList) {
             pw.print("StoreData =>");
@@ -653,21 +723,22 @@
         /**
          * Store the file name for setting the file permissions/logging purposes.
          */
-        private String mFileName;
-        /**
-         * The integrity file storing integrity checking data for the store file.
-         */
-        private DataIntegrityChecker mDataIntegrityChecker;
+        private final String mFileName;
         /**
          * {@link StoreFileId} Type of store file.
          */
-        private @StoreFileId int mFileId;
+        private final @StoreFileId int mFileId;
+        /**
+         * Integrity checking for the store file.
+         */
+        private final WifiConfigStoreEncryptionUtil mEncryptionUtil;
 
-        public StoreFile(File file, @StoreFileId int fileId) {
+        public StoreFile(File file, @StoreFileId int fileId,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) {
             mAtomicFile = new AtomicFile(file);
-            mFileName = mAtomicFile.getBaseFile().getAbsolutePath();
-            mDataIntegrityChecker = new DataIntegrityChecker(mFileName);
+            mFileName = file.getAbsolutePath();
             mFileId = fileId;
+            mEncryptionUtil = encryptionUtil;
         }
 
         /**
@@ -680,6 +751,13 @@
         }
 
         /**
+         * @return Returns the encryption util used for this store file.
+         */
+        public @Nullable WifiConfigStoreEncryptionUtil getEncryptionUtil() {
+            return mEncryptionUtil;
+        }
+
+        /**
          * Read the entire raw data from the store file and return in a byte array.
          *
          * @return raw data read from the file or null if the file is not found or the data has
@@ -691,20 +769,8 @@
             byte[] bytes = null;
             try {
                 bytes = mAtomicFile.readFully();
-                // Check that the file has not been altered since last writeBufferedRawData()
-                if (!mDataIntegrityChecker.isOk(bytes)) {
-                    Log.wtf(TAG, "Data integrity problem with file: " + mFileName);
-                    return null;
-                }
             } catch (FileNotFoundException e) {
                 return null;
-            } catch (DigestException e) {
-                // When integrity checking is introduced. The existing data will have no related
-                // integrity file for validation. Thus, we will assume the existing data is correct
-                // and immediately create the integrity file.
-                Log.i(TAG, "isOK() had no integrity data to check; thus vacuously "
-                        + "true. Running update now.");
-                mDataIntegrityChecker.update(bytes);
             }
             return bytes;
         }
@@ -741,8 +807,6 @@
                 }
                 throw e;
             }
-            // There was a legitimate change and update the integrity checker.
-            mDataIntegrityChecker.update(mWriteData);
             // Reset the pending write data after write.
             mWriteData = null;
         }
@@ -765,8 +829,10 @@
          * Serialize a XML data block to the output stream.
          *
          * @param out The output stream to serialize the data to
+         * @param encryptionUtil Utility to help encrypt any credential data.
          */
-        void serializeData(XmlSerializer out)
+        void serializeData(XmlSerializer out,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException;
 
         /**
@@ -775,10 +841,14 @@
          * @param in The input stream to read the data from. This could be null if there is
          *           nothing in the store.
          * @param outerTagDepth The depth of the outer tag in the XML document
+         * @param version Version of config store file.
+         * @param encryptionUtil Utility to help decrypt any credential data.
+         *
          * Note: This will be invoked every time a store file is read, even if there is nothing
          *                      in the store for them.
          */
-        void deserializeData(@Nullable XmlPullParser in, int outerTagDepth)
+        void deserializeData(@Nullable XmlPullParser in, int outerTagDepth, @Version int version,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException;
 
         /**
diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
index 69a655b..cb83594 100644
--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
@@ -710,12 +710,14 @@
         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
             // PMF mandatory for OWE networks
             if (!config.requirePMF) {
+                Log.e(TAG, "PMF must be enabled for OWE networks");
                 return false;
             }
         }
         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
             // PMF mandatory for WPA3-Personal networks
             if (!config.requirePMF) {
+                Log.e(TAG, "PMF must be enabled for SAE networks");
                 return false;
             }
             if (!validatePassword(config.preSharedKey, isAdd, true)) {
@@ -725,6 +727,7 @@
         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
             // PMF mandatory for WPA3-Enterprise networks
             if (!config.requirePMF) {
+                Log.e(TAG, "PMF must be enabled for Suite-B 192-bit networks");
                 return false;
             }
         }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 7411422..2e4b5c8 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -916,11 +916,14 @@
         settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
                             | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
         settings.numBssidsPerScan = 0;
-
+        // retrieve the list of hidden network SSIDs from saved network to scan for
         List<ScanSettings.HiddenNetwork> hiddenNetworkList =
-                mConfigManager.retrieveHiddenNetworkList();
+                new ArrayList<>(mConfigManager.retrieveHiddenNetworkList());
+        // retrieve the list of hidden network SSIDs from Network suggestion to scan for
+        hiddenNetworkList.addAll(
+                mWifiInjector.getWifiNetworkSuggestionsManager().retrieveHiddenNetworkList());
         settings.hiddenNetworks =
-                hiddenNetworkList.toArray(new ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
+                hiddenNetworkList.toArray(new ScanSettings.HiddenNetwork[0]);
 
         SingleScanListener singleScanListener =
                 new SingleScanListener(isFullBandScan);
diff --git a/service/java/com/android/server/wifi/WifiCountryCode.java b/service/java/com/android/server/wifi/WifiCountryCode.java
index a669508..f9d147c 100644
--- a/service/java/com/android/server/wifi/WifiCountryCode.java
+++ b/service/java/com/android/server/wifi/WifiCountryCode.java
@@ -48,6 +48,7 @@
     private String mTelephonyCountryTimestamp = null;
     private String mDriverCountryTimestamp = null;
     private String mReadyTimestamp = null;
+    private boolean mForceCountryCode = false;
 
     public WifiCountryCode(
             WifiNative wifiNative,
@@ -83,18 +84,6 @@
     }
 
     /**
-     * This is called when airplane mode is enabled.
-     * In this case we should invalidate all other country code except the
-     * phone default one.
-     */
-    public synchronized void airplaneModeEnabled() {
-        Log.d(TAG, "Airplane Mode Enabled");
-        // Airplane mode is enabled, we need to reset the country code to phone default.
-        // Country code will be set upon when wpa_supplicant starts next time.
-        mTelephonyCountryCode = null;
-    }
-
-    /**
      * Change the state to indicates if wpa_supplicant is ready to handle country code changing
      * request or not.
      * We call native code to request country code changes only when wpa_supplicant is
@@ -111,6 +100,36 @@
     }
 
     /**
+     * Enable force-country-code mode
+     * @param countryCode The forced two-letter country code
+     */
+    synchronized void enableForceCountryCode(String countryCode) {
+        if (TextUtils.isEmpty(countryCode)) {
+            Log.d(TAG, "Fail to force country code because the received country code is empty");
+            return;
+        }
+        mForceCountryCode = true;
+        mTelephonyCountryCode = countryCode.toUpperCase(Locale.US);
+        // If wpa_supplicant is ready we set the country code now, otherwise it will be
+        // set once wpa_supplicant is ready.
+        if (mReady) {
+            updateCountryCode();
+        } else {
+            Log.d(TAG, "skip update supplicant not ready yet");
+        }
+    }
+
+    /**
+     * Disable force-country-code mode
+     */
+    synchronized void disableForceCountryCode() {
+        mForceCountryCode = false;
+        // Set mTelephonyCountryCode to null so that default country code is used until
+        // next call of setCountryCode().
+        mTelephonyCountryCode = null;
+    }
+
+    /**
      * Handle country code change request.
      * @param countryCode The country code intended to set.
      * This is supposed to be from Telephony service.
@@ -118,6 +137,10 @@
      * @return Returns true if the country code passed in is acceptable.
      */
     public synchronized boolean setCountryCode(String countryCode) {
+        if (mForceCountryCode) {
+            Log.d(TAG, "Country code can't be set because it is the force-country-code mode");
+            return false;
+        }
         Log.d(TAG, "Receive set country code request: " + countryCode);
         mTelephonyCountryTimestamp = FORMATTER.format(new Date(System.currentTimeMillis()));
 
diff --git a/service/java/com/android/server/wifi/WifiDataStall.java b/service/java/com/android/server/wifi/WifiDataStall.java
index 1054c2e..6eb3b41 100644
--- a/service/java/com/android/server/wifi/WifiDataStall.java
+++ b/service/java/com/android/server/wifi/WifiDataStall.java
@@ -17,6 +17,9 @@
 package com.android.server.wifi;
 
 import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.os.Handler;
+import android.os.Looper;
 import android.provider.Settings;
 
 import com.android.server.wifi.nano.WifiMetricsProto.WifiIsUnusableEvent;
@@ -33,19 +36,50 @@
     public static final int MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT = 50;
     // Maximum time gap between two WifiLinkLayerStats to trigger a data stall
     public static final long MAX_MS_DELTA_FOR_DATA_STALL = 60 * 1000; // 1 minute
+    // Maximum time that a data stall start time stays valid.
+    public static final long VALIDITY_PERIOD_OF_DATA_STALL_START_MS = 30 * 1000; // 0.5 minutes
+    // Default Tx packet error rate when there is no Tx attempt
+    public static final int DEFAULT_TX_PACKET_ERROR_RATE = 20;
+    // Default CCA level when CCA stats are not available
+    public static final int DEFAULT_CCA_LEVEL = 0;
 
     private final Context mContext;
+    private final DeviceConfigFacade mDeviceConfigFacade;
     private final FrameworkFacade mFacade;
     private final WifiMetrics mWifiMetrics;
 
+    private Handler mHandler;
     private int mMinTxBad;
     private int mMinTxSuccessWithoutRx;
+    private int mDataStallDurationMs;
+    private int mDataStallTxTputThrMbps;
+    private int mDataStallRxTputThrMbps;
+    private int mDataStallTxPerThr;
+    private int mDataStallCcaLevelThr;
+    private int mLastFrequency = -1;
+    private String mLastBssid;
+    private long mLastTotalRadioOnFreqTimeMs = -1;
+    private long mLastTotalCcaBusyFreqTimeMs = -1;
+    private long mDataStallStartTimeMs = -1;
+    private Clock mClock;
+    private boolean mDataStallTx = false;
+    private boolean mDataStallRx = false;
 
-    public WifiDataStall(Context context, FrameworkFacade facade, WifiMetrics wifiMetrics) {
+    public WifiDataStall(Context context, FrameworkFacade facade, WifiMetrics wifiMetrics,
+            DeviceConfigFacade deviceConfigFacade, Looper clientModeImplLooper, Clock clock) {
         mContext = context;
+        mDeviceConfigFacade = deviceConfigFacade;
         mFacade = facade;
+        mHandler = new Handler(clientModeImplLooper);
         mWifiMetrics = wifiMetrics;
+        mClock = clock;
         loadSettings();
+
+        mDeviceConfigFacade.addOnPropertiesChangedListener(
+                command -> mHandler.post(command),
+                properties -> {
+                    updateUsabilityDataCollectionFlags();
+                });
     }
 
     /**
@@ -59,15 +93,18 @@
                 MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT);
         mWifiMetrics.setWifiDataStallMinTxBad(mMinTxBad);
         mWifiMetrics.setWifiDataStallMinRxWithoutTx(mMinTxSuccessWithoutRx);
+        updateUsabilityDataCollectionFlags();
     }
 
     /**
      * Checks for data stall by looking at tx/rx packet counts
      * @param oldStats second most recent WifiLinkLayerStats
      * @param newStats most recent WifiLinkLayerStats
+     * @param wifiInfo WifiInfo for current connection
      * @return trigger type of WifiIsUnusableEvent
      */
-    public int checkForDataStall(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats) {
+    public int checkForDataStall(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats,
+            WifiInfo wifiInfo) {
         if (oldStats == null || newStats == null) {
             mWifiMetrics.resetWifiIsUnusableLinkLayerStats();
             return WifiIsUnusableEvent.TYPE_UNKNOWN;
@@ -103,25 +140,130 @@
 
         mWifiMetrics.updateWifiIsUnusableLinkLayerStats(txSuccessDelta, txRetriesDelta,
                 txBadDelta, rxSuccessDelta, timeMsDelta);
+
         if (timeMsDelta < MAX_MS_DELTA_FOR_DATA_STALL) {
-            // There is a data stall if there are too many tx failures
-            // or if we are not receiving any packets despite many tx successes
-            boolean dataStallBadTx = (txBadDelta >= mMinTxBad);
-            boolean dataStallTxSuccessWithoutRx =
-                    (rxSuccessDelta == 0 && txSuccessDelta >= mMinTxSuccessWithoutRx);
-            if (dataStallBadTx && dataStallTxSuccessWithoutRx) {
-                mWifiMetrics.logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH);
-                return WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH;
-            } else if (dataStallBadTx) {
-                mWifiMetrics.logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
-                return WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX;
-            } else if (dataStallTxSuccessWithoutRx) {
-                mWifiMetrics.logWifiIsUnusableEvent(
-                        WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX);
-                return WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX;
+            int txLinkSpeed = wifiInfo.getLinkSpeed();
+            int rxLinkSpeed = wifiInfo.getRxLinkSpeedMbps();
+            boolean isSameBssidAndFreq = mLastBssid == null || mLastFrequency == -1
+                    || (mLastBssid.equals(wifiInfo.getBSSID())
+                    && mLastFrequency == wifiInfo.getFrequency());
+            mLastFrequency = wifiInfo.getFrequency();
+            mLastBssid = wifiInfo.getBSSID();
+
+            int ccaLevel = updateCcaLevel(newStats, wifiInfo, isSameBssidAndFreq);
+            int txPer = updateTxPer(txSuccessDelta, txRetriesDelta, isSameBssidAndFreq);
+
+            boolean isTxTputLow = false;
+            boolean isRxTputLow = false;
+            if (txLinkSpeed > 0) {
+                int txTput = txLinkSpeed * (100 - txPer) * (100 - ccaLevel);
+                isTxTputLow = txTput < mDataStallTxTputThrMbps * 100 * 100;
+            }
+            if (rxLinkSpeed > 0) {
+                int rxTput = rxLinkSpeed * (100 - ccaLevel);
+                isRxTputLow = rxTput < mDataStallRxTputThrMbps * 100;
+            }
+
+            boolean dataStallTx = isTxTputLow || ccaLevel >= mDataStallCcaLevelThr
+                    || txPer >= mDataStallTxPerThr;
+            boolean dataStallRx = isRxTputLow || ccaLevel >= mDataStallCcaLevelThr;
+
+            // Data stall event is triggered if there are consecutive Tx and/or Rx data stalls
+            // Reset mDataStallStartTimeMs to -1 if currently there is no Tx or Rx data stall
+            if (dataStallTx || dataStallRx) {
+                mDataStallTx = mDataStallTx || dataStallTx;
+                mDataStallRx = mDataStallRx || dataStallRx;
+                if (mDataStallStartTimeMs == -1) {
+                    mDataStallStartTimeMs = mClock.getElapsedSinceBootMillis();
+                    if (mDataStallDurationMs == 0) {
+                        mDataStallStartTimeMs = -1;
+                        int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx);
+                        mDataStallRx = false;
+                        mDataStallTx = false;
+                        return result;
+                    }
+                } else {
+                    long elapsedTime =  mClock.getElapsedSinceBootMillis() - mDataStallStartTimeMs;
+                    if (elapsedTime >= mDataStallDurationMs) {
+                        mDataStallStartTimeMs = -1;
+                        if (elapsedTime <= VALIDITY_PERIOD_OF_DATA_STALL_START_MS) {
+                            int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx);
+                            mDataStallRx = false;
+                            mDataStallTx = false;
+                            return result;
+                        } else {
+                            mDataStallTx = false;
+                            mDataStallRx = false;
+                        }
+                    } else {
+                        // No need to do anything.
+                    }
+                }
+            } else {
+                mDataStallStartTimeMs = -1;
+                mDataStallTx = false;
+                mDataStallRx = false;
             }
         }
 
         return WifiIsUnusableEvent.TYPE_UNKNOWN;
     }
+
+    private int updateCcaLevel(WifiLinkLayerStats newStats, WifiInfo wifiInfo,
+            boolean isSameBssidAndFreq) {
+        WifiLinkLayerStats.ChannelStats statsMap = newStats.channelStatsMap.get(mLastFrequency);
+        if (statsMap == null || !isSameBssidAndFreq) {
+            return DEFAULT_CCA_LEVEL;
+        }
+        int radioOnTimeDelta = (int) (statsMap.radioOnTimeMs - mLastTotalRadioOnFreqTimeMs);
+        int ccaBusyTimeDelta = (int) (statsMap.ccaBusyTimeMs - mLastTotalCcaBusyFreqTimeMs);
+        mLastTotalRadioOnFreqTimeMs = statsMap.radioOnTimeMs;
+        mLastTotalCcaBusyFreqTimeMs = statsMap.ccaBusyTimeMs;
+
+        boolean isCcaValid = (radioOnTimeDelta > 0) && (ccaBusyTimeDelta >= 0)
+                && (ccaBusyTimeDelta <= radioOnTimeDelta);
+        // Update CCA level only if CCA stats are valid.
+        if (!isCcaValid) {
+            return DEFAULT_CCA_LEVEL;
+        }
+        return (int) (ccaBusyTimeDelta * 100 / radioOnTimeDelta);
+    }
+
+    private int updateTxPer(long txSuccessDelta, long txRetriesDelta, boolean isSameBssidAndFreq) {
+        if (!isSameBssidAndFreq) {
+            return DEFAULT_TX_PACKET_ERROR_RATE;
+        }
+        long txAttempts = txSuccessDelta + txRetriesDelta;
+        if (txAttempts <= 0) {
+            return DEFAULT_TX_PACKET_ERROR_RATE;
+        }
+        return (int) (txRetriesDelta * 100 / txAttempts);
+    }
+
+    private int calculateUsabilityEventType(boolean dataStallTx, boolean dataStallRx) {
+        int result = WifiIsUnusableEvent.TYPE_UNKNOWN;
+        if (dataStallTx && dataStallRx) {
+            result = WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH;
+        } else if (dataStallTx) {
+            result = WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX;
+        } else if (dataStallRx) {
+            result = WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX;
+        }
+        mWifiMetrics.logWifiIsUnusableEvent(result);
+        return result;
+    }
+
+    private void updateUsabilityDataCollectionFlags() {
+        mDataStallDurationMs = mDeviceConfigFacade.getDataStallDurationMs();
+        mDataStallTxTputThrMbps = mDeviceConfigFacade.getDataStallTxTputThrMbps();
+        mDataStallRxTputThrMbps = mDeviceConfigFacade.getDataStallRxTputThrMbps();
+        mDataStallTxPerThr = mDeviceConfigFacade.getDataStallTxPerThr();
+        mDataStallCcaLevelThr = mDeviceConfigFacade.getDataStallCcaLevelThr();
+
+        mWifiMetrics.setDataStallDurationMs(mDataStallDurationMs);
+        mWifiMetrics.setDataStallTxTputThrMbps(mDataStallTxTputThrMbps);
+        mWifiMetrics.setDataStallRxTputThrMbps(mDataStallRxTputThrMbps);
+        mWifiMetrics.setDataStallTxPerThr(mDataStallTxPerThr);
+        mWifiMetrics.setDataStallCcaLevelThr(mDataStallCcaLevelThr);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiDiagnostics.java b/service/java/com/android/server/wifi/WifiDiagnostics.java
index 52a787a..da1e5b3 100644
--- a/service/java/com/android/server/wifi/WifiDiagnostics.java
+++ b/service/java/com/android/server/wifi/WifiDiagnostics.java
@@ -18,7 +18,9 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.util.ArraySet;
 import android.util.Base64;
+import android.util.Log;
 import android.util.SparseLongArray;
 
 import com.android.internal.R;
@@ -40,6 +42,7 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.zip.Deflater;
 
@@ -124,6 +127,9 @@
     private WifiInjector mWifiInjector;
     private Clock mClock;
 
+    /** Interfaces started logging */
+    private final Set<String> mActiveInterfaces = new ArraySet<>();
+
     public WifiDiagnostics(Context context, WifiInjector wifiInjector,
                            WifiNative wifiNative, BuildProperties buildProperties,
                            LastMileLogger lastMileLogger, Clock clock) {
@@ -148,40 +154,35 @@
         mClock = clock;
     }
 
+    /**
+     * Start wifi HAL dependent logging features.
+     * This method should be called only after the interface has
+     * been set up.
+     *
+     * @param ifaceName the interface requesting to start logging.
+     */
     @Override
-    public synchronized void startLogging(boolean verboseEnabled) {
-        mFirmwareVersion = mWifiNative.getFirmwareVersion();
-        mDriverVersion = mWifiNative.getDriverVersion();
-        mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet();
+    public synchronized void startLogging(@NonNull String ifaceName) {
+        if (mActiveInterfaces.contains(ifaceName)) {
+            Log.w(TAG, "Interface: " + ifaceName + " had already started logging");
+            return;
+        }
+        if (mActiveInterfaces.isEmpty()) {
+            mFirmwareVersion = mWifiNative.getFirmwareVersion();
+            mDriverVersion = mWifiNative.getDriverVersion();
+            mSupportedFeatureSet = mWifiNative.getSupportedLoggerFeatureSet();
 
-        if (!mIsLoggingEventHandlerRegistered) {
-            mIsLoggingEventHandlerRegistered = mWifiNative.setLoggingEventHandler(mHandler);
+            if (!mIsLoggingEventHandlerRegistered) {
+                mIsLoggingEventHandlerRegistered = mWifiNative.setLoggingEventHandler(mHandler);
+            }
+
+            startLoggingRingBuffers();
         }
 
-        if (verboseEnabled) {
-            mLogLevel = VERBOSE_LOG_WITH_WAKEUP;
-            mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_LARGE;
-        } else {
-            mLogLevel = VERBOSE_NORMAL_LOG;
-            mMaxRingBufferSizeBytes = enableVerboseLoggingForDogfood()
-                    ? RING_BUFFER_BYTE_LIMIT_LARGE : RING_BUFFER_BYTE_LIMIT_SMALL;
-            clearVerboseLogs();
-        }
+        mActiveInterfaces.add(ifaceName);
 
-        if (mRingBuffers == null) {
-            fetchRingBuffers();
-        }
-
-        if (mRingBuffers != null) {
-            /* log level may have changed, so restart logging with new levels */
-            stopLoggingAllBuffers();
-            resizeRingBuffers();
-            startLoggingAllExceptPerPacketBuffers();
-        }
-
-        if (!mWifiNative.startPktFateMonitoring(mWifiNative.getClientInterfaceName())) {
-            mLog.wC("Failed to start packet fate monitoring");
-        }
+        Log.d(TAG, "startLogging() iface list is " + mActiveInterfaces
+                + " after adding " + ifaceName);
     }
 
     @Override
@@ -202,8 +203,28 @@
         }
     }
 
+    /**
+     * Stop wifi HAL dependent logging features.
+     * This method should be called before the interface has been
+     * torn down.
+     *
+     * @param ifaceName the interface requesting to stop logging.
+     */
     @Override
-    public synchronized void stopLogging() {
+    public synchronized void stopLogging(@NonNull String ifaceName) {
+        if (!mActiveInterfaces.contains(ifaceName)) {
+            Log.w(TAG, "ifaceName: " + ifaceName + " is not in the start log user list");
+            return;
+        }
+
+        mActiveInterfaces.remove(ifaceName);
+
+        Log.d(TAG, "stopLogging() iface list is " + mActiveInterfaces
+                + " after removing " + ifaceName);
+
+        if (!mActiveInterfaces.isEmpty()) {
+            return;
+        }
         if (mIsLoggingEventHandlerRegistered) {
             if (!mWifiNative.resetLogHandler()) {
                 mLog.wC("Fail to reset log handler");
@@ -217,7 +238,6 @@
         if (mLogLevel != VERBOSE_NO_LOG) {
             stopLoggingAllBuffers();
             mRingBuffers = null;
-            mLogLevel = VERBOSE_NO_LOG;
         }
     }
 
@@ -442,12 +462,33 @@
         mWifiMetrics.logFirmwareAlert(errorCode);
     }
 
+    /**
+     * Enables or disables verbose logging
+     *
+     * @param verbose - with the obvious interpretation
+     */
+    @Override
+    public synchronized void enableVerboseLogging(boolean verboseEnabled) {
+        if (verboseEnabled) {
+            mLogLevel = VERBOSE_LOG_WITH_WAKEUP;
+            mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_LARGE;
+        } else {
+            mLogLevel = VERBOSE_NORMAL_LOG;
+            mMaxRingBufferSizeBytes = enableVerboseLoggingForDogfood()
+                    ? RING_BUFFER_BYTE_LIMIT_LARGE : RING_BUFFER_BYTE_LIMIT_SMALL;
+        }
+
+        if (!mActiveInterfaces.isEmpty()) {
+            mLog.wC("verbosity changed: restart logging");
+            startLoggingRingBuffers();
+        }
+    }
+
     private boolean isVerboseLoggingEnabled() {
         return mLogLevel > VERBOSE_NORMAL_LOG;
     }
 
     private void clearVerboseLogs() {
-        mPacketFatesForLastFailure = null;
 
         for (int i = 0; i < mLastAlerts.size(); i++) {
             mLastAlerts.get(i).clearVerboseLogs();
@@ -486,6 +527,21 @@
         }
     }
 
+    private void startLoggingRingBuffers() {
+        if (!isVerboseLoggingEnabled()) {
+            clearVerboseLogs();
+        }
+        if (mRingBuffers == null) {
+            fetchRingBuffers();
+        }
+        if (mRingBuffers != null) {
+            // Log level may have changed, so restart logging with new levels.
+            stopLoggingAllBuffers();
+            resizeRingBuffers();
+            startLoggingAllExceptPerPacketBuffers();
+        }
+    }
+
     private boolean startLoggingAllExceptPerPacketBuffers() {
 
         if (mRingBuffers == null) {
@@ -743,4 +799,16 @@
 
         pw.println("--------------------------------------------------------------------");
     }
+
+    /**
+     * Enable packet fate monitoring.
+     *
+     * @param ifaceName Name of the interface.
+     */
+    @Override
+    public void startPktFateMonitoring(@NonNull String ifaceName) {
+        if (!mWifiNative.startPktFateMonitoring(ifaceName)) {
+            mLog.wC("Failed to start packet fate monitoring");
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index bd20501..35cebcc 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
+import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.SystemSensorManager;
@@ -82,6 +83,7 @@
 
     private final Context mContext;
     private final FrameworkFacade mFrameworkFacade = new FrameworkFacade();
+    private final DeviceConfigFacade mDeviceConfigFacade;
     private final HandlerThread mWifiServiceHandlerThread;
     private final HandlerThread mWifiCoreHandlerThread;
     private final HandlerThread mWifiP2pServiceHandlerThread;
@@ -153,6 +155,8 @@
     private final LinkProbeManager mLinkProbeManager;
     private final IpMemoryStore mIpMemoryStore;
     private final CellularLinkLayerStatsCollector mCellularLinkLayerStatsCollector;
+    private final MacAddressUtil mMacAddressUtil;
+    private final ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
 
     public WifiInjector(Context context) {
         if (context == null) {
@@ -167,7 +171,11 @@
 
         sWifiInjector = this;
 
+        mMacAddressUtil = new MacAddressUtil();
         mContext = context;
+        mDeviceConfigFacade = new DeviceConfigFacade();
+        mConnectionFailureNotificationBuilder = new ConnectionFailureNotificationBuilder(
+                mContext, getWifiStackPackageName(), mFrameworkFacade);
         mWifiScoreCard = new WifiScoreCard(mClock,
                 Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID));
         mSettingsStore = new WifiSettingsStore(mContext);
@@ -202,7 +210,7 @@
                 mCellularLinkLayerStatsCollector);
         // Modules interacting with Native.
         mWifiMonitor = new WifiMonitor(this);
-        mHalDeviceManager = new HalDeviceManager(mClock);
+        mHalDeviceManager = new HalDeviceManager(mClock, clientModeImplLooper);
         mWifiVendorHal =
                 new WifiVendorHal(mHalDeviceManager, mWifiCoreHandlerThread.getLooper());
         mSupplicantStaIfaceHal =
@@ -239,7 +247,7 @@
         mWifiKeyStore = new WifiKeyStore(mKeyStore);
         mWifiConfigStore = new WifiConfigStore(
                 mContext, clientModeImplLooper, mClock, mWifiMetrics,
-                WifiConfigStore.createSharedFile());
+                WifiConfigStore.createSharedFile(mFrameworkFacade.isNiapModeOn(mContext)));
         SubscriptionManager subscriptionManager =
                 mContext.getSystemService(SubscriptionManager.class);
         // Config Manager
@@ -249,7 +257,7 @@
                 mWifiPermissionsWrapper, this, new NetworkListSharedStoreData(mContext),
                 new NetworkListUserStoreData(mContext),
                 new DeletedEphemeralSsidsStoreData(mClock), new RandomizedMacStoreData(),
-                mFrameworkFacade, mWifiCoreHandlerThread.getLooper());
+                mFrameworkFacade, mWifiCoreHandlerThread.getLooper(), mDeviceConfigFacade);
         mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
         mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
         mConnectivityLocalLog = new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 256 : 512);
@@ -299,7 +307,8 @@
         mWifiDiagnostics = new WifiDiagnostics(
                 mContext, this, mWifiNative, mBuildProperties,
                 new LastMileLogger(this), mClock);
-        mWifiDataStall = new WifiDataStall(mContext, mFrameworkFacade, mWifiMetrics);
+        mWifiDataStall = new WifiDataStall(mContext, mFrameworkFacade, mWifiMetrics,
+                mDeviceConfigFacade, clientModeImplLooper, mClock);
         mWifiMetrics.setWifiDataStall(mWifiDataStall);
         mLinkProbeManager = new LinkProbeManager(mClock, mWifiNative, mWifiMetrics,
                 mFrameworkFacade, mWifiCoreHandlerThread.getLooper(), mContext);
@@ -535,7 +544,7 @@
                                            @NonNull SoftApModeConfiguration config) {
         return new SoftApManager(mContext, mWifiCoreHandlerThread.getLooper(),
                 mFrameworkFacade, mWifiNative, mCountryCode.getCountryCode(), callback,
-                mWifiApConfigStore, config, mWifiMetrics, mSarManager);
+                mWifiApConfigStore, config, mWifiMetrics, mSarManager, mWifiDiagnostics);
     }
 
     /**
@@ -605,8 +614,9 @@
                 mWifiCoreHandlerThread.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
                 mWifiConfigManager, mWifiConfigStore, clientModeImpl,
                 new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
-        mWifiLastResortWatchdog = new WifiLastResortWatchdog(this, mClock,
-                mWifiMetrics, clientModeImpl, clientModeImpl.getHandler().getLooper());
+        mWifiLastResortWatchdog = new WifiLastResortWatchdog(this, mContext, mClock,
+                mWifiMetrics, clientModeImpl, clientModeImpl.getHandler().getLooper(),
+                mDeviceConfigFacade);
         return new WifiConnectivityManager(mContext, getScoringParams(),
                 clientModeImpl, this,
                 mWifiConfigManager, clientModeImpl.getWifiInfo(),
@@ -617,6 +627,17 @@
     }
 
     /**
+     * Construct a new instance of ConnectionFailureNotifier.
+     * @param wifiConnectivityManager
+     * @return the created instance
+     */
+    public ConnectionFailureNotifier makeConnectionFailureNotifier(
+            WifiConnectivityManager wifiConnectivityManager) {
+        return new ConnectionFailureNotifier(mContext, this, mFrameworkFacade, mWifiConfigManager,
+                wifiConnectivityManager, new Handler(mWifiCoreHandlerThread.getLooper()));
+    }
+
+    /**
      * Construct a new instance of {@link WifiNetworkFactory}.
      * TODO(b/116233964): Remove cyclic dependency between WifiConnectivityManager & ClientModeImpl.
      */
@@ -691,6 +712,18 @@
         return mRttHandlerThread;
     }
 
+    public MacAddressUtil getMacAddressUtil() {
+        return mMacAddressUtil;
+    }
+
+    public NotificationManager getNotificationManager() {
+        return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    public ConnectionFailureNotificationBuilder getConnectionFailureNotificationBuilder() {
+        return mConnectionFailureNotificationBuilder;
+    }
+
     /**
      * Returns a single instance of HalDeviceManager for injection.
      */
@@ -746,6 +779,14 @@
         return mIpMemoryStore;
     }
 
+    public HostapdHal getHostapdHal() {
+        return mHostapdHal;
+    }
+
+    public String getWifiStackPackageName() {
+        return mContext.getPackageName();
+    }
+
     @NonNull
     public WifiKeyStore getWifiKeyStore() {
         return mWifiKeyStore;
diff --git a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
index 6889b50..e8a0688 100644
--- a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
+++ b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -75,6 +77,10 @@
     @VisibleForTesting
     public static final long LAST_TRIGGER_TIMEOUT_MILLIS = 2 * 3600 * 1000; // 2 hours
 
+    private int mAbnormalConnectionDurationMs;
+    private boolean mAbnormalConnectionBugreportEnabled;
+
+
     /**
      * Cached WifiConfigurations of available networks seen within MAX_BSSID_AGE scan results
      * Key:BSSID, Value:Counters of failure types
@@ -102,22 +108,95 @@
     private Looper mClientModeImplLooper;
     private double mBugReportProbability = PROB_TAKE_BUGREPORT_DEFAULT;
     private Clock mClock;
+    private Context mContext;
+    private DeviceConfigFacade mDeviceConfigFacade;
     // If any connection failure happened after watchdog triggering restart then assume watchdog
     // did not fix the problem
     private boolean mWatchdogFixedWifi = true;
+    private long mLastStartConnectTime = 0;
+    private Handler mHandler;
 
     /**
      * Local log used for debugging any WifiLastResortWatchdog issues.
      */
     private final LocalLog mLocalLog = new LocalLog(100);
 
-    WifiLastResortWatchdog(WifiInjector wifiInjector, Clock clock, WifiMetrics wifiMetrics,
-            ClientModeImpl clientModeImpl, Looper clientModeImplLooper) {
+    WifiLastResortWatchdog(WifiInjector wifiInjector, Context context, Clock clock,
+            WifiMetrics wifiMetrics, ClientModeImpl clientModeImpl, Looper clientModeImplLooper,
+            DeviceConfigFacade deviceConfigFacade) {
         mWifiInjector = wifiInjector;
         mClock = clock;
         mWifiMetrics = wifiMetrics;
         mClientModeImpl = clientModeImpl;
         mClientModeImplLooper = clientModeImplLooper;
+        mContext = context;
+        mDeviceConfigFacade = deviceConfigFacade;
+        updateDeviceConfigFlags();
+        mHandler = new Handler(clientModeImplLooper) {
+            public void handleMessage(Message msg) {
+                processMessage(msg);
+            }
+        };
+
+        mDeviceConfigFacade.addOnPropertiesChangedListener(
+                command -> mHandler.post(command),
+                properties -> {
+                    updateDeviceConfigFlags();
+                });
+    }
+
+    private void updateDeviceConfigFlags() {
+        mAbnormalConnectionBugreportEnabled =
+                mDeviceConfigFacade.isAbnormalConnectionBugreportEnabled();
+        mAbnormalConnectionDurationMs =
+                mDeviceConfigFacade.getAbnormalConnectionDurationMs();
+        logv("updateDeviceConfigFlags: mAbnormalConnectionDurationMs = "
+                + mAbnormalConnectionDurationMs
+                + ", mAbnormalConnectionBugreportEnabled = "
+                + mAbnormalConnectionBugreportEnabled);
+    }
+
+    /**
+     * Returns handler for L2 events from supplicant.
+     * @return Handler
+     */
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Refreshes when the last CMD_START_CONNECT is triggered.
+     */
+    public void noteStartConnectTime() {
+        mHandler.post(() -> {
+            mLastStartConnectTime = mClock.getElapsedSinceBootMillis();
+        });
+    }
+
+    private void processMessage(Message msg) {
+        switch (msg.what) {
+            case WifiMonitor.NETWORK_CONNECTION_EVENT:
+                // Trigger bugreport for successful connections that take abnormally long
+                if (mAbnormalConnectionBugreportEnabled && mLastStartConnectTime > 0) {
+                    long durationMs = mClock.getElapsedSinceBootMillis() - mLastStartConnectTime;
+                    if (durationMs > mAbnormalConnectionDurationMs) {
+                        final String bugTitle = "Wi-Fi Bugreport: Abnormal connection time";
+                        final String bugDetail = "Expected connection to take less than "
+                                + mAbnormalConnectionDurationMs + " milliseconds. "
+                                + "Actually took " + durationMs + " milliseconds.";
+                        logv("Triggering bug report for abnormal connection time.");
+                        mWifiInjector.getClientModeImplHandler().post(() -> {
+                            mClientModeImpl.takeBugReport(bugTitle, bugDetail);
+                        });
+                    }
+                }
+                // Should reset last connection time after each connection regardless if bugreport
+                // is enabled or not.
+                mLastStartConnectTime = 0;
+                break;
+            default:
+                return;
+        }
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/WifiLockManager.java b/service/java/com/android/server/wifi/WifiLockManager.java
index e292a84..45e6882 100644
--- a/service/java/com/android/server/wifi/WifiLockManager.java
+++ b/service/java/com/android/server/wifi/WifiLockManager.java
@@ -183,10 +183,6 @@
      * @return true if the lock was successfully acquired, false if the lockMode was invalid.
      */
     public boolean acquireWifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
-        if (!isValidLockMode(lockMode)) {
-            throw new IllegalArgumentException("lockMode =" + lockMode);
-        }
-
         // Make a copy of the WorkSource before adding it to the WakeLock
         // This is to make sure worksource value can not be changed by caller
         // after function returns.
@@ -384,7 +380,13 @@
         }
     }
 
-    private static boolean isValidLockMode(int lockMode) {
+    /**
+     * Validate that the lock mode is valid - i.e. one of the supported enumerations.
+     *
+     * @param lockMode The lock mode to verify.
+     * @return true for valid lock modes, false otherwise.
+     */
+    public static boolean isValidLockMode(int lockMode) {
         if (lockMode != WifiManager.WIFI_MODE_FULL
                 && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY
                 && lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index a62ad37..6db4e99 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -26,6 +26,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.DeviceMobilityState;
@@ -180,6 +181,11 @@
     // Maximum time that a score breaching low event stays valid.
     public static final int VALIDITY_PERIOD_OF_SCORE_BREACH_LOW_MS = 90 * 1000; // 1.5 minutes
 
+    public static final int BAND_2G_MAX_FREQ_MHZ = 2484;
+    public static final int BAND_5G_LOW_MAX_FREQ_MHZ = 5240;
+    public static final int BAND_5G_MID_MAX_FREQ_MHZ = 5720;
+    public static final int BAND_5G_HIGH_MAX_FREQ_MHZ = 5865;
+
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
@@ -216,6 +222,7 @@
     private LinkedList<StaEventWithTime> mStaEventList = new LinkedList<>();
     private int mLastPollRssi = -127;
     private int mLastPollLinkSpeed = -1;
+    private int mLastPollRxLinkSpeed = -1;
     private int mLastPollFreq = -1;
     private int mLastScore = -1;
 
@@ -254,6 +261,16 @@
     private final SparseIntArray mRssiDeltaCounts = new SparseIntArray();
     /** Mapping of link speed values to LinkSpeedCount objects. */
     private final SparseArray<LinkSpeedCount> mLinkSpeedCounts = new SparseArray<>();
+
+    private final IntCounter mTxLinkSpeedCount2g = new IntCounter();
+    private final IntCounter mTxLinkSpeedCount5gLow = new IntCounter();
+    private final IntCounter mTxLinkSpeedCount5gMid = new IntCounter();
+    private final IntCounter mTxLinkSpeedCount5gHigh = new IntCounter();
+    private final IntCounter mRxLinkSpeedCount2g = new IntCounter();
+    private final IntCounter mRxLinkSpeedCount5gLow = new IntCounter();
+    private final IntCounter mRxLinkSpeedCount5gMid = new IntCounter();
+    private final IntCounter mRxLinkSpeedCount5gHigh = new IntCounter();
+
     /** RSSI of the scan result for the last connection event*/
     private int mScanResultRssi = 0;
     /** Boot-relative timestamp when the last candidate scanresult was received, used to calculate
@@ -464,6 +481,8 @@
                 sb.append(", mHidden=" + mRouterFingerPrintProto.hidden);
                 sb.append(", mRouterTechnology=" + mRouterFingerPrintProto.routerTechnology);
                 sb.append(", mSupportsIpv6=" + mRouterFingerPrintProto.supportsIpv6);
+                sb.append(", mEapMethod=" + mRouterFingerPrintProto.eapMethod);
+                sb.append(", mAuthPhase2Method=" + mRouterFingerPrintProto.authPhase2Method);
             }
             return sb.toString();
         }
@@ -500,9 +519,61 @@
                     if (candidate != null) {
                         updateMetricsFromScanResult(candidate);
                     }
+                    if (mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
+                            .authentication == WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE
+                            && config.enterpriseConfig != null) {
+                        int eapMethod = config.enterpriseConfig.getEapMethod();
+                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
+                                .eapMethod = getEapMethodProto(eapMethod);
+                        int phase2Method = config.enterpriseConfig.getPhase2Method();
+                        mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
+                                .authPhase2Method = getAuthPhase2MethodProto(phase2Method);
+                    }
                 }
             }
         }
+        private int getEapMethodProto(int eapMethod) {
+            switch (eapMethod) {
+                case WifiEnterpriseConfig.Eap.TLS:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_TLS;
+                case WifiEnterpriseConfig.Eap.UNAUTH_TLS:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_UNAUTH_TLS;
+                case WifiEnterpriseConfig.Eap.PEAP:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_PEAP;
+                case WifiEnterpriseConfig.Eap.PWD:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_PWD;
+                case WifiEnterpriseConfig.Eap.TTLS:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_TTLS;
+                case WifiEnterpriseConfig.Eap.SIM:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_SIM;
+                case WifiEnterpriseConfig.Eap.AKA:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_AKA;
+                case WifiEnterpriseConfig.Eap.AKA_PRIME:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_AKA_PRIME;
+                default:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_EAP_UNKNOWN;
+            }
+        }
+        private int getAuthPhase2MethodProto(int phase2Method) {
+            switch (phase2Method) {
+                case WifiEnterpriseConfig.Phase2.PAP:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_PAP;
+                case WifiEnterpriseConfig.Phase2.MSCHAP:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_MSCHAP;
+                case WifiEnterpriseConfig.Phase2.MSCHAPV2:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_MSCHAPV2;
+                case WifiEnterpriseConfig.Phase2.GTC:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_GTC;
+                case WifiEnterpriseConfig.Phase2.SIM:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_SIM;
+                case WifiEnterpriseConfig.Phase2.AKA:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_AKA;
+                case WifiEnterpriseConfig.Phase2.AKA_PRIME:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_AKA_PRIME;
+                default:
+                    return WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_NONE;
+            }
+        }
     }
 
     /**
@@ -1527,6 +1598,9 @@
         mLastPollFreq = wifiInfo.getFrequency();
         incrementRssiPollRssiCount(mLastPollFreq, mLastPollRssi);
         incrementLinkSpeedCount(mLastPollLinkSpeed, mLastPollRssi);
+        mLastPollRxLinkSpeed = wifiInfo.getRxLinkSpeedMbps();
+        incrementTxLinkSpeedBandCount(mLastPollLinkSpeed, mLastPollFreq);
+        incrementRxLinkSpeedBandCount(mLastPollRxLinkSpeed, mLastPollFreq);
     }
 
     /**
@@ -1596,6 +1670,56 @@
     }
 
     /**
+     * Increment occurrence count of Tx link speed for operating sub-band
+     * Ignores link speed values that are lower than MIN_LINK_SPEED_MBPS
+     * @param txLinkSpeed PHY layer Tx link speed in Mbps
+     * @param frequency Channel frequency of beacon frames in MHz
+     */
+    @VisibleForTesting
+    public void incrementTxLinkSpeedBandCount(int txLinkSpeed, int frequency) {
+        if (!(mLinkSpeedCountsLogging
+                && txLinkSpeed >= MIN_LINK_SPEED_MBPS)) {
+            return;
+        }
+        synchronized (mLock) {
+            if (frequency <= BAND_2G_MAX_FREQ_MHZ) {
+                mTxLinkSpeedCount2g.increment(txLinkSpeed);
+            } else if (frequency <= BAND_5G_LOW_MAX_FREQ_MHZ) {
+                mTxLinkSpeedCount5gLow.increment(txLinkSpeed);
+            } else if (frequency <= BAND_5G_MID_MAX_FREQ_MHZ) {
+                mTxLinkSpeedCount5gMid.increment(txLinkSpeed);
+            } else {
+                mTxLinkSpeedCount5gHigh.increment(txLinkSpeed);
+            }
+        }
+    }
+
+    /**
+     * Increment occurrence count of Rx link speed for operating sub-band
+     * Ignores link speed values that are lower than MIN_LINK_SPEED_MBPS
+     * @param rxLinkSpeed PHY layer Tx link speed in Mbps
+     * @param frequency Channel frequency of beacon frames in MHz
+     */
+    @VisibleForTesting
+    public void incrementRxLinkSpeedBandCount(int rxLinkSpeed, int frequency) {
+        if (!(mLinkSpeedCountsLogging
+                && rxLinkSpeed >= MIN_LINK_SPEED_MBPS)) {
+            return;
+        }
+        synchronized (mLock) {
+            if (frequency <= BAND_2G_MAX_FREQ_MHZ) {
+                mRxLinkSpeedCount2g.increment(rxLinkSpeed);
+            } else if (frequency <= BAND_5G_LOW_MAX_FREQ_MHZ) {
+                mRxLinkSpeedCount5gLow.increment(rxLinkSpeed);
+            } else if (frequency <= BAND_5G_MID_MAX_FREQ_MHZ) {
+                mRxLinkSpeedCount5gMid.increment(rxLinkSpeed);
+            } else {
+                mRxLinkSpeedCount5gHigh.increment(rxLinkSpeed);
+            }
+        }
+    }
+
+    /**
      * Increment count of Watchdog successes.
      */
     public void incrementNumLastResortWatchdogSuccesses() {
@@ -2713,6 +2837,16 @@
                         + mExperimentValues.wifiDataStallMinTxSuccessWithoutRx);
                 pw.println("mExperimentValues.linkSpeedCountsLoggingEnabled="
                         + mExperimentValues.linkSpeedCountsLoggingEnabled);
+                pw.println("mExperimentValues.dataStallDurationMs="
+                        + mExperimentValues.dataStallDurationMs);
+                pw.println("mExperimentValues.dataStallTxTputThrMbps="
+                        + mExperimentValues.dataStallTxTputThrMbps);
+                pw.println("mExperimentValues.dataStallRxTputThrMbps="
+                        + mExperimentValues.dataStallRxTputThrMbps);
+                pw.println("mExperimentValues.dataStallTxPerThr="
+                        + mExperimentValues.dataStallTxPerThr);
+                pw.println("mExperimentValues.dataStallCcaLevelThr="
+                        + mExperimentValues.dataStallCcaLevelThr);
                 pw.println("WifiIsUnusableEventList: ");
                 for (WifiIsUnusableWithTime event : mWifiIsUnusableList) {
                     pw.println(event);
@@ -2793,6 +2927,15 @@
                         + mWifiLogProto.numAddOrUpdateNetworkCalls);
                 pw.println("mWifiLogProto.numEnableNetworkCalls="
                         + mWifiLogProto.numEnableNetworkCalls);
+
+                pw.println("mWifiLogProto.txLinkSpeedCount2g=" + mTxLinkSpeedCount2g);
+                pw.println("mWifiLogProto.txLinkSpeedCount5gLow=" + mTxLinkSpeedCount5gLow);
+                pw.println("mWifiLogProto.txLinkSpeedCount5gMid=" + mTxLinkSpeedCount5gMid);
+                pw.println("mWifiLogProto.txLinkSpeedCount5gHigh=" + mTxLinkSpeedCount5gHigh);
+                pw.println("mWifiLogProto.rxLinkSpeedCount2g=" + mRxLinkSpeedCount2g);
+                pw.println("mWifiLogProto.rxLinkSpeedCount5gLow=" + mRxLinkSpeedCount5gLow);
+                pw.println("mWifiLogProto.rxLinkSpeedCount5gMid=" + mRxLinkSpeedCount5gMid);
+                pw.println("mWifiLogProto.rxLinkSpeedCount5gHigh=" + mRxLinkSpeedCount5gHigh);
             }
         }
     }
@@ -2854,6 +2997,7 @@
     public void updateSavedNetworks(List<WifiConfiguration> networks) {
         synchronized (mLock) {
             mWifiLogProto.numSavedNetworks = networks.size();
+            mWifiLogProto.numSavedNetworksWithMacRandomization = 0;
             mWifiLogProto.numOpenNetworks = 0;
             mWifiLogProto.numLegacyPersonalNetworks = 0;
             mWifiLogProto.numLegacyEnterpriseNetworks = 0;
@@ -3337,6 +3481,15 @@
                                 entry.count = count;
                                 return entry;
                             });
+            // 'G' is due to that 1st Letter after _ becomes capital during protobuff compilation
+            mWifiLogProto.txLinkSpeedCount2G = mTxLinkSpeedCount2g.toProto();
+            mWifiLogProto.txLinkSpeedCount5GLow = mTxLinkSpeedCount5gLow.toProto();
+            mWifiLogProto.txLinkSpeedCount5GMid = mTxLinkSpeedCount5gMid.toProto();
+            mWifiLogProto.txLinkSpeedCount5GHigh = mTxLinkSpeedCount5gHigh.toProto();
+            mWifiLogProto.rxLinkSpeedCount2G = mRxLinkSpeedCount2g.toProto();
+            mWifiLogProto.rxLinkSpeedCount5GLow = mRxLinkSpeedCount5gLow.toProto();
+            mWifiLogProto.rxLinkSpeedCount5GMid = mRxLinkSpeedCount5gMid.toProto();
+            mWifiLogProto.rxLinkSpeedCount5GHigh = mRxLinkSpeedCount5gHigh.toProto();
         }
     }
 
@@ -3440,6 +3593,14 @@
             mRssiPollCountsMap.clear();
             mRssiDeltaCounts.clear();
             mLinkSpeedCounts.clear();
+            mTxLinkSpeedCount2g.clear();
+            mTxLinkSpeedCount5gLow.clear();
+            mTxLinkSpeedCount5gMid.clear();
+            mTxLinkSpeedCount5gHigh.clear();
+            mRxLinkSpeedCount2g.clear();
+            mRxLinkSpeedCount5gLow.clear();
+            mRxLinkSpeedCount5gMid.clear();
+            mRxLinkSpeedCount5gHigh.clear();
             mWifiAlertReasonCounts.clear();
             mWifiScoreCounts.clear();
             mWifiUsabilityScoreCounts.clear();
@@ -3681,12 +3842,15 @@
         mLastPollRssi = -127;
         mLastPollFreq = -1;
         mLastPollLinkSpeed = -1;
+        mLastPollRxLinkSpeed = -1;
         mLastScore = -1;
         mLastWifiUsabilityScore = -1;
         mLastPredictionHorizonSec = -1;
-        mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis()));
-        // Prune StaEventList if it gets too long
-        if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
+        synchronized (mLock) {
+            mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis()));
+            // Prune StaEventList if it gets too long
+            if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
+        }
     }
 
     private ConfigInfo createConfigInfo(WifiConfiguration config) {
@@ -4518,6 +4682,7 @@
                 }
             }
             mWifiUsabilityStatsCounter = 0;
+            mWifiUsabilityStatsEntriesList.clear();
         }
     }
 
@@ -5095,4 +5260,49 @@
             mNumProvisionSuccess++;
         }
     }
+
+    /**
+     * Sets the duration for evaluating Wifi condition to trigger a data stall
+     */
+    public void setDataStallDurationMs(int duration) {
+        synchronized (mLock) {
+            mExperimentValues.dataStallDurationMs = duration;
+        }
+    }
+
+    /**
+     * Sets the threshold of Tx throughput below which to trigger a data stall
+     */
+    public void setDataStallTxTputThrMbps(int txTputThr) {
+        synchronized (mLock) {
+            mExperimentValues.dataStallTxTputThrMbps = txTputThr;
+        }
+    }
+
+    /**
+     * Sets the threshold of Rx throughput below which to trigger a data stall
+     */
+    public void setDataStallRxTputThrMbps(int rxTputThr) {
+        synchronized (mLock) {
+            mExperimentValues.dataStallRxTputThrMbps = rxTputThr;
+        }
+    }
+
+    /**
+     * Sets the threshold of Tx packet error rate above which to trigger a data stall
+     */
+    public void setDataStallTxPerThr(int txPerThr) {
+        synchronized (mLock) {
+            mExperimentValues.dataStallTxPerThr = txPerThr;
+        }
+    }
+
+    /**
+     * Sets the threshold of CCA level above which to trigger a data stall
+     */
+    public void setDataStallCcaLevelThr(int ccaLevel) {
+        synchronized (mLock) {
+            mExperimentValues.dataStallCcaLevelThr = ccaLevel;
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java
index ecc1a9f..4b5866b 100644
--- a/service/java/com/android/server/wifi/WifiNetworkFactory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java
@@ -68,6 +68,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -81,6 +82,8 @@
     @VisibleForTesting
     private static final int SCORE_FILTER = 60;
     @VisibleForTesting
+    public static final int CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS = 20 * 1000;  // 20 seconds
+    @VisibleForTesting
     public static final int PERIODIC_SCAN_INTERVAL_MS = 10 * 1000; // 10 seconds
     @VisibleForTesting
     public static final int NETWORK_CONNECTION_TIMEOUT_MS = 30 * 1000; // 30 seconds
@@ -97,6 +100,9 @@
     @VisibleForTesting
     public static final String UI_START_INTENT_EXTRA_REQUEST_IS_FOR_SINGLE_NETWORK =
             "com.android.settings.wifi.extra.REQUEST_IS_FOR_SINGLE_NETWORK";
+    // Capacity limit of approved Access Point per App
+    @VisibleForTesting
+    public static final int NUM_OF_ACCESS_POINT_LIMIT_PER_APP = 50;
 
     private final Context mContext;
     private final ActivityManager mActivityManager;
@@ -117,8 +123,8 @@
     private final ExternalCallbackTracker<INetworkRequestMatchCallback> mRegisteredCallbacks;
     private final Messenger mSrcMessenger;
     // Store all user approved access points for apps.
-    // TODO(b/122658039): Persist this.
-    private final Map<String, Set<AccessPoint>> mUserApprovedAccessPointMap = new HashMap<>();
+    @VisibleForTesting
+    public final Map<String, LinkedHashSet<AccessPoint>> mUserApprovedAccessPointMap;
     private WifiScanner mWifiScanner;
 
     private int mGenericConnectionReqCount = 0;
@@ -226,37 +232,10 @@
             if (mVerboseLoggingEnabled) {
                 Log.v(TAG, "Received " + scanResults.length + " scan results");
             }
-            List<ScanResult> matchedScanResults =
-                    getNetworksMatchingActiveNetworkRequest(scanResults);
-            if (mActiveMatchedScanResults == null) {
-                // only note the first match size in metrics (chances of this changing in further
-                // scans is pretty low)
-                mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram(
-                        matchedScanResults.size());
-            }
-            mActiveMatchedScanResults = matchedScanResults;
-
-            ScanResult approvedScanResult = null;
-            if (isActiveRequestForSingleAccessPoint()) {
-                approvedScanResult =
-                        findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults();
-            }
-            if (approvedScanResult != null
-                    && !mWifiConfigManager.wasEphemeralNetworkDeleted(
-                            ScanResultUtil.createQuotedSSID(approvedScanResult.SSID))) {
-                Log.v(TAG, "Approved access point found in matching scan results. "
-                        + "Triggering connect " + approvedScanResult);
-                handleConnectToNetworkUserSelectionInternal(
-                        ScanResultUtil.createNetworkFromScanResult(approvedScanResult));
-                mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass();
-                // TODO (b/122658039): Post notification.
-            } else {
-                if (mVerboseLoggingEnabled) {
-                    Log.v(TAG, "No approved access points found in matching scan results. "
-                            + "Sending match callback");
-                }
-                sendNetworkRequestMatchCallbacksForActiveRequest(matchedScanResults);
-                // Didn't find an approved match, schedule the next scan.
+            if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(scanResults)) {
+                // Didn't find an approved match, send the matching results to UI and schedule the
+                // next scan.
+                sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
                 scheduleNextPeriodicScan();
             }
         }
@@ -346,12 +325,13 @@
         public Map<String, Set<AccessPoint>> toSerialize() {
             // Clear the flag after writing to disk.
             mHasNewDataToSerialize = false;
-            return mUserApprovedAccessPointMap;
+            return new HashMap<>(mUserApprovedAccessPointMap);
         }
 
         @Override
         public void fromDeserialized(Map<String, Set<AccessPoint>> approvedAccessPointMap) {
-            mUserApprovedAccessPointMap.putAll(approvedAccessPointMap);
+            approvedAccessPointMap.forEach((key, value) ->
+                    mUserApprovedAccessPointMap.put(key, new LinkedHashSet<>(value)));
         }
 
         @Override
@@ -397,6 +377,7 @@
         mConnectionTimeoutAlarmListener = new ConnectionTimeoutAlarmListener();
         mRegisteredCallbacks = new ExternalCallbackTracker<INetworkRequestMatchCallback>(mHandler);
         mSrcMessenger = new Messenger(new Handler(looper, mNetworkConnectionTriggerCallback));
+        mUserApprovedAccessPointMap = new HashMap<>();
 
         // register the data store for serializing/deserializing data.
         configStore.registerStoreData(
@@ -447,7 +428,12 @@
                     new NetworkFactoryUserSelectionCallback(mActiveSpecificNetworkRequest));
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to invoke user selection registration callback " + callback, e);
+            return;
         }
+
+        // If we are already in the midst of processing a request, send matching callbacks
+        // immediately on registering the callback.
+        sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
     }
 
     /**
@@ -606,10 +592,19 @@
                     wns.requestorUid, wns.requestorPackageName);
             mWifiMetrics.incrementNetworkRequestApiNumRequest();
 
-            // Start UI to let the user grant/disallow this request from the app.
-            startUi();
-            // Trigger periodic scans for finding a network in the request.
-            startPeriodicScans();
+            // Fetch the latest cached scan results to speed up network matching.
+            ScanResult[] cachedScanResults = getFilteredCachedScanResults();
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Using cached " + cachedScanResults.length + " scan results");
+            }
+            if (!handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(cachedScanResults)) {
+                // Start UI to let the user grant/disallow this request from the app.
+                startUi();
+                // Didn't find an approved match, send the matching results to UI and trigger
+                // periodic scans for finding a network in the request.
+                sendNetworkRequestMatchCallbacksForActiveRequest(mActiveMatchedScanResults);
+                startPeriodicScans();
+            }
         }
     }
 
@@ -706,6 +701,11 @@
         WifiConfiguration existingSavedNetwork =
                 mWifiConfigManager.getConfiguredNetwork(network.configKey());
         if (existingSavedNetwork != null) {
+            if (WifiConfigurationUtil.hasCredentialChanged(existingSavedNetwork, network)) {
+                // TODO (b/142035508): What if the user has a saved network with different
+                // credentials?
+                Log.w(TAG, "Network config already present in config manager, reusing");
+            }
             return existingSavedNetwork.networkId;
         }
         NetworkUpdateResult networkUpdateResult =
@@ -718,6 +718,32 @@
         return networkUpdateResult.netId;
     }
 
+    // Helper method to remove the provided network configuration from WifiConfigManager, if it was
+    // added by an app's specifier request.
+    private void disconnectAndRemoveNetworkFromWifiConfigManager(
+            @Nullable WifiConfiguration network) {
+        // Trigger a disconnect first.
+        mWifiInjector.getClientModeImpl().disconnectCommand();
+
+        if (network == null) return;
+        WifiConfiguration wcmNetwork =
+                mWifiConfigManager.getConfiguredNetwork(network.configKey());
+        if (wcmNetwork == null) {
+            Log.e(TAG, "Network not present in config manager");
+            return;
+        }
+        // Remove the network if it was added previously by an app's specifier request.
+        if (wcmNetwork.ephemeral && wcmNetwork.fromWifiNetworkSpecifier) {
+            boolean success =
+                    mWifiConfigManager.removeNetwork(wcmNetwork.networkId, wcmNetwork.creatorUid);
+            if (!success) {
+                Log.e(TAG, "Failed to remove network from config manager");
+            } else if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Removed network from config manager " + wcmNetwork.networkId);
+            }
+        }
+    }
+
     // Helper method to trigger a connection request & schedule a timeout alarm to track the
     // connection request.
     private void connectToNetwork(@NonNull WifiConfiguration network) {
@@ -756,7 +782,6 @@
         networkToConnect.SSID = network.SSID;
         // Set the WifiConfiguration.BSSID field to prevent roaming.
         networkToConnect.BSSID = findBestBssidFromActiveMatchedScanResultsForNetwork(network);
-        // Mark the network ephemeral so that it's automatically removed at the end of connection.
         networkToConnect.ephemeral = true;
         networkToConnect.fromWifiNetworkSpecifier = true;
 
@@ -764,7 +789,8 @@
         mUserSelectedNetwork = networkToConnect;
 
         // Disconnect from the current network before issuing a new connect request.
-        mWifiInjector.getClientModeImpl().disconnectCommand();
+        disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork);
+
         // Trigger connection to the network.
         connectToNetwork(networkToConnect);
         // Triggered connection to network, now wait for the connection status.
@@ -960,6 +986,7 @@
         mConnectedSpecificNetworkRequestSpecifier = mActiveSpecificNetworkRequestSpecifier;
         mActiveSpecificNetworkRequest = null;
         mActiveSpecificNetworkRequestSpecifier = null;
+        mActiveMatchedScanResults = null;
         mPendingConnectionSuccess = false;
         // Cancel connection timeout alarm.
         cancelConnectionTimeout();
@@ -968,7 +995,7 @@
     // Invoked at the termination of current connected request processing.
     private void teardownForConnectedNetwork() {
         Log.i(TAG, "Disconnecting from network on reset");
-        mWifiInjector.getClientModeImpl().disconnectCommand();
+        disconnectAndRemoveNetworkFromWifiConfigManager(mUserSelectedNetwork);
         mConnectedSpecificNetworkRequest = null;
         mConnectedSpecificNetworkRequestSpecifier = null;
         // ensure there is no active request in progress.
@@ -1106,7 +1133,8 @@
     }
 
     private void sendNetworkRequestMatchCallbacksForActiveRequest(
-            List<ScanResult> matchedScanResults) {
+            @Nullable List<ScanResult> matchedScanResults) {
+        if (matchedScanResults == null || matchedScanResults.isEmpty()) return;
         if (mRegisteredCallbacks.getNumCallbacks() == 0) {
             Log.e(TAG, "No callback registered for sending network request matches. "
                     + "Ignoring...");
@@ -1135,10 +1163,11 @@
         mConnectionTimeoutSet = true;
     }
 
-    private @NonNull CharSequence getAppName(@NonNull String packageName) {
+    private @NonNull CharSequence getAppName(@NonNull String packageName, int uid) {
         ApplicationInfo applicationInfo = null;
         try {
-            applicationInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
+            applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+                packageName, 0, UserHandle.getUserId(uid));
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Failed to find app name for " + packageName);
             return "";
@@ -1153,7 +1182,8 @@
         intent.addCategory(UI_START_INTENT_CATEGORY);
         intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(UI_START_INTENT_EXTRA_APP_NAME,
-                getAppName(mActiveSpecificNetworkRequestSpecifier.requestorPackageName));
+                getAppName(mActiveSpecificNetworkRequestSpecifier.requestorPackageName,
+                           mActiveSpecificNetworkRequestSpecifier.requestorUid));
         intent.putExtra(UI_START_INTENT_EXTRA_REQUEST_IS_FOR_SINGLE_NETWORK,
                 isActiveRequestForSingleNetwork());
         mContext.startActivityAsUser(intent, UserHandle.getUserHandleForUid(
@@ -1235,6 +1265,9 @@
                     new AccessPoint(scanResult.SSID,
                             MacAddress.fromString(scanResult.BSSID), fromScanResult.networkType);
             if (approvedAccessPoints.contains(accessPoint)) {
+                // keep the most recently used AP in the end
+                approvedAccessPoints.remove(accessPoint);
+                approvedAccessPoints.add(accessPoint);
                 if (mVerboseLoggingEnabled) {
                     Log.v(TAG, "Found " + accessPoint
                             + " in user approved access point for " + requestorPackageName);
@@ -1254,10 +1287,11 @@
         // object representing an entire network from UI, we need to ensure that all the visible
         // BSSIDs matching the original request and the selected network are stored.
         Set<AccessPoint> newUserApprovedAccessPoints = new HashSet<>();
+
+        ScanResultMatchInfo fromWifiConfiguration =
+                ScanResultMatchInfo.fromWifiConfiguration(network);
         for (ScanResult scanResult : mActiveMatchedScanResults) {
             ScanResultMatchInfo fromScanResult = ScanResultMatchInfo.fromScanResult(scanResult);
-            ScanResultMatchInfo fromWifiConfiguration =
-                    ScanResultMatchInfo.fromWifiConfiguration(network);
             if (fromScanResult.equals(fromWifiConfiguration)) {
                 AccessPoint approvedAccessPoint =
                         new AccessPoint(scanResult.SSID, MacAddress.fromString(scanResult.BSSID),
@@ -1268,10 +1302,10 @@
         if (newUserApprovedAccessPoints.isEmpty()) return;
 
         String requestorPackageName = mActiveSpecificNetworkRequestSpecifier.requestorPackageName;
-        Set<AccessPoint> approvedAccessPoints =
+        LinkedHashSet<AccessPoint> approvedAccessPoints =
                 mUserApprovedAccessPointMap.get(requestorPackageName);
         if (approvedAccessPoints == null) {
-            approvedAccessPoints = new HashSet<>();
+            approvedAccessPoints = new LinkedHashSet<>();
             mUserApprovedAccessPointMap.put(requestorPackageName, approvedAccessPoints);
             // Note the new app in metrics.
             mWifiMetrics.incrementNetworkRequestApiNumApps();
@@ -1280,22 +1314,92 @@
             Log.v(TAG, "Adding " + newUserApprovedAccessPoints
                     + " to user approved access point for " + requestorPackageName);
         }
+        // keep the most recently added APs in the end
+        approvedAccessPoints.removeAll(newUserApprovedAccessPoints);
         approvedAccessPoints.addAll(newUserApprovedAccessPoints);
+        cleanUpLRUAccessPoints(approvedAccessPoints);
         saveToStore();
     }
 
     /**
+     * Handle scan results
+     * a) Find all scan results matching the active network request.
+     * b) If the request is for a single bssid, check if the matching ScanResult was pre-approved
+     * by the user.
+     * c) If yes to (b), trigger a connect immediately and returns true. Else, returns false.
+     *
+     * @param scanResults Array of {@link ScanResult} to be processed.
+     * @return true if a pre-approved network was found for connection, false otherwise.
+     */
+    private boolean handleScanResultsAndTriggerConnectIfUserApprovedMatchFound(
+            ScanResult[] scanResults) {
+        List<ScanResult> matchedScanResults =
+                getNetworksMatchingActiveNetworkRequest(scanResults);
+        if ((mActiveMatchedScanResults == null || mActiveMatchedScanResults.isEmpty())
+                && !matchedScanResults.isEmpty()) {
+            // only note the first match size in metrics (chances of this changing in further
+            // scans is pretty low)
+            mWifiMetrics.incrementNetworkRequestApiMatchSizeHistogram(
+                    matchedScanResults.size());
+        }
+        mActiveMatchedScanResults = matchedScanResults;
+
+        ScanResult approvedScanResult = null;
+        if (isActiveRequestForSingleAccessPoint()) {
+            approvedScanResult =
+                    findUserApprovedAccessPointForActiveRequestFromActiveMatchedScanResults();
+        }
+        if (approvedScanResult != null
+                && !mWifiConfigManager.wasEphemeralNetworkDeleted(
+                ScanResultUtil.createQuotedSSID(approvedScanResult.SSID))) {
+            Log.v(TAG, "Approved access point found in matching scan results. "
+                    + "Triggering connect " + approvedScanResult);
+            handleConnectToNetworkUserSelectionInternal(
+                    ScanResultUtil.createNetworkFromScanResult(approvedScanResult));
+            mWifiMetrics.incrementNetworkRequestApiNumUserApprovalBypass();
+            return true;
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "No approved access points found in matching scan results");
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the latest cached scan results from wifi scanner and filter out any
+     * {@link ScanResult} older than {@link #CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS}.
+     */
+    private @NonNull ScanResult[] getFilteredCachedScanResults() {
+        List<ScanResult> cachedScanResults = mWifiScanner.getSingleScanResults();
+        if (cachedScanResults == null || cachedScanResults.isEmpty()) return new ScanResult[0];
+        long currentTimeInMillis = mClock.getElapsedSinceBootMillis();
+        return cachedScanResults.stream()
+                .filter(scanResult
+                        -> ((currentTimeInMillis - (scanResult.timestamp / 1000))
+                        < CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS))
+                .toArray(ScanResult[]::new);
+    }
+
+    /**
+     * Clean up least recently used Access Points if specified app reach the limit.
+     */
+    private static void cleanUpLRUAccessPoints(Set<AccessPoint> approvedAccessPoints) {
+        if (approvedAccessPoints.size() <= NUM_OF_ACCESS_POINT_LIMIT_PER_APP) {
+            return;
+        }
+        Iterator iter = approvedAccessPoints.iterator();
+        while (iter.hasNext() && approvedAccessPoints.size() > NUM_OF_ACCESS_POINT_LIMIT_PER_APP) {
+            iter.next();
+            iter.remove();
+        }
+    }
+
+    /**
      * Remove all user approved access points for the specified app.
      */
     public void removeUserApprovedAccessPointsForApp(@NonNull String packageName) {
-        Iterator<Map.Entry<String, Set<AccessPoint>>> iter =
-                mUserApprovedAccessPointMap.entrySet().iterator();
-        while (iter.hasNext()) {
-            Map.Entry<String, Set<AccessPoint>> entry = iter.next();
-            if (packageName.equals(entry.getKey())) {
-                Log.i(TAG, "Removing all approved access points for " + packageName);
-                iter.remove();
-            }
+        if (mUserApprovedAccessPointMap.remove(packageName) != null) {
+            Log.i(TAG, "Removing all approved access points for " + packageName);
         }
         saveToStore();
     }
diff --git a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
index 91a8643..ae7892c 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
@@ -37,6 +37,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSuggestion;
+import android.net.wifi.WifiScanner;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -51,6 +52,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -90,6 +92,10 @@
     @VisibleForTesting
     public static final String EXTRA_UID =
             "com.android.server.wifi.extra.NetworkSuggestion.UID";
+    /**
+     * Limit number of hidden networks attach to scan
+     */
+    private static final int NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN = 100;
 
     private final Context mContext;
     private final Resources mResources;
@@ -575,6 +581,8 @@
             if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) {
                 Log.i(TAG, "Setting the carrier provisioning app approved");
                 perAppInfo.hasUserApproved = true;
+            } else {
+                sendUserApprovalNotification(packageName, uid);
             }
         }
         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
@@ -644,7 +652,6 @@
             perAppInfo.extNetworkSuggestions.removeAll(extNetworkSuggestions);
         } else {
             // empty list is used to clear everything for the app. Store a copy for use below.
-            extNetworkSuggestions = new HashSet<>(perAppInfo.extNetworkSuggestions);
             perAppInfo.extNetworkSuggestions.clear();
         }
         if (perAppInfo.extNetworkSuggestions.isEmpty()) {
@@ -694,6 +701,13 @@
                     + ". Network suggestions not found in active network suggestions");
             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
         }
+        if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) {
+            // empty list is used to clear everything for the app.
+            if (extNetworkSuggestions.isEmpty()) {
+                extNetworkSuggestions = new HashSet<>(perAppInfo.extNetworkSuggestions);
+            }
+            triggerDisconnectIfServingNetworkSuggestionRemoved(extNetworkSuggestions);
+        }
         removeInternal(extNetworkSuggestions, packageName, perAppInfo);
         saveToStore();
         mWifiMetrics.incrementNetworkSuggestionApiNumModification();
@@ -787,10 +801,11 @@
                 PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
-    private @NonNull CharSequence getAppName(@NonNull String packageName) {
+    private @NonNull CharSequence getAppName(@NonNull String packageName, int uid) {
         ApplicationInfo applicationInfo = null;
         try {
-            applicationInfo = mPackageManager.getApplicationInfo(packageName, 0);
+            applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+                packageName, 0, UserHandle.getUserId(uid));
         } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Failed to find app name for " + packageName);
             return "";
@@ -813,13 +828,14 @@
                                 packageName, uid))
                         .build();
 
-        CharSequence appName = getAppName(packageName);
+        CharSequence appName = getAppName(packageName, uid);
         Notification notification = new Notification.Builder(
                 mContext, SystemNotificationChannels.NETWORK_STATUS)
                 .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
                 .setTicker(mResources.getString(R.string.wifi_suggestion_title))
                 .setContentTitle(mResources.getString(R.string.wifi_suggestion_title))
-                .setContentText(mResources.getString(R.string.wifi_suggestion_content, appName))
+                .setStyle(new Notification.BigTextStyle()
+                        .bigText(mResources.getString(R.string.wifi_suggestion_content, appName)))
                 .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION,
                         packageName, uid))
                 .setShowWhen(false)
@@ -956,6 +972,28 @@
     }
 
     /**
+     * Get hidden network from active network suggestions.
+     * Todo(): Now limit by a fixed number, maybe we can try rotation?
+     * @return set of WifiConfigurations
+     */
+    public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList() {
+        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks = new ArrayList<>();
+        for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) {
+            if (!appInfo.hasUserApproved) continue;
+            for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions) {
+                if (!ewns.wns.wifiConfiguration.hiddenSSID) continue;
+                hiddenNetworks.add(
+                        new WifiScanner.ScanSettings.HiddenNetwork(
+                                ewns.wns.wifiConfiguration.SSID));
+                if (hiddenNetworks.size() >= NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN) {
+                    return hiddenNetworks;
+                }
+            }
+        }
+        return hiddenNetworks;
+    }
+
+    /**
      * Helper method to send the post connection broadcast to specified package.
      */
     private void sendPostConnectionBroadcast(
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index 70a749b..33cc150 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -249,7 +249,8 @@
         double filteredRssi = mVelocityBasedConnectedScore.getFilteredRssi();
         double rssiThreshold = mVelocityBasedConnectedScore.getAdjustedRssiThreshold();
         int freq = wifiInfo.getFrequency();
-        int linkSpeed = wifiInfo.getLinkSpeed();
+        int txLinkSpeed = wifiInfo.getLinkSpeed();
+        int rxLinkSpeed = wifiInfo.getRxLinkSpeedMbps();
         double txSuccessRate = wifiInfo.txSuccessRate;
         double txRetriesRate = wifiInfo.txRetriesRate;
         double txBadRate = wifiInfo.txBadRate;
@@ -258,9 +259,9 @@
         try {
             String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
             s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
-                    "%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d",
+                    "%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d",
                     timestamp, mSessionNumber, netId,
-                    rssi, filteredRssi, rssiThreshold, freq, linkSpeed,
+                    rssi, filteredRssi, rssiThreshold, freq, txLinkSpeed, rxLinkSpeed,
                     txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
                     mNudYes, mNudCount,
                     s1, s2, score);
@@ -292,8 +293,8 @@
         synchronized (mLinkMetricsHistory) {
             history = new LinkedList<>(mLinkMetricsHistory);
         }
-        pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold,"
-                + "freq,linkspeed,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,s1,s2,score");
+        pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold, freq,txLinkSpeed,"
+                + "rxLinkSpeed,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,s1,s2,score");
         for (String line : history) {
             pw.println(line);
         }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 81beab0..b48e801 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -529,10 +529,6 @@
                         if (mSettingsStore.handleAirplaneModeToggled()) {
                             mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
                         }
-                        if (mSettingsStore.isAirplaneModeOn()) {
-                            Log.d(TAG, "resetting country code because Airplane mode is ON");
-                            mCountryCode.airplaneModeEnabled();
-                        }
                     }
                 },
                 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
@@ -756,10 +752,11 @@
     }
 
     // Helper method to check if the entity initiating the binder call is a system app.
-    private boolean isSystem(String packageName) {
+    private boolean isSystem(String packageName, int uid) {
         long ident = Binder.clearCallingIdentity();
         try {
-            ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(packageName, 0);
+            ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0, UserHandle.getUserId(uid));
             return info.isSystemApp() || info.isUpdatedSystemApp();
         } catch (PackageManager.NameNotFoundException e) {
             // In case of exception, assume unknown app (more strict checking)
@@ -854,12 +851,12 @@
      * Note: Invoke mAppOps.checkPackage(uid, packageName) before to ensure correct package name.
      */
     private boolean isTargetSdkLessThanQOrPrivileged(String packageName, int pid, int uid) {
-        return mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q)
+        return mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, uid)
                 || isPrivileged(pid, uid)
                 // DO/PO apps should be able to add/modify saved networks.
                 || isDeviceOrProfileOwner(uid)
                 // TODO: Remove this system app bypass once Q is released.
-                || isSystem(packageName)
+                || isSystem(packageName, uid)
                 || mWifiPermissionsUtil.checkSystemAlertWindowPermission(uid, packageName);
     }
 
@@ -875,8 +872,10 @@
             return false;
         }
         boolean isPrivileged = isPrivileged(Binder.getCallingPid(), Binder.getCallingUid());
-        if (!isPrivileged
-                && !mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q)) {
+        if (!isPrivileged && !isDeviceOrProfileOwner(Binder.getCallingUid())
+                && !mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q,
+                  Binder.getCallingUid())
+                && !isSystem(packageName, Binder.getCallingUid())) {
             mLog.info("setWifiEnabled not allowed for uid=%")
                     .c(Binder.getCallingUid()).flush();
             return false;
@@ -894,6 +893,11 @@
             return false;
         }
 
+        // If we're in crypt debounce, ignore any wifi state change APIs.
+        if (mFrameworkFacade.inStorageManagerCryptKeeperBounce()) {
+            return false;
+        }
+
         mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
                 .c(Binder.getCallingUid()).c(enable).flush();
         long ident = Binder.clearCallingIdentity();
@@ -1046,6 +1050,10 @@
     public boolean startSoftAp(WifiConfiguration wifiConfig) {
         // NETWORK_STACK is a signature only permission.
         enforceNetworkStackPermission();
+        // If we're in crypt debounce, ignore any wifi state change APIs.
+        if (mFrameworkFacade.inStorageManagerCryptKeeperBounce()) {
+            return false;
+        }
 
         mLog.info("startSoftAp uid=%").c(Binder.getCallingUid()).flush();
 
@@ -1092,6 +1100,10 @@
     public boolean stopSoftAp() {
         // NETWORK_STACK is a signature only permission.
         enforceNetworkStackPermission();
+        // If we're in crypt debounce, ignore any wifi state change APIs.
+        if (mFrameworkFacade.inStorageManagerCryptKeeperBounce()) {
+            return false;
+        }
 
         // only permitted callers are allowed to this point - they must have gone through
         // connectivity service since this method is protected with the NETWORK_STACK PERMISSION
@@ -1422,6 +1434,10 @@
             return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
         }
 
+        if (mFrameworkFacade.inStorageManagerCryptKeeperBounce()) {
+            return LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE;
+        }
+
         mLog.info("startLocalOnlyHotspot uid=% pid=%").c(uid).c(pid).flush();
 
         synchronized (mLocalOnlyHotspotRequests) {
@@ -2290,18 +2306,16 @@
     @Override
     public boolean removePasspointConfiguration(String fqdn, String packageName) {
         final int uid = Binder.getCallingUid();
-        if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
-                && !mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) {
-            if (mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q)) {
-                return false;
-            }
-            throw new SecurityException(TAG + ": Permission denied");
+        boolean privileged = false;
+        if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
+                || mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) {
+            privileged = true;
         }
         mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT)) {
             return false;
         }
-        return mClientModeImpl.syncRemovePasspointConfig(mClientModeImplChannel, fqdn);
+        return mClientModeImpl.syncRemovePasspointConfig(mClientModeImplChannel, privileged, fqdn);
     }
 
     /**
@@ -2314,13 +2328,10 @@
     @Override
     public List<PasspointConfiguration> getPasspointConfigurations(String packageName) {
         final int uid = Binder.getCallingUid();
-        mAppOps.checkPackage(uid, packageName);
-        if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
-                && !mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)) {
-            if (mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q)) {
-                return new ArrayList<>();
-            }
-            throw new SecurityException(TAG + ": Permission denied");
+        boolean privileged = false;
+        if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)
+                || mWifiPermissionsUtil.checkNetworkSetupWizardPermission(uid)) {
+            privileged = true;
         }
         if (mVerboseLoggingEnabled) {
             mLog.info("getPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
@@ -2329,7 +2340,7 @@
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
             return new ArrayList<>();
         }
-        return mClientModeImpl.syncGetPasspointConfigs(mClientModeImplChannel);
+        return mClientModeImpl.syncGetPasspointConfigs(mClientModeImplChannel, privileged);
     }
 
     /**
@@ -2813,6 +2824,10 @@
         WorkSource updatedWs = (ws == null || ws.isEmpty())
                 ? new WorkSource(Binder.getCallingUid()) : ws;
 
+        if (!WifiLockManager.isValidLockMode(lockMode)) {
+            throw new IllegalArgumentException("lockMode =" + lockMode);
+        }
+
         Mutable<Boolean> lockSuccess = new Mutable<>();
         boolean runWithScissorsSuccess = mWifiInjector.getClientModeImplHandler().runWithScissors(
                 () -> {
@@ -2962,7 +2977,7 @@
                 if (mContext.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_WIFI_PASSPOINT)) {
                     List<PasspointConfiguration> configs = mClientModeImpl.syncGetPasspointConfigs(
-                            mClientModeImplChannel);
+                            mClientModeImplChannel, true);
                     if (configs != null) {
                         for (PasspointConfiguration config : configs) {
                             removePasspointConfiguration(config.getHomeSp().getFqdn(), packageName);
@@ -2980,9 +2995,20 @@
                 mWifiNetworkSuggestionsManager.clear();
                 mWifiInjector.getWifiScoreCard().clear();
             });
+            notifyFactoryReset();
         }
     }
 
+    /**
+     * Notify the Factory Reset Event to application who may installed wifi configurations.
+     */
+    private void notifyFactoryReset() {
+        Intent intent = new Intent(WifiManager.WIFI_NETWORK_SETTINGS_RESET_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                android.Manifest.permission.NETWORK_CARRIER_PROVISIONING);
+    }
+
     /* private methods */
     static boolean logAndReturnFalse(String s) {
         Log.d(TAG, s);
diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java
index 2565416..b2289a8 100644
--- a/service/java/com/android/server/wifi/WifiShellCommand.java
+++ b/service/java/com/android/server/wifi/WifiShellCommand.java
@@ -18,10 +18,14 @@
 
 import android.app.AppGlobals;
 import android.content.pm.IPackageManager;
+import android.net.wifi.WifiScanner;
 import android.os.Binder;
 import android.os.ShellCommand;
 
+import com.android.server.wifi.util.ApConfigUtil;
+
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -47,6 +51,9 @@
     private final WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     private final WifiConfigManager mWifiConfigManager;
     private final IPackageManager mPM;
+    private final WifiNative mWifiNative;
+    private final HostapdHal mHostapdHal;
+    private final WifiCountryCode mWifiCountryCode;
 
     WifiShellCommand(WifiInjector wifiInjector) {
         mClientModeImpl = wifiInjector.getClientModeImpl();
@@ -54,6 +61,9 @@
         mWifiNetworkSuggestionsManager = wifiInjector.getWifiNetworkSuggestionsManager();
         mWifiConfigManager = wifiInjector.getWifiConfigManager();
         mPM = AppGlobals.getPackageManager();
+        mHostapdHal = wifiInjector.getHostapdHal();
+        mWifiNative = wifiInjector.getWifiNative();
+        mWifiCountryCode = wifiInjector.getWifiCountryCode();
     }
 
     @Override
@@ -179,6 +189,62 @@
                 case "send-link-probe": {
                     return sendLinkProbe(pw);
                 }
+                case "force-softap-channel": {
+                    String nextArg = getNextArgRequired();
+                    if ("enabled".equals(nextArg))  {
+                        int apChannelMHz;
+                        try {
+                            apChannelMHz = Integer.parseInt(getNextArgRequired());
+                        } catch (NumberFormatException e) {
+                            pw.println("Invalid argument to 'force-softap-channel enabled' "
+                                    + "- must be a positive integer");
+                            return -1;
+                        }
+                        int apChannel = ApConfigUtil.convertFrequencyToChannel(apChannelMHz);
+                        if (apChannel == -1 || !isApChannelMHzValid(apChannelMHz)) {
+                            pw.println("Invalid argument to 'force-softap-channel enabled' "
+                                    + "- must be a valid WLAN channel");
+                            return -1;
+                        }
+                        mHostapdHal.enableForceSoftApChannel(apChannel);
+                        return 0;
+                    } else if ("disabled".equals(nextArg)) {
+                        mHostapdHal.disableForceSoftApChannel();
+                        return 0;
+                    } else {
+                        pw.println(
+                                "Invalid argument to 'force-softap-channel' - must be 'enabled'"
+                                        + " or 'disabled'");
+                        return -1;
+                    }
+                }
+                case "force-country-code": {
+                    String nextArg = getNextArgRequired();
+                    if ("enabled".equals(nextArg))  {
+                        String countryCode = getNextArgRequired();
+                        if (!(countryCode.length() == 2
+                                && countryCode.chars().allMatch(Character::isLetter))) {
+                            pw.println("Invalid argument to 'force-country-code enabled' "
+                                    + "- must be a two-letter string");
+                            return -1;
+                        }
+                        mWifiCountryCode.enableForceCountryCode(countryCode);
+                        return 0;
+                    } else if ("disabled".equals(nextArg)) {
+                        mWifiCountryCode.disableForceCountryCode();
+                        return 0;
+                    } else {
+                        pw.println(
+                                "Invalid argument to 'force-country-code' - must be 'enabled'"
+                                        + " or 'disabled'");
+                        return -1;
+                    }
+                }
+                case "get-country-code": {
+                    pw.println("Wifi Country Code = "
+                            + mWifiCountryCode.getCountryCode());
+                    return 0;
+                }
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -214,6 +280,25 @@
         return 0;
     }
 
+    private boolean isApChannelMHzValid(int apChannelMHz) {
+        int[] allowed2gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+        int[] allowed5gFreq = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
+        int[] allowed5gDfsFreq =
+            mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
+        if (allowed2gFreq == null) {
+            allowed2gFreq = new int[0];
+        }
+        if (allowed5gFreq == null) {
+            allowed5gFreq = new int[0];
+        }
+        if (allowed5gDfsFreq == null) {
+            allowed5gDfsFreq = new int[0];
+        }
+        return (Arrays.binarySearch(allowed2gFreq, apChannelMHz) >= 0
+                || Arrays.binarySearch(allowed5gFreq, apChannelMHz) >= 0
+                || Arrays.binarySearch(allowed5gDfsFreq, apChannelMHz) >= 0);
+    }
+
     private void checkRootPermission() {
         final int uid = Binder.getCallingUid();
         if (uid == 0) {
@@ -252,6 +337,13 @@
         pw.println("    Clears the deleted ephemeral networks list.");
         pw.println("  send-link-probe");
         pw.println("    Manually triggers a link probe.");
+        pw.println("  force-softap-channel enabled <int> | disabled");
+        pw.println("    Sets whether soft AP channel is forced to <int> MHz");
+        pw.println("    or left for normal   operation.");
+        pw.println("  force-country-code enabled <two-letter code> | disabled ");
+        pw.println("    Sets country code to <two-letter code> or left for normal value");
+        pw.println("  get-country-code");
+        pw.println("    Gets country code as a two-letter string");
         pw.println();
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java
index 8023bff..e780c10 100644
--- a/service/java/com/android/server/wifi/WifiVendorHal.java
+++ b/service/java/com/android/server/wifi/WifiVendorHal.java
@@ -1072,7 +1072,7 @@
     /**
      * Translation table used by getSupportedFeatureSet for translating IWifiChip caps for V1.1
      */
-    private static final int[][] sChipFeatureCapabilityTranslation = {
+    private static final long[][] sChipFeatureCapabilityTranslation = {
             {WifiManager.WIFI_FEATURE_TX_POWER_LIMIT,
                     android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT
             },
@@ -1139,7 +1139,7 @@
     /**
      * Translation table used by getSupportedFeatureSet for translating IWifiStaIface caps
      */
-    private static final int[][] sStaFeatureCapabilityTranslation = {
+    private static final long[][] sStaFeatureCapabilityTranslation = {
             {WifiManager.WIFI_FEATURE_INFRA_5G,
                     IWifiStaIface.StaIfaceCapabilityMask.STA_5G
             },
@@ -1188,8 +1188,8 @@
      * @return bitmask defined by WifiManager.WIFI_FEATURE_*
      */
     @VisibleForTesting
-    int wifiFeatureMaskFromStaCapabilities(int capabilities) {
-        int features = 0;
+    long wifiFeatureMaskFromStaCapabilities(int capabilities) {
+        long features = 0;
         for (int i = 0; i < sStaFeatureCapabilityTranslation.length; i++) {
             if ((capabilities & sStaFeatureCapabilityTranslation[i][1]) != 0) {
                 features |= sStaFeatureCapabilityTranslation[i][0];
@@ -2884,6 +2884,7 @@
                 mLog.e("Unexpected number of iface info in list " + numIfacesOnEachRadio);
                 return;
             }
+            Runnable runnable = null;
             // 2 ifaces simultaneous on 2 radios.
             if (radioModeInfoList.size() == 2 && numIfacesOnEachRadio == 1) {
                 // Iface on radio0 should be different from the iface on radio1 for DBS & SBS.
@@ -2892,22 +2893,31 @@
                     return;
                 }
                 if (radioModeInfo0.bandInfo != radioModeInfo1.bandInfo) {
-                    handler.onDbs();
+                    runnable = () -> {
+                        handler.onDbs();
+                    };
                 } else {
-                    handler.onSbs(radioModeInfo0.bandInfo);
+                    runnable = () -> {
+                        handler.onSbs(radioModeInfo0.bandInfo);
+                    };
                 }
             // 2 ifaces time sharing on 1 radio.
             } else if (radioModeInfoList.size() == 1 && numIfacesOnEachRadio == 2) {
                 IfaceInfo ifaceInfo0 = radioModeInfo0.ifaceInfos.get(0);
                 IfaceInfo ifaceInfo1 = radioModeInfo0.ifaceInfos.get(1);
                 if (ifaceInfo0.channel != ifaceInfo1.channel) {
-                    handler.onMcc(radioModeInfo0.bandInfo);
+                    runnable = () -> {
+                        handler.onMcc(radioModeInfo0.bandInfo);
+                    };
                 } else {
-                    handler.onScc(radioModeInfo0.bandInfo);
+                    runnable = () -> {
+                        handler.onScc(radioModeInfo0.bandInfo);
+                    };
                 }
             } else {
                 // Not concurrency scenario, uninteresting...
             }
+            if (runnable != null) mHalEventHandler.post(runnable);
         }
     }
 
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index 1ac73ae..5832ee8 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -1286,7 +1286,7 @@
             // Note: checks are done on the manager. This is a backup for apps which bypass the
             // check.
             if (!allowNdpResponderFromAnyOverride && !wifiPermissionsUtil.isTargetSdkLessThan(
-                    client.getCallingPackage(), Build.VERSION_CODES.P)) {
+                    client.getCallingPackage(), Build.VERSION_CODES.P, uid)) {
                 if (ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB
                         && ns.type != WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB) {
                     Log.e(TAG, "processNetworkSpecifier: networkSpecifier=" + ns
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPMatcher.java b/service/java/com/android/server/wifi/hotspot2/ANQPMatcher.java
index d95ab38..69f98b0 100644
--- a/service/java/com/android/server/wifi/hotspot2/ANQPMatcher.java
+++ b/service/java/com/android/server/wifi/hotspot2/ANQPMatcher.java
@@ -75,10 +75,11 @@
      *
      * @param element The Roaming Consortium ANQP element
      * @param providerOIs The roaming consortium OIs of the provider
+     * @param matchAll Indicates if a match with all OIs must be done
      * @return true if a match is found
      */
     public static boolean matchRoamingConsortium(RoamingConsortiumElement element,
-            long[] providerOIs) {
+            long[] providerOIs, boolean matchAll) {
         if (element == null) {
             return false;
         }
@@ -88,10 +89,14 @@
         List<Long> rcOIs = element.getOIs();
         for (long oi : providerOIs) {
             if (rcOIs.contains(oi)) {
-                return true;
+                if (!matchAll) {
+                    return true;
+                }
+            } else if (matchAll) {
+                return false;
             }
         }
-        return false;
+        return matchAll;
     }
 
     /**
@@ -100,27 +105,19 @@
      *
      * @param element The NAI Realm ANQP element
      * @param realm The realm of the provider's credential
-     * @param eapMethodID The EAP Method ID of the provider's credential
-     * @param authParam The authentication parameter of the provider's credential
      * @return an integer indicating the match status
      */
-    public static int matchNAIRealm(NAIRealmElement element, String realm, int eapMethodID,
-            AuthParam authParam) {
+    public static boolean matchNAIRealm(NAIRealmElement element, String realm) {
         if (element == null || element.getRealmDataList().isEmpty()) {
-            return AuthMatch.INDETERMINATE;
+            return false;
         }
 
-        int bestMatch = AuthMatch.NONE;
         for (NAIRealmData realmData : element.getRealmDataList()) {
-            int match = matchNAIRealmData(realmData, realm, eapMethodID, authParam);
-            if (match > bestMatch) {
-                bestMatch = match;
-                if (bestMatch == AuthMatch.EXACT) {
-                    break;
-                }
+            if (matchNAIRealmData(realmData, realm)) {
+                return true;
             }
         }
-        return bestMatch;
+        return false;
     }
 
     /**
@@ -172,42 +169,16 @@
      *
      * @param realmData The NAI Realm data
      * @param realm The realm of the provider's credential
-     * @param eapMethodID The EAP Method ID of the provider's credential
-     * @param authParam The authentication parameter of the provider's credential
-     * @return an integer indicating the match status
+     * @return true if a match is found
      */
-    private static int matchNAIRealmData(NAIRealmData realmData, String realm, int eapMethodID,
-            AuthParam authParam) {
+    private static boolean matchNAIRealmData(NAIRealmData realmData, String realm) {
         // Check for realm domain name match.
-        int realmMatch = AuthMatch.NONE;
         for (String realmStr : realmData.getRealms()) {
             if (DomainMatcher.arg2SubdomainOfArg1(realm, realmStr)) {
-                realmMatch = AuthMatch.REALM;
-                break;
+                return true;
             }
         }
-
-        if (realmData.getEAPMethods().isEmpty()) {
-            return realmMatch;
-        }
-
-        // Check for EAP method match.
-        int eapMethodMatch = AuthMatch.NONE;
-        for (EAPMethod eapMethod : realmData.getEAPMethods()) {
-            eapMethodMatch = matchEAPMethod(eapMethod, eapMethodID, authParam);
-            if (eapMethodMatch != AuthMatch.NONE) {
-                break;
-            }
-        }
-
-        if (eapMethodMatch == AuthMatch.NONE) {
-            return AuthMatch.NONE;
-        }
-
-        if (realmMatch == AuthMatch.NONE) {
-            return eapMethodMatch;
-        }
-        return realmMatch | eapMethodMatch;
+        return false;
     }
 
     private static int getEapMethodForNAIRealmWithCarrier(String realm,
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
index c748ca1..9256093 100644
--- a/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
+++ b/service/java/com/android/server/wifi/hotspot2/OsuServerConnection.java
@@ -43,6 +43,7 @@
 import java.io.InputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
+import java.net.URLConnection;
 import java.security.KeyManagementException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -163,7 +164,7 @@
      */
     public boolean connect(@NonNull URL url, @NonNull Network network) {
         if (url == null) {
-            Log.e(TAG, "url is null");
+            Log.e(TAG, "URL is null");
             return false;
         }
         if (network == null) {
@@ -171,6 +172,14 @@
             return false;
         }
 
+        String protocol = url.getProtocol();
+        // According to section 7.5.1 OSU operational requirements, in HS2.0 R3 specification,
+        // the URL must be HTTPS. Enforce it here.
+        if (!TextUtils.equals(protocol, "https")) {
+            Log.e(TAG, "OSU server URL must be HTTPS");
+            return false;
+        }
+
         mHandler.post(() -> performTlsConnection(url, network));
         return true;
     }
@@ -179,27 +188,25 @@
      * Validates the service provider by comparing its identities found in OSU Server cert
      * to the friendlyName obtained from ANQP exchange that is displayed to the user.
      *
-     * @param locale       a {@link Locale} object used for matching the friendly name in
-     *                     subjectAltName section of the certificate along with
-     *                     {@param friendlyName}.
-     * @param friendlyName a string of the friendly name used for finding the same name in
-     *                     subjectAltName section of the certificate.
+     * @param friendlyNames the friendly names used for finding the same name in
+     *                     subjectAltName section of the certificate, which is a map of language
+     *                     codes from ISO-639 and names.
      * @return boolean true if friendlyName shows up as one of the identities in the cert
      */
-    public boolean validateProvider(Locale locale,
-            String friendlyName) {
+    public boolean validateProvider(
+            Map<String, String> friendlyNames) {
 
-        if (locale == null || TextUtils.isEmpty(friendlyName)) {
+        if (friendlyNames.size() == 0) {
             return false;
         }
 
         for (Pair<Locale, String> identity : ServiceProviderVerifier.getProviderNames(
                 mTrustManager.getProviderCert())) {
-            if (identity.first == null) continue;
+            if (identity.first == null || TextUtils.isEmpty(identity.second)) continue;
 
             // Compare the language code for ISO-639.
-            if (identity.first.getISO3Language().equals(locale.getISO3Language()) &&
-                    TextUtils.equals(identity.second, friendlyName)) {
+            if (TextUtils.equals(identity.second,
+                    friendlyNames.get(identity.first.getISO3Language()))) {
                 if (mVerboseLoggingEnabled) {
                     Log.v(TAG, "OSU certificate is valid for "
                             + identity.first.getISO3Language() + "/" + identity.second);
@@ -271,13 +278,37 @@
         mNetwork = network;
         mUrl = url;
 
-        HttpsURLConnection urlConnection;
+        URLConnection urlConnection;
+        HttpsURLConnection httpsURLConnection;
+
         try {
-            urlConnection = (HttpsURLConnection) mNetwork.openConnection(mUrl);
-            urlConnection.setSSLSocketFactory(mSocketFactory);
-            urlConnection.setConnectTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS);
-            urlConnection.setReadTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS);
-            urlConnection.connect();
+            urlConnection = mNetwork.openConnection(mUrl);
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to establish a URL connection: " + e);
+            if (mOsuServerCallbacks != null) {
+                mOsuServerCallbacks.onServerConnectionStatus(
+                        mOsuServerCallbacks.getSessionId(),
+                        false);
+            }
+            return;
+        }
+
+        if (urlConnection instanceof HttpsURLConnection) {
+            httpsURLConnection = (HttpsURLConnection) urlConnection;
+        } else {
+            Log.e(TAG, "Invalid URL connection");
+            if (mOsuServerCallbacks != null) {
+                mOsuServerCallbacks.onServerConnectionStatus(mOsuServerCallbacks.getSessionId(),
+                        false);
+            }
+            return;
+        }
+
+        try {
+            httpsURLConnection.setSSLSocketFactory(mSocketFactory);
+            httpsURLConnection.setConnectTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS);
+            httpsURLConnection.setReadTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS);
+            httpsURLConnection.connect();
         } catch (IOException e) {
             Log.e(TAG, "Unable to establish a URL connection: " + e);
             if (mOsuServerCallbacks != null) {
@@ -286,7 +317,7 @@
             }
             return;
         }
-        mUrlConnection = urlConnection;
+        mUrlConnection = httpsURLConnection;
         if (mOsuServerCallbacks != null) {
             mOsuServerCallbacks.onServerConnectionStatus(mOsuServerCallbacks.getSessionId(), true);
         }
@@ -572,9 +603,15 @@
                         (SSLSocket) null);
                 certsValid = true;
             } catch (CertificateException e) {
-                Log.e(TAG, "Unable to validate certs " + e);
-                if (mVerboseLoggingEnabled) {
-                    e.printStackTrace();
+                Log.e(TAG, "Certificate validation failure: " + e);
+                int i = 0;
+                for (X509Certificate cert : chain) {
+                    // Provide some more details about the invalid certificate
+                    Log.e(TAG, "Cert " + i + " details: " + cert.getSubjectDN());
+                    Log.e(TAG, "Not before: " + cert.getNotBefore() + ", not after: "
+                            + cert.getNotAfter());
+                    Log.e(TAG, "Cert " + i + " issuer: " + cert.getIssuerDN());
+                    i++;
                 }
             }
             if (mOsuServerCallbacks != null) {
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java b/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java
index 419ea79..7f5a6b4 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreData.java
@@ -16,7 +16,10 @@
 
 package com.android.server.wifi.hotspot2;
 
+import android.annotation.Nullable;
+
 import com.android.server.wifi.WifiConfigStore;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -72,13 +75,16 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         serializeShareData(out);
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java b/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java
index 0114cfb..123cf89 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointConfigUserStoreData.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.hotspot2;
 
+import android.annotation.Nullable;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.text.TextUtils;
 
@@ -23,6 +24,7 @@
 import com.android.server.wifi.SIMAccessor;
 import com.android.server.wifi.WifiConfigStore;
 import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -103,13 +105,16 @@
     }
 
     @Override
-    public void serializeData(XmlSerializer out)
+    public void serializeData(XmlSerializer out,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         serializeUserData(out);
     }
 
     @Override
-    public void deserializeData(XmlPullParser in, int outerTagDepth)
+    public void deserializeData(XmlPullParser in, int outerTagDepth,
+            @WifiConfigStore.Version int version,
+            @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
             throws XmlPullParserException, IOException {
         // Ignore empty reads.
         if (in == null) {
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
index bdb323b..0666943 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -181,6 +181,7 @@
         public void setProviders(List<PasspointProvider> providers) {
             mProviders.clear();
             for (PasspointProvider provider : providers) {
+                provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
                 mProviders.put(provider.getConfig().getHomeSp().getFqdn(), provider);
                 if (provider.getPackageName() != null) {
                     startTrackingAppOpsChange(provider.getPackageName(),
@@ -252,7 +253,7 @@
         for (Map.Entry<String, PasspointProvider> entry : getPasspointProviderWithPackage(
                 packageName).entrySet()) {
             String fqdn = entry.getValue().getConfig().getHomeSp().getFqdn();
-            removeProvider(Process.WIFI_UID, fqdn);
+            removeProvider(Process.WIFI_UID /* ignored */, true, fqdn);
             disconnectIfPasspointNetwork(fqdn);
         }
     }
@@ -277,7 +278,7 @@
         AppOpsChangedListener appOpsChangedListener = mAppOpsChangedListenerPerApp.remove(
                 packageName);
         if (appOpsChangedListener == null) {
-            Log.wtf(TAG, "No app ops listener found for " + packageName);
+            Log.i(TAG, "No app ops listener found for " + packageName);
             return;
         }
         mAppOps.stopWatchingMode(appOpsChangedListener);
@@ -343,6 +344,9 @@
     public void enableVerboseLogging(int verbose) {
         mVerboseLoggingEnabled = (verbose > 0) ? true : false;
         mPasspointProvisioner.enableVerboseLogging(verbose);
+        for (PasspointProvider provider : mProviders.values()) {
+            provider.enableVerboseLogging(verbose);
+        }
     }
 
     /**
@@ -403,6 +407,7 @@
             mProviders.get(config.getHomeSp().getFqdn()).uninstallCertsAndKeys();
             mProviders.remove(config.getHomeSp().getFqdn());
         }
+        newProvider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
         mProviders.put(config.getHomeSp().getFqdn(), newProvider);
         mWifiConfigManager.saveToStore(true /* forceWrite */);
         if (newProvider.getPackageName() != null) {
@@ -629,22 +634,29 @@
      * Remove a Passpoint provider identified by the given FQDN.
      *
      * @param callingUid Calling UID.
+     * @param privileged Whether the caller is a privileged entity
      * @param fqdn The FQDN of the provider to remove
      * @return true if a provider is removed, false otherwise
      */
-    public boolean removeProvider(int callingUid, String fqdn) {
+    public boolean removeProvider(int callingUid, boolean privileged, String fqdn) {
         mWifiMetrics.incrementNumPasspointProviderUninstallation();
         String packageName;
-        if (!mProviders.containsKey(fqdn)) {
+        PasspointProvider provider = mProviders.get(fqdn);
+        if (provider == null) {
             Log.e(TAG, "Config doesn't exist");
             return false;
         }
+        if (!privileged && callingUid != provider.getCreatorUid()) {
+            Log.e(TAG, "UID " + callingUid + " cannot remove profile created by "
+                    + provider.getCreatorUid());
+            return false;
+        }
         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(callingUid)) {
             Log.e(TAG, "UID " + callingUid + " not visible to the current user");
             return false;
         }
-        mProviders.get(fqdn).uninstallCertsAndKeys();
-        packageName = mProviders.get(fqdn).getPackageName();
+        provider.uninstallCertsAndKeys();
+        packageName = provider.getPackageName();
         mProviders.remove(fqdn);
         mWifiConfigManager.saveToStore(true /* forceWrite */);
 
@@ -677,12 +689,17 @@
      *
      * An empty list will be returned when no provider is installed.
      *
+     * @param callingUid Calling UID.
+     * @param privileged Whether the caller is a privileged entity
      * @return A list of {@link PasspointConfiguration}
      */
-    public List<PasspointConfiguration> getProviderConfigs() {
+    public List<PasspointConfiguration> getProviderConfigs(int callingUid, boolean privileged) {
         List<PasspointConfiguration> configs = new ArrayList<>();
         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
-            configs.add(entry.getValue().getConfig());
+            PasspointProvider provider = entry.getValue();
+            if (privileged || callingUid == provider.getCreatorUid()) {
+                configs.add(provider.getConfig());
+            }
         }
         return configs;
     }
@@ -982,7 +999,8 @@
     public Map<OsuProvider, PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(
             List<OsuProvider> osuProviders) {
         Map<OsuProvider, PasspointConfiguration> matchingPasspointConfigs = new HashMap<>();
-        List<PasspointConfiguration> passpointConfigurations = getProviderConfigs();
+        List<PasspointConfiguration> passpointConfigurations =
+                getProviderConfigs(Process.WIFI_UID /* ignored */, true);
 
         for (OsuProvider osuProvider : osuProviders) {
             Map<String, String> friendlyNamesForOsuProvider = osuProvider.getFriendlyNameList();
@@ -1126,6 +1144,7 @@
                 Arrays.asList(enterpriseConfig.getCaCertificateAlias()),
                 enterpriseConfig.getClientCertificateAlias(),
                 enterpriseConfig.getClientCertificateAlias(), null, false, false);
+        provider.enableVerboseLogging(mVerboseLoggingEnabled ? 1 : 0);
         mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
         return true;
     }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
index ec8a009..148af39 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointNetworkEvaluator.java
@@ -231,24 +231,20 @@
         if (existingNetwork != null) {
             WifiConfiguration.NetworkSelectionStatus status =
                     existingNetwork.getNetworkSelectionStatus();
-            if (!status.isNetworkEnabled()) {
-                boolean isSuccess = mWifiConfigManager.tryEnableNetwork(existingNetwork.networkId);
-                if (isSuccess) {
-                    return existingNetwork;
-                }
+            if (!status.isNetworkEnabled()
+                    && !mWifiConfigManager.tryEnableNetwork(existingNetwork.networkId)) {
                 localLog("Current configuration for the Passpoint AP " + config.SSID
                         + " is disabled, skip this candidate");
                 return null;
             }
-            return existingNetwork;
         }
 
-        // Add the newly created WifiConfiguration to WifiConfigManager.
+        // Add or update with the newly created WifiConfiguration to WifiConfigManager.
         NetworkUpdateResult result =
                 mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
         if (!result.isSuccess()) {
             localLog("Failed to add passpoint network");
-            return null;
+            return existingNetwork;
         }
         mWifiConfigManager.enableNetwork(result.getNetworkId(), false, Process.WIFI_UID);
         mWifiConfigManager.setNetworkCandidateScanResult(result.getNetworkId(),
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
index ca9814a..8db71d3 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
@@ -95,6 +95,7 @@
 
     private boolean mHasEverConnected;
     private boolean mIsShared;
+    private boolean mVerboseLoggingEnabled;
 
     /**
      * This is a flag to indicate if the Provider is created temporarily.
@@ -327,39 +328,53 @@
      * Return the matching status with the given AP, based on the ANQP elements from the AP.
      *
      * @param anqpElements ANQP elements from the AP
-     * @param roamingConsortium Roaming Consortium information element from the AP
+     * @param roamingConsortiumFromAp Roaming Consortium information element from the AP
      * @return {@link PasspointMatch}
      */
     public PasspointMatch match(Map<ANQPElementType, ANQPElement> anqpElements,
-            RoamingConsortium roamingConsortium) {
-        PasspointMatch providerMatch = matchProviderExceptFor3GPP(anqpElements, roamingConsortium);
+            RoamingConsortium roamingConsortiumFromAp) {
+        // Match FQDN for Home provider or RCOI(s) for Roaming provider
+        // For SIM credential, the FQDN is in the format of wlan.mnc*.mcc*.3gppnetwork.org
+        PasspointMatch providerMatch = matchFqdnAndRcoi(anqpElements, roamingConsortiumFromAp);
 
         // 3GPP Network matching.
         if (providerMatch == PasspointMatch.None && ANQPMatcher.matchThreeGPPNetwork(
                 (ThreeGPPNetworkElement) anqpElements.get(ANQPElementType.ANQP3GPPNetwork),
                 mImsiParameter, mMatchingSIMImsiList)) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Final RoamingProvider match with "
+                        + anqpElements.get(ANQPElementType.ANQP3GPPNetwork));
+            }
             return PasspointMatch.RoamingProvider;
         }
 
-        // Perform authentication match against the NAI Realm.
-        int authMatch = ANQPMatcher.matchNAIRealm(
+        // Perform NAI Realm matching
+        boolean realmMatch = ANQPMatcher.matchNAIRealm(
                 (NAIRealmElement) anqpElements.get(ANQPElementType.ANQPNAIRealm),
-                mConfig.getCredential().getRealm(), mEAPMethodID, mAuthParam);
-
-        // In case of Auth mismatch, demote provider match.
-        if (authMatch == AuthMatch.NONE) {
-            return PasspointMatch.None;
-        }
+                mConfig.getCredential().getRealm());
 
         // In case of no realm match, return provider match as is.
-        if ((authMatch & AuthMatch.REALM) == 0) {
+        if (!realmMatch) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "No NAI realm match, final match: " + providerMatch);
+            }
             return providerMatch;
         }
 
-        // Promote the provider match to roaming provider if provider match is not found, but NAI
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "NAI realm match with " + mConfig.getCredential().getRealm());
+        }
+
+        // Promote the provider match to RoamingProvider if provider match is not found, but NAI
         // realm is matched.
-        return providerMatch == PasspointMatch.None ? PasspointMatch.RoamingProvider
-                : providerMatch;
+        if (providerMatch == PasspointMatch.None) {
+            providerMatch = PasspointMatch.RoamingProvider;
+        }
+
+        if (mVerboseLoggingEnabled) {
+            Log.d(TAG, "Final match: " + providerMatch);
+        }
+        return providerMatch;
     }
 
     /**
@@ -570,42 +585,129 @@
     }
 
     /**
+     * Match given OIs to the Roaming Consortium OIs
+     *
+     * @param providerOis Provider OIs to match against
+     * @param roamingConsortiumElement RCOIs in the ANQP element
+     * @param roamingConsortiumFromAp RCOIs in the AP scan results
+     * @param matchAll Indicates if all providerOis must match the RCOIs elements
+     * @return {@code true} if there is a match, {@code false} otherwise.
+     */
+    private boolean matchOis(long[] providerOis,
+            RoamingConsortiumElement roamingConsortiumElement,
+            RoamingConsortium roamingConsortiumFromAp,
+            boolean matchAll) {
+
+
+        // ANQP Roaming Consortium OI matching.
+        if (ANQPMatcher.matchRoamingConsortium(roamingConsortiumElement, providerOis, matchAll)) {
+            if (mVerboseLoggingEnabled) {
+                Log.e(TAG, "ANQP RCOI match " + roamingConsortiumElement);
+            }
+            return true;
+        }
+
+        // AP Roaming Consortium OI matching.
+        long[] apRoamingConsortiums = roamingConsortiumFromAp.getRoamingConsortiums();
+        if (apRoamingConsortiums == null || providerOis == null) {
+            return false;
+        }
+        // Roaming Consortium OI information element matching.
+        for (long apOi: apRoamingConsortiums) {
+            boolean matched = false;
+            for (long providerOi: providerOis) {
+                if (apOi == providerOi) {
+                    if (mVerboseLoggingEnabled) {
+                        Log.e(TAG, "AP RCOI match: " + apOi);
+                    }
+                    if (!matchAll) {
+                        return true;
+                    } else {
+                        matched = true;
+                        break;
+                    }
+                }
+            }
+            if (matchAll && !matched) {
+                return false;
+            }
+        }
+        return matchAll;
+    }
+
+    /**
      * Perform a provider match based on the given ANQP elements except for matching 3GPP Network.
      *
      * @param anqpElements List of ANQP elements
-     * @param roamingConsortium Roaming Consortium information element from the AP
+     * @param roamingConsortiumFromAp Roaming Consortium information element from the AP
      * @return {@link PasspointMatch}
      */
-    private PasspointMatch matchProviderExceptFor3GPP(
+    private PasspointMatch matchFqdnAndRcoi(
             Map<ANQPElementType, ANQPElement> anqpElements,
-            RoamingConsortium roamingConsortium) {
+            RoamingConsortium roamingConsortiumFromAp) {
         // Domain name matching.
         if (ANQPMatcher.matchDomainName(
                 (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName),
                 mConfig.getHomeSp().getFqdn(), mImsiParameter, mMatchingSIMImsiList)) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Domain name " + mConfig.getHomeSp().getFqdn()
+                        + " match: HomeProvider");
+            }
             return PasspointMatch.HomeProvider;
         }
 
-        // ANQP Roaming Consortium OI matching.
-        long[] providerOIs = mConfig.getHomeSp().getRoamingConsortiumOis();
-        if (ANQPMatcher.matchRoamingConsortium(
-                (RoamingConsortiumElement) anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
-                providerOIs)) {
-            return PasspointMatch.RoamingProvider;
-        }
-
-        long[] roamingConsortiums = roamingConsortium.getRoamingConsortiums();
-        // Roaming Consortium OI information element matching.
-        if (roamingConsortiums != null && providerOIs != null) {
-            for (long sta_oi: roamingConsortiums) {
-                for (long ap_oi: providerOIs) {
-                    if (sta_oi == ap_oi) {
-                        return PasspointMatch.RoamingProvider;
+        // Other Home Partners matching.
+        if (mConfig.getHomeSp().getOtherHomePartners() != null) {
+            for (String otherHomePartner : mConfig.getHomeSp().getOtherHomePartners()) {
+                if (ANQPMatcher.matchDomainName(
+                        (DomainNameElement) anqpElements.get(ANQPElementType.ANQPDomName),
+                        otherHomePartner, null, null)) {
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "Other Home Partner " + otherHomePartner
+                                + " match: HomeProvider");
                     }
+                    return PasspointMatch.HomeProvider;
                 }
             }
         }
 
+        // HomeOI matching
+        if (mConfig.getHomeSp().getMatchAllOis() != null) {
+            // Ensure that every HomeOI whose corresponding HomeOIRequired value is true shall match
+            // an OI in the Roaming Consortium advertised by the hotspot operator.
+            if (matchOis(mConfig.getHomeSp().getMatchAllOis(), (RoamingConsortiumElement)
+                            anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
+                    roamingConsortiumFromAp, true)) {
+                if (mVerboseLoggingEnabled) {
+                    Log.e(TAG, "All HomeOI RCOI match: HomeProvider");
+                }
+                return PasspointMatch.HomeProvider;
+            }
+        } else if (mConfig.getHomeSp().getMatchAnyOis() != null) {
+            // Ensure that any HomeOI whose corresponding HomeOIRequired value is false shall match
+            // an OI in the Roaming Consortium advertised by the hotspot operator.
+            if (matchOis(mConfig.getHomeSp().getMatchAnyOis(), (RoamingConsortiumElement)
+                            anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
+                    roamingConsortiumFromAp, false)) {
+                if (mVerboseLoggingEnabled) {
+                    Log.e(TAG, "Any HomeOI RCOI match: HomeProvider");
+                }
+                return PasspointMatch.HomeProvider;
+            }
+        }
+
+        // Roaming Consortium OI matching.
+        if (matchOis(mConfig.getHomeSp().getRoamingConsortiumOis(), (RoamingConsortiumElement)
+                        anqpElements.get(ANQPElementType.ANQPRoamingConsortium),
+                roamingConsortiumFromAp, false)) {
+            if (mVerboseLoggingEnabled) {
+                Log.e(TAG, "ANQP RCOI match: RoamingProvider");
+            }
+            return PasspointMatch.RoamingProvider;
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.e(TAG, "No domain name or RCOI match");
+        }
         return PasspointMatch.None;
     }
 
@@ -768,4 +870,12 @@
         simCredential.setEapType(eapType);
         return simCredential;
     }
+
+    /**
+     * Enable verbose logging
+     * @param verbose more than 0 enables verbose logging
+     */
+    public void enableVerboseLogging(int verbose) {
+        mVerboseLoggingEnabled = (verbose > 0) ? true : false;
+    }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
index bdd035f..137d9fa 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -366,7 +366,7 @@
                 return;
             }
             if (!mOsuServerConnection.validateProvider(
-                    Locale.getDefault(), mOsuProvider.getFriendlyName())) {
+                    mOsuProvider.getFriendlyNameList())) {
                 Log.e(TAG,
                         "OSU Server certificate does not have the one matched with the selected "
                                 + "Service Name: "
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index 3f37456..055fa72 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -100,6 +100,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -148,6 +149,11 @@
             android.Manifest.permission.ACCESS_WIFI_STATE
     };
 
+    // Maximum number of bytes allowed for a network name, i.e. SSID.
+    private static final int MAX_NETWORK_NAME_BYTES = 32;
+    // Minimum number of bytes for a network name, i.e. DIRECT-xy.
+    private static final int MIN_NETWORK_NAME_BYTES = 9;
+
     // Two minutes comes from the wpa_supplicant setting
     private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000;
     private static int sGroupCreatingTimeoutIndex = 0;
@@ -3255,6 +3261,23 @@
         }
 
         /**
+         * Check the network name complies standard SSID naming rules.
+         *
+         * The network name of a group is also the broadcasting SSID,
+         * as a result, the network name must complies standard SSID naming
+         * rules.
+         */
+        private boolean isValidNetworkName(String networkName) {
+            if (TextUtils.isEmpty(networkName)) return false;
+
+            byte[] ssidBytes = networkName.getBytes(StandardCharsets.UTF_8);
+            if (ssidBytes.length < MIN_NETWORK_NAME_BYTES) return false;
+            if (ssidBytes.length > MAX_NETWORK_NAME_BYTES) return false;
+
+            return true;
+        }
+
+        /**
          * A config is valid as a group if it has network name and passphrase.
          * Supplicant can construct a group on the fly for creating a group with specified config
          * or join a group without negotiation and WPS.
@@ -3264,7 +3287,7 @@
         private boolean isConfigValidAsGroup(WifiP2pConfig config) {
             if (config == null) return false;
             if (TextUtils.isEmpty(config.deviceAddress)) return false;
-            if (!TextUtils.isEmpty(config.networkName)
+            if (isValidNetworkName(config.networkName)
                     && !TextUtils.isEmpty(config.passphrase)) {
                 return true;
             }
diff --git a/service/java/com/android/server/wifi/rtt/RttNative.java b/service/java/com/android/server/wifi/rtt/RttNative.java
index eaf9470..ffbf5be 100644
--- a/service/java/com/android/server/wifi/rtt/RttNative.java
+++ b/service/java/com/android/server/wifi/rtt/RttNative.java
@@ -311,6 +311,7 @@
                 config.channel.centerFreq1 = responder.centerFreq1;
                 config.bw = halRttChannelBandwidthFromResponderChannelWidth(responder.channelWidth);
                 config.preamble = halRttPreambleFromResponderPreamble(responder.preamble);
+                validateBwAndPreambleCombination(config.bw, config.preamble);
 
                 if (config.peer == RttPeerType.NAN) {
                     config.mustRequestLci = false;
@@ -349,6 +350,20 @@
         return rttConfigs;
     }
 
+    private static void validateBwAndPreambleCombination(int bw, int preamble) {
+        if (bw <= RttBw.BW_20MHZ) {
+            return;
+        }
+        if (bw == RttBw.BW_40MHZ && preamble >= RttPreamble.HT) {
+            return;
+        }
+        if (bw >= RttBw.BW_80MHZ && preamble >= RttPreamble.VHT) {
+            return;
+        }
+        throw new IllegalArgumentException(
+                "bw and preamble combination is invalid, bw: " + bw + " preamble: " + preamble);
+    }
+
     private static int halRttPeerTypeFromResponderType(int responderType) {
         switch (responderType) {
             case ResponderConfig.RESPONDER_AP:
diff --git a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
index 19ae154..d69ce8f 100644
--- a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -1178,16 +1178,11 @@
                                 "ResponderLocation: lci/lcr parser failed exception -- " + e);
                     }
                     // Clear LCI and LCR data if the location data should not be retransmitted,
-                    // has a retention expiration time, contains no useful data, or did not parse.
-                    if (responderLocation == null) {
+                    // has a retention expiration time, contains no useful data, or did not parse,
+                    // or the caller is not in a privileged context.
+                    if (responderLocation == null || !isCalledFromPrivilegedContext) {
                         lci = null;
                         lcr = null;
-                    } else if (!isCalledFromPrivilegedContext) {
-                        // clear the raw lci and lcr buffers and civic location data if the
-                        // caller is not in a privileged context.
-                        lci = null;
-                        lcr = null;
-                        responderLocation.setCivicLocationSubelementDefaults();
                     }
                     if (resultForRequest.successNumber <= 1
                             && resultForRequest.distanceSdInMm != 0) {
diff --git a/service/java/com/android/server/wifi/util/ApConfigUtil.java b/service/java/com/android/server/wifi/util/ApConfigUtil.java
index dfda45b..c3a4c0c 100644
--- a/service/java/com/android/server/wifi/util/ApConfigUtil.java
+++ b/service/java/com/android/server/wifi/util/ApConfigUtil.java
@@ -33,7 +33,7 @@
 
     public static final int DEFAULT_AP_BAND = WifiConfiguration.AP_BAND_2GHZ;
     public static final int DEFAULT_AP_CHANNEL = 6;
-
+    public static final int HIGHEST_2G_AP_CHANNEL = 14;
     /* Return code for updateConfiguration. */
     public static final int SUCCESS = 0;
     public static final int ERROR_NO_CHANNEL = 1;
diff --git a/service/java/com/android/server/wifi/util/DataIntegrityChecker.java b/service/java/com/android/server/wifi/util/DataIntegrityChecker.java
deleted file mode 100644
index 1f3c6b3..0000000
--- a/service/java/com/android/server/wifi/util/DataIntegrityChecker.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * 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 com.android.server.wifi.util;
-
-import android.annotation.NonNull;
-import android.os.SystemProperties;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.security.DigestException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.UnrecoverableEntryException;
-import java.security.cert.CertificateException;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-/**
- * Tools to provide integrity checking of byte arrays based on NIAP Common Criteria Protection
- * Profile <a href="https://www.niap-ccevs.org/MMO/PP/-417-/#FCS_STG_EXT.3.1">FCS_STG_EXT.3.1</a>.
- */
-public class DataIntegrityChecker {
-    private static final String TAG = "DataIntegrityChecker";
-
-    private static final String FILE_SUFFIX = ".encrypted-checksum";
-    private static final String ALIAS_SUFFIX = ".data-integrity-checker-key";
-    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
-    private static final String DIGEST_ALGORITHM = "SHA-256";
-    private static final int GCM_TAG_LENGTH = 128;
-    private static final String KEY_STORE = "AndroidKeyStore";
-
-    /**
-     * When KEYSTORE_FAILURE_RETURN_VALUE is true, all cryptographic operation failures will not
-     * enforce security and {@link #isOk(byte[])} always return true.
-     */
-    private static final boolean KEYSTORE_FAILURE_RETURN_VALUE = true;
-
-    private final File mIntegrityFile;
-
-    /**
-     * Construct a new integrity checker to update and check if/when a data file was altered
-     * outside expected conditions.
-     *
-     * @param integrityFilename The {@link File} path prefix for where the integrity data is stored.
-     *                          A file will be created in the name of integrityFile with the suffix
-     *                          {@link DataIntegrityChecker#FILE_SUFFIX} We recommend using the same
-     *                          path as the file for which integrity is performed on.
-     * @throws NullPointerException When integrity file is null or the empty string.
-     */
-    public DataIntegrityChecker(@NonNull String integrityFilename) {
-        if (TextUtils.isEmpty(integrityFilename)) {
-            throw new NullPointerException("integrityFilename must not be null or the empty "
-                    + "string");
-        }
-        mIntegrityFile = new File(integrityFilename + FILE_SUFFIX);
-    }
-
-    /**
-     * Computes a digest of a byte array, encrypt it, and store the result
-     *
-     * Call this method immediately before storing the byte array
-     *
-     * @param data The data desired to ensure integrity
-     */
-    public void update(byte[] data) {
-        if (data == null || data.length < 1) {
-            reportException(new Exception("No data to update"), "No data to update.");
-            return;
-        }
-        byte[] digest = getDigest(data);
-        if (digest == null || digest.length < 1) {
-            return;
-        }
-        String alias = mIntegrityFile.getName() + ALIAS_SUFFIX;
-        EncryptedData integrityData = encrypt(digest, alias);
-        if (integrityData != null) {
-            writeIntegrityData(integrityData, mIntegrityFile);
-        } else {
-            reportException(new Exception("integrityData null upon update"),
-                    "integrityData null upon update");
-        }
-    }
-
-    /**
-     * Check the integrity of a given byte array
-     *
-     * Call this method immediately before trusting the byte array. This method will return false
-     * when the byte array was altered since the last {@link #update(byte[])}
-     * call, when {@link #update(byte[])} has never been called, or if there is
-     * an underlying issue with the cryptographic functions or the key store.
-     *
-     * @param data The data to check if its been altered
-     * @throws DigestException The integrity mIntegrityFile cannot be read. Ensure
-     *      {@link #isOk(byte[])} is called after {@link #update(byte[])}. Otherwise, consider the
-     *      result vacuously true and immediately call {@link #update(byte[])}.
-     * @return true if the data was not altered since {@link #update(byte[])} was last called
-     */
-    public boolean isOk(byte[] data) throws DigestException {
-        if (data == null || data.length < 1) {
-            return KEYSTORE_FAILURE_RETURN_VALUE;
-        }
-        byte[] currentDigest = getDigest(data);
-        if (currentDigest == null || currentDigest.length < 1) {
-            return KEYSTORE_FAILURE_RETURN_VALUE;
-        }
-
-        EncryptedData encryptedData = null;
-
-        try {
-            encryptedData = readIntegrityData(mIntegrityFile);
-        } catch (IOException e) {
-            reportException(e, "readIntegrityData had an IO exception");
-            return KEYSTORE_FAILURE_RETURN_VALUE;
-        } catch (ClassNotFoundException e) {
-            reportException(e, "readIntegrityData could not find the class EncryptedData");
-            return KEYSTORE_FAILURE_RETURN_VALUE;
-        }
-
-        if (encryptedData == null) {
-            // File not found is not considered to be an error.
-            throw new DigestException("No stored digest is available to compare.");
-        }
-        byte[] storedDigest = decrypt(encryptedData);
-        if (storedDigest == null) {
-            return KEYSTORE_FAILURE_RETURN_VALUE;
-        }
-        return constantTimeEquals(storedDigest, currentDigest);
-    }
-
-    private byte[] getDigest(byte[] data) {
-        try {
-            return MessageDigest.getInstance(DIGEST_ALGORITHM).digest(data);
-        } catch (NoSuchAlgorithmException e) {
-            reportException(e, "getDigest could not find algorithm: " + DIGEST_ALGORITHM);
-            return null;
-        }
-    }
-
-    private EncryptedData encrypt(byte[] data, String keyAlias) {
-        EncryptedData encryptedData = null;
-        try {
-            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
-            SecretKey secretKeyReference = getOrCreateSecretKey(keyAlias);
-            if (secretKeyReference != null) {
-                cipher.init(Cipher.ENCRYPT_MODE, secretKeyReference);
-                encryptedData = new EncryptedData(cipher.doFinal(data), cipher.getIV(), keyAlias);
-            } else {
-                reportException(new Exception("secretKeyReference is null."),
-                        "secretKeyReference is null.");
-            }
-        } catch (NoSuchAlgorithmException e) {
-            reportException(e, "encrypt could not find the algorithm: " + CIPHER_ALGORITHM);
-        } catch (NoSuchPaddingException e) {
-            reportException(e, "encrypt had a padding exception");
-        } catch (InvalidKeyException e) {
-            reportException(e, "encrypt received an invalid key");
-        } catch (BadPaddingException e) {
-            reportException(e, "encrypt had a padding problem");
-        } catch (IllegalBlockSizeException e) {
-            reportException(e, "encrypt had an illegal block size");
-        }
-        return encryptedData;
-    }
-
-    private byte[] decrypt(EncryptedData encryptedData) {
-        byte[] decryptedData = null;
-        try {
-            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
-            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, encryptedData.getIv());
-            SecretKey secretKeyReference = getOrCreateSecretKey(encryptedData.getKeyAlias());
-            if (secretKeyReference != null) {
-                cipher.init(Cipher.DECRYPT_MODE, secretKeyReference, spec);
-                decryptedData = cipher.doFinal(encryptedData.getEncryptedData());
-            }
-        } catch (NoSuchAlgorithmException e) {
-            reportException(e, "decrypt could not find cipher algorithm " + CIPHER_ALGORITHM);
-        } catch (NoSuchPaddingException e) {
-            reportException(e, "decrypt could not find padding algorithm");
-        } catch (IllegalBlockSizeException e) {
-            reportException(e, "decrypt had a illegal block size");
-        } catch (BadPaddingException e) {
-            reportException(e, "decrypt had bad padding");
-        } catch (InvalidKeyException e) {
-            reportException(e, "decrypt had an invalid key");
-        } catch (InvalidAlgorithmParameterException e) {
-            reportException(e, "decrypt had an invalid algorithm parameter");
-        }
-        return decryptedData;
-    }
-
-    private SecretKey getOrCreateSecretKey(String keyAlias) {
-        SecretKey secretKey = null;
-        try {
-            KeyStore keyStore = KeyStore.getInstance(KEY_STORE);
-            keyStore.load(null);
-            if (keyStore.containsAlias(keyAlias)) { // The key exists in key store. Get the key.
-                KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore
-                        .getEntry(keyAlias, null);
-                if (secretKeyEntry != null) {
-                    secretKey = secretKeyEntry.getSecretKey();
-                } else {
-                    reportException(new Exception("keystore contains the alias and the secret key "
-                            + "entry was null"),
-                            "keystore contains the alias and the secret key entry was null");
-                }
-            } else { // The key does not exist in key store. Create the key and store it.
-                KeyGenerator keyGenerator = KeyGenerator
-                        .getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE);
-
-                KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(keyAlias,
-                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                        .build();
-
-                keyGenerator.init(keyGenParameterSpec);
-                secretKey = keyGenerator.generateKey();
-            }
-        } catch (CertificateException e) {
-            reportException(e, "getOrCreateSecretKey had a certificate exception.");
-        } catch (InvalidAlgorithmParameterException e) {
-            reportException(e, "getOrCreateSecretKey had an invalid algorithm parameter");
-        } catch (IOException e) {
-            reportException(e, "getOrCreateSecretKey had an IO exception.");
-        } catch (KeyStoreException e) {
-            reportException(e, "getOrCreateSecretKey cannot find the keystore: " + KEY_STORE);
-        } catch (NoSuchAlgorithmException e) {
-            reportException(e, "getOrCreateSecretKey cannot find algorithm");
-        } catch (NoSuchProviderException e) {
-            reportException(e, "getOrCreateSecretKey cannot find crypto provider");
-        } catch (UnrecoverableEntryException e) {
-            reportException(e, "getOrCreateSecretKey had an unrecoverable entry exception.");
-        }
-        return secretKey;
-    }
-
-    private void writeIntegrityData(EncryptedData encryptedData, File file) {
-        try (FileOutputStream fos = new FileOutputStream(file);
-             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
-            oos.writeObject(encryptedData);
-        } catch (FileNotFoundException e) {
-            reportException(e, "writeIntegrityData could not find the integrity file");
-        } catch (IOException e) {
-            reportException(e, "writeIntegrityData had an IO exception");
-        }
-    }
-
-    private EncryptedData readIntegrityData(File file) throws IOException, ClassNotFoundException  {
-        try (FileInputStream fis = new FileInputStream(file);
-             ObjectInputStream ois = new ObjectInputStream(fis)) {
-            return (EncryptedData) ois.readObject();
-        } catch (FileNotFoundException e) {
-            // File not found, this is not considered to be a real error. The file will be created
-            // by the system next time the data file is written. Note that it is not possible for
-            // non system user to delete or modify the file.
-            Log.w(TAG, "readIntegrityData could not find integrity file");
-        }
-        return null;
-    }
-
-    private boolean constantTimeEquals(byte[] a, byte[] b) {
-        if (a == null && b == null) {
-            return true;
-        }
-
-        if (a == null || b == null || a.length != b.length) {
-            return false;
-        }
-
-        byte differenceAccumulator = 0;
-        for (int i = 0; i < a.length; ++i) {
-            differenceAccumulator |= a[i] ^ b[i];
-        }
-        return (differenceAccumulator == 0);
-    }
-
-    /* TODO(b/128526030): Remove this error reporting code upon resolving the bug. */
-    private static final boolean REQUEST_BUG_REPORT = false;
-    private void reportException(Exception exception, String error) {
-        Log.wtf(TAG, "An irrecoverable key store error was encountered: " + error);
-        if (REQUEST_BUG_REPORT) {
-            SystemProperties.set("dumpstate.options", "bugreportwifi");
-            SystemProperties.set("ctl.start", "bugreport");
-        }
-    }
-}
diff --git a/service/java/com/android/server/wifi/util/EncryptedData.java b/service/java/com/android/server/wifi/util/EncryptedData.java
index 468f28e..baec204 100644
--- a/service/java/com/android/server/wifi/util/EncryptedData.java
+++ b/service/java/com/android/server/wifi/util/EncryptedData.java
@@ -16,22 +16,23 @@
 
 package com.android.server.wifi.util;
 
-import java.io.Serializable;
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
- * A class to store data created by {@link DataIntegrityChecker}.
+ * A class to store data created by {@link WifiConfigStoreEncryptionUtil}.
  */
-public class EncryptedData implements Serializable {
-    private static final long serialVersionUID = 1337L;
+public class EncryptedData {
+    private final byte[] mEncryptedData;
+    private final byte[] mIv;
 
-    private byte[] mEncryptedData;
-    private byte[] mIv;
-    private String mKeyAlias;
-
-    public EncryptedData(byte[] encryptedData, byte[] iv, String keyAlias) {
+    public EncryptedData(byte[] encryptedData, byte[] iv) {
+        Preconditions.checkNotNull(encryptedData);
+        Preconditions.checkNotNull(iv);
         mEncryptedData = encryptedData;
         mIv = iv;
-        mKeyAlias = keyAlias;
     }
 
     public byte[] getEncryptedData() {
@@ -42,7 +43,16 @@
         return mIv;
     }
 
-    public String getKeyAlias() {
-        return mKeyAlias;
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof EncryptedData)) return false;
+        EncryptedData otherEncryptedData = (EncryptedData) other;
+        return Arrays.equals(this.mEncryptedData, otherEncryptedData.mEncryptedData)
+                && Arrays.equals(this.mIv, otherEncryptedData.mIv);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(Arrays.hashCode(mEncryptedData), Arrays.hashCode(mIv));
     }
 }
diff --git a/service/java/com/android/server/wifi/util/ScanResultUtil.java b/service/java/com/android/server/wifi/util/ScanResultUtil.java
index 39e9d2c..b924838 100644
--- a/service/java/com/android/server/wifi/util/ScanResultUtil.java
+++ b/service/java/com/android/server/wifi/util/ScanResultUtil.java
@@ -104,10 +104,10 @@
 
     /**
      * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition
-     * network. This checks if the provided capabilities string contains PSK+SAE or not.
+     * network. This checks if the provided capabilities string contains both PSK and SAE or not.
      */
     public static boolean isScanResultForPskSaeTransitionNetwork(ScanResult scanResult) {
-        return scanResult.capabilities.contains("PSK+SAE");
+        return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE");
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/util/TelephonyUtil.java b/service/java/com/android/server/wifi/util/TelephonyUtil.java
index 4af40dd..3154df9 100644
--- a/service/java/com/android/server/wifi/util/TelephonyUtil.java
+++ b/service/java/com/android/server/wifi/util/TelephonyUtil.java
@@ -22,6 +22,7 @@
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
@@ -730,4 +731,43 @@
     public static boolean isSimPresent(@Nonnull SubscriptionManager sm) {
         return sm.getActiveSubscriptionIdList().length > 0;
     }
+
+    /**
+     * Decorates a pseudonym with the NAI realm, in case it wasn't provided by the server
+     *
+     * @param tm TelephonyManager instance
+     * @param pseudonym The pseudonym (temporary identity) provided by the server
+     * @return pseudonym@realm which is based on current MCC/MNC, {@code null} if SIM is
+     * not ready or absent.
+     */
+    public static String decoratePseudonymWith3GppRealm(@NonNull TelephonyManager tm,
+            String pseudonym) {
+        if (tm == null || TextUtils.isEmpty(pseudonym)) {
+            return null;
+        }
+        if (pseudonym.contains("@")) {
+            // Pseudonym is already decorated
+            return pseudonym;
+        }
+        TelephonyManager defaultDataTm = tm.createForSubscriptionId(
+                SubscriptionManager.getDefaultDataSubscriptionId());
+        if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) {
+            return null;
+        }
+        String mccMnc = defaultDataTm.getSimOperator();
+        if (mccMnc == null || mccMnc.isEmpty()) {
+            return null;
+        }
+
+        // Extract mcc & mnc from mccMnc
+        String mcc = mccMnc.substring(0, 3);
+        String mnc = mccMnc.substring(3);
+
+        if (mnc.length() == 2) {
+            mnc = "0" + mnc;
+        }
+
+        String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc);
+        return String.format("%s@%s", pseudonym, realm);
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java b/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java
new file mode 100644
index 0000000..46bf0fe
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/WifiConfigStoreEncryptionUtil.java
@@ -0,0 +1,195 @@
+/*
+ * 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 com.android.server.wifi.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.security.keystore.AndroidKeyStoreProvider;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableEntryException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * Tools to help encrypt/decrypt
+ */
+public class WifiConfigStoreEncryptionUtil {
+    private static final String TAG = "WifiConfigStoreEncryptionUtil";
+
+    private static final String ALIAS_SUFFIX = ".data-encryption-key";
+    private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+    private static final int GCM_TAG_LENGTH = 128;
+    private static final int KEY_LENGTH = 256;
+    private static final String KEY_STORE = "AndroidKeyStore";
+
+    private final String mDataFileName;
+
+    /**
+     * Construct a new util to help {@link com.android.server.wifi.WifiConfigStore.StoreData}
+     * modules to encrypt/decrypt credential data written/read from this config store file.
+     *
+     * @param dataFileName The full path of the data file.
+     * @throws NullPointerException When data file is empty string.
+     */
+    public WifiConfigStoreEncryptionUtil(@NonNull String dataFileName) {
+        if (TextUtils.isEmpty(dataFileName)) {
+            throw new NullPointerException("dataFileName must not be null or the empty "
+                    + "string");
+        }
+        mDataFileName = dataFileName;
+    }
+
+    private String getKeyAlias() {
+        return mDataFileName + ALIAS_SUFFIX;
+    }
+
+    /**
+     * Encrypt the provided data blob.
+     *
+     * @param data Data blob to be encrypted.
+     * @return Instance of {@link EncryptedData} containing the encrypted info.
+     */
+    public @Nullable EncryptedData encrypt(byte[] data) {
+        EncryptedData encryptedData = null;
+        try {
+            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+            SecretKey secretKeyReference = getOrCreateSecretKey(getKeyAlias());
+            if (secretKeyReference != null) {
+                cipher.init(Cipher.ENCRYPT_MODE, secretKeyReference);
+                encryptedData = new EncryptedData(cipher.doFinal(data), cipher.getIV());
+            } else {
+                reportException(new Exception("secretKeyReference is null."),
+                        "secretKeyReference is null.");
+            }
+        } catch (NoSuchAlgorithmException e) {
+            reportException(e, "encrypt could not find the algorithm: " + CIPHER_ALGORITHM);
+        } catch (NoSuchPaddingException e) {
+            reportException(e, "encrypt had a padding exception");
+        } catch (InvalidKeyException e) {
+            reportException(e, "encrypt received an invalid key");
+        } catch (BadPaddingException e) {
+            reportException(e, "encrypt had a padding problem");
+        } catch (IllegalBlockSizeException e) {
+            reportException(e, "encrypt had an illegal block size");
+        }
+        return encryptedData;
+    }
+
+    /**
+     * Decrypt the original data blob from the provided {@link EncryptedData}.
+     *
+     * @param encryptedData Instance of {@link EncryptedData} containing the encrypted info.
+     * @return Original data blob that was encrypted.
+     */
+    public @Nullable byte[] decrypt(@NonNull EncryptedData encryptedData) {
+        byte[] decryptedData = null;
+        try {
+            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+            GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, encryptedData.getIv());
+            SecretKey secretKeyReference = getOrCreateSecretKey(getKeyAlias());
+            if (secretKeyReference != null) {
+                cipher.init(Cipher.DECRYPT_MODE, secretKeyReference, spec);
+                decryptedData = cipher.doFinal(encryptedData.getEncryptedData());
+            }
+        } catch (NoSuchAlgorithmException e) {
+            reportException(e, "decrypt could not find cipher algorithm " + CIPHER_ALGORITHM);
+        } catch (NoSuchPaddingException e) {
+            reportException(e, "decrypt could not find padding algorithm");
+        } catch (IllegalBlockSizeException e) {
+            reportException(e, "decrypt had a illegal block size");
+        } catch (BadPaddingException e) {
+            reportException(e, "decrypt had bad padding");
+        } catch (InvalidKeyException e) {
+            reportException(e, "decrypt had an invalid key");
+        } catch (InvalidAlgorithmParameterException e) {
+            reportException(e, "decrypt had an invalid algorithm parameter");
+        }
+        return decryptedData;
+    }
+
+    private SecretKey getOrCreateSecretKey(String keyAlias) {
+        SecretKey secretKey = null;
+        try {
+            KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.WIFI_UID);
+            if (keyStore.containsAlias(keyAlias)) { // The key exists in key store. Get the key.
+                KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore
+                        .getEntry(keyAlias, null);
+                if (secretKeyEntry != null) {
+                    secretKey = secretKeyEntry.getSecretKey();
+                } else {
+                    reportException(new Exception("keystore contains the alias and the secret key "
+                            + "entry was null"),
+                            "keystore contains the alias and the secret key entry was null");
+                }
+            } else { // The key does not exist in key store. Create the key and store it.
+                KeyGenerator keyGenerator = KeyGenerator
+                        .getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE);
+
+                KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(keyAlias,
+                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                        .setKeySize(KEY_LENGTH)
+                        .setUid(Process.WIFI_UID)
+                        .build();
+
+                keyGenerator.init(keyGenParameterSpec);
+                secretKey = keyGenerator.generateKey();
+            }
+        } catch (InvalidAlgorithmParameterException e) {
+            reportException(e, "getOrCreateSecretKey had an invalid algorithm parameter");
+        } catch (KeyStoreException e) {
+            reportException(e, "getOrCreateSecretKey cannot find the keystore: " + KEY_STORE);
+        } catch (NoSuchAlgorithmException e) {
+            reportException(e, "getOrCreateSecretKey cannot find algorithm");
+        } catch (NoSuchProviderException e) {
+            reportException(e, "getOrCreateSecretKey cannot find crypto provider");
+        } catch (UnrecoverableEntryException e) {
+            reportException(e, "getOrCreateSecretKey had an unrecoverable entry exception.");
+        }
+        return secretKey;
+    }
+
+    /* TODO(b/128526030): Remove this error reporting code upon resolving the bug. */
+    private static final boolean REQUEST_BUG_REPORT = false;
+    private void reportException(Exception exception, String error) {
+        Log.wtf(TAG, "An irrecoverable key store error was encountered: " + error, exception);
+        if (REQUEST_BUG_REPORT) {
+            SystemProperties.set("dumpstate.options", "bugreportwifi");
+            SystemProperties.set("ctl.start", "bugreport");
+        }
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
index a08d876..ca93b71 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -125,10 +125,11 @@
     /**
      * Checks whether than the target SDK of the package is less than the specified version code.
      */
-    public boolean isTargetSdkLessThan(String packageName, int versionCode) {
+    public boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
         long ident = Binder.clearCallingIdentity();
         try {
-            if (mContext.getPackageManager().getApplicationInfo(packageName, 0).targetSdkVersion
+            if (mContext.getPackageManager().getApplicationInfoAsUser(
+                    packageName, 0, UserHandle.getUserId(callingUid)).targetSdkVersion
                     < versionCode) {
                 return true;
             }
@@ -154,7 +155,7 @@
      */
     public boolean checkCallersLocationPermission(String pkgName, int uid,
             boolean coarseForTargetSdkLessThanQ) {
-        boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q);
+        boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
 
         String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
         if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
diff --git a/service/java/com/android/server/wifi/util/XmlUtil.java b/service/java/com/android/server/wifi/util/XmlUtil.java
index 188d3b5..db0f428 100644
--- a/service/java/com/android/server/wifi/util/XmlUtil.java
+++ b/service/java/com/android/server/wifi/util/XmlUtil.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.util;
 
+import android.annotation.Nullable;
 import android.net.IpConfiguration;
 import android.net.IpConfiguration.IpAssignment;
 import android.net.IpConfiguration.ProxySettings;
@@ -28,6 +29,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 
@@ -377,19 +379,51 @@
         }
 
         /**
+         * Write preshared key to the XML stream.
+         *
+         * If encryptionUtil is null or if encryption fails for some reason, the pre-shared
+         * key is stored in plaintext, else the encrypted psk is stored.
+         */
+        private static void writePreSharedKeyToXml(
+                XmlSerializer out, String preSharedKey,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
+                throws XmlPullParserException, IOException {
+            EncryptedData encryptedData = null;
+            if (encryptionUtil != null) {
+                if (preSharedKey != null) {
+                    encryptedData = encryptionUtil.encrypt(preSharedKey.getBytes());
+                    if (encryptedData == null) {
+                        // We silently fail encryption failures!
+                        Log.wtf(TAG, "Encryption of preSharedKey failed");
+                    }
+                }
+            }
+            if (encryptedData != null) {
+                XmlUtil.writeNextSectionStart(out, XML_TAG_PRE_SHARED_KEY);
+                EncryptedDataXmlUtil.writeToXml(out, encryptedData);
+                XmlUtil.writeNextSectionEnd(out, XML_TAG_PRE_SHARED_KEY);
+            } else {
+                XmlUtil.writeNextValue(out, XML_TAG_PRE_SHARED_KEY, preSharedKey);
+            }
+        }
+
+        /**
          * Write the Configuration data elements that are common for backup & config store to the
          * XML stream.
          *
-         * @param out           XmlSerializer instance pointing to the XML stream.
+         * @param out XmlSerializer instance pointing to the XML stream.
          * @param configuration WifiConfiguration object to be serialized.
+         * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. Backup/restore stores
+         *                       keys unencrypted.
          */
         public static void writeCommonElementsToXml(
-                XmlSerializer out, WifiConfiguration configuration)
+                XmlSerializer out, WifiConfiguration configuration,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException {
             XmlUtil.writeNextValue(out, XML_TAG_CONFIG_KEY, configuration.configKey());
             XmlUtil.writeNextValue(out, XML_TAG_SSID, configuration.SSID);
             XmlUtil.writeNextValue(out, XML_TAG_BSSID, configuration.BSSID);
-            XmlUtil.writeNextValue(out, XML_TAG_PRE_SHARED_KEY, configuration.preSharedKey);
+            writePreSharedKeyToXml(out, configuration.preSharedKey, encryptionUtil);
             writeWepKeysToXml(out, configuration.wepKeys);
             XmlUtil.writeNextValue(out, XML_TAG_WEP_TX_KEY_INDEX, configuration.wepTxKeyIndex);
             XmlUtil.writeNextValue(out, XML_TAG_HIDDEN_SSID, configuration.hiddenSSID);
@@ -428,7 +462,7 @@
          */
         public static void writeToXmlForBackup(XmlSerializer out, WifiConfiguration configuration)
                 throws XmlPullParserException, IOException {
-            writeCommonElementsToXml(out, configuration);
+            writeCommonElementsToXml(out, configuration, null);
             XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, configuration.meteredOverride);
         }
 
@@ -436,13 +470,15 @@
          * Write the Configuration data elements for config store from the provided Configuration
          * to the XML stream.
          *
-         * @param out           XmlSerializer instance pointing to the XML stream.
+         * @param out XmlSerializer instance pointing to the XML stream.
          * @param configuration WifiConfiguration object to be serialized.
+         * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
          */
         public static void writeToXmlForConfigStore(
-                XmlSerializer out, WifiConfiguration configuration)
+                XmlSerializer out, WifiConfiguration configuration,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException {
-            writeCommonElementsToXml(out, configuration);
+            writeCommonElementsToXml(out, configuration, encryptionUtil);
             XmlUtil.writeNextValue(out, XML_TAG_STATUS, configuration.status);
             XmlUtil.writeNextValue(out, XML_TAG_FQDN, configuration.FQDN);
             XmlUtil.writeNextValue(
@@ -509,13 +545,16 @@
          * Note: This is used for parsing both backup data and config store data. Looping through
          * the tags make it easy to add or remove elements in the future versions if needed.
          *
-         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param in XmlPullParser instance pointing to the XML stream.
          * @param outerTagDepth depth of the outer tag in the XML document.
+         * @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not.
+         * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
          * @return Pair<Config key, WifiConfiguration object> if parsing is successful,
          * null otherwise.
          */
         public static Pair<String, WifiConfiguration> parseFromXml(
-                XmlPullParser in, int outerTagDepth)
+                XmlPullParser in, int outerTagDepth, boolean shouldExpectEncryptedCredentials,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException {
             WifiConfiguration configuration = new WifiConfiguration();
             String configKeyInData = null;
@@ -523,147 +562,175 @@
 
             // Loop through and parse out all the elements from the stream within this section.
             while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
-                String[] valueName = new String[1];
-                Object value = XmlUtil.readCurrentValue(in, valueName);
-                if (valueName[0] == null) {
-                    throw new XmlPullParserException("Missing value name");
-                }
-                switch (valueName[0]) {
-                    case XML_TAG_CONFIG_KEY:
-                        configKeyInData = (String) value;
-                        break;
-                    case XML_TAG_SSID:
-                        configuration.SSID = (String) value;
-                        break;
-                    case XML_TAG_BSSID:
-                        configuration.BSSID = (String) value;
-                        break;
-                    case XML_TAG_PRE_SHARED_KEY:
-                        configuration.preSharedKey = (String) value;
-                        break;
-                    case XML_TAG_WEP_KEYS:
-                        populateWepKeysFromXmlValue(value, configuration.wepKeys);
-                        break;
-                    case XML_TAG_WEP_TX_KEY_INDEX:
-                        configuration.wepTxKeyIndex = (int) value;
-                        break;
-                    case XML_TAG_HIDDEN_SSID:
-                        configuration.hiddenSSID = (boolean) value;
-                        break;
-                    case XML_TAG_REQUIRE_PMF:
-                        configuration.requirePMF = (boolean) value;
-                        break;
-                    case XML_TAG_ALLOWED_KEY_MGMT:
-                        byte[] allowedKeyMgmt = (byte[]) value;
-                        configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt);
-                        break;
-                    case XML_TAG_ALLOWED_PROTOCOLS:
-                        byte[] allowedProtocols = (byte[]) value;
-                        configuration.allowedProtocols = BitSet.valueOf(allowedProtocols);
-                        break;
-                    case XML_TAG_ALLOWED_AUTH_ALGOS:
-                        byte[] allowedAuthAlgorithms = (byte[]) value;
-                        configuration.allowedAuthAlgorithms = BitSet.valueOf(allowedAuthAlgorithms);
-                        break;
-                    case XML_TAG_ALLOWED_GROUP_CIPHERS:
-                        byte[] allowedGroupCiphers = (byte[]) value;
-                        configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers);
-                        break;
-                    case XML_TAG_ALLOWED_PAIRWISE_CIPHERS:
-                        byte[] allowedPairwiseCiphers = (byte[]) value;
-                        configuration.allowedPairwiseCiphers =
-                                BitSet.valueOf(allowedPairwiseCiphers);
-                        break;
-                    case XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS:
-                        byte[] allowedGroupMgmtCiphers = (byte[]) value;
-                        configuration.allowedGroupManagementCiphers =
-                                BitSet.valueOf(allowedGroupMgmtCiphers);
-                        break;
-                    case XML_TAG_ALLOWED_SUITE_B_CIPHERS:
-                        byte[] allowedSuiteBCiphers = (byte[]) value;
-                        configuration.allowedSuiteBCiphers =
-                                BitSet.valueOf(allowedSuiteBCiphers);
-                        break;
-                    case XML_TAG_SHARED:
-                        configuration.shared = (boolean) value;
-                        break;
-                    case XML_TAG_STATUS:
-                        int status = (int) value;
-                        // Any network which was CURRENT before reboot needs
-                        // to be restored to ENABLED.
-                        if (status == WifiConfiguration.Status.CURRENT) {
-                            status = WifiConfiguration.Status.ENABLED;
-                        }
-                        configuration.status = status;
-                        break;
-                    case XML_TAG_FQDN:
-                        configuration.FQDN = (String) value;
-                        break;
-                    case XML_TAG_PROVIDER_FRIENDLY_NAME:
-                        configuration.providerFriendlyName = (String) value;
-                        break;
-                    case XML_TAG_LINKED_NETWORKS_LIST:
-                        configuration.linkedConfigurations = (HashMap<String, Integer>) value;
-                        break;
-                    case XML_TAG_DEFAULT_GW_MAC_ADDRESS:
-                        configuration.defaultGwMacAddress = (String) value;
-                        break;
-                    case XML_TAG_VALIDATED_INTERNET_ACCESS:
-                        configuration.validatedInternetAccess = (boolean) value;
-                        break;
-                    case XML_TAG_NO_INTERNET_ACCESS_EXPECTED:
-                        configuration.noInternetAccessExpected = (boolean) value;
-                        break;
-                    case XML_TAG_USER_APPROVED:
-                        configuration.userApproved = (int) value;
-                        break;
-                    case XML_TAG_METERED_HINT:
-                        configuration.meteredHint = (boolean) value;
-                        break;
-                    case XML_TAG_METERED_OVERRIDE:
-                        configuration.meteredOverride = (int) value;
-                        break;
-                    case XML_TAG_USE_EXTERNAL_SCORES:
-                        configuration.useExternalScores = (boolean) value;
-                        break;
-                    case XML_TAG_NUM_ASSOCIATION:
-                        configuration.numAssociation = (int) value;
-                        break;
-                    case XML_TAG_CREATOR_UID:
-                        configuration.creatorUid = (int) value;
-                        break;
-                    case XML_TAG_CREATOR_NAME:
-                        configuration.creatorName = (String) value;
-                        break;
-                    case XML_TAG_CREATION_TIME:
-                        configuration.creationTime = (String) value;
-                        break;
-                    case XML_TAG_LAST_UPDATE_UID:
-                        configuration.lastUpdateUid = (int) value;
-                        break;
-                    case XML_TAG_LAST_UPDATE_NAME:
-                        configuration.lastUpdateName = (String) value;
-                        break;
-                    case XML_TAG_LAST_CONNECT_UID:
-                        configuration.lastConnectUid = (int) value;
-                        break;
-                    case XML_TAG_IS_LEGACY_PASSPOINT_CONFIG:
-                        configuration.isLegacyPasspointConfig = (boolean) value;
-                        break;
-                    case XML_TAG_ROAMING_CONSORTIUM_OIS:
-                        configuration.roamingConsortiumIds = (long[]) value;
-                        break;
-                    case XML_TAG_RANDOMIZED_MAC_ADDRESS:
-                        configuration.setRandomizedMacAddress(
-                                MacAddress.fromString((String) value));
-                        break;
-                    case XML_TAG_MAC_RANDOMIZATION_SETTING:
-                        configuration.macRandomizationSetting = (int) value;
-                        macRandomizationSettingExists = true;
-                        break;
-                    default:
-                        throw new XmlPullParserException(
-                                "Unknown value name found: " + valueName[0]);
+                if (in.getAttributeValue(null, "name") != null) {
+                    // Value elements.
+                    String[] valueName = new String[1];
+                    Object value = XmlUtil.readCurrentValue(in, valueName);
+                    if (valueName[0] == null) {
+                        throw new XmlPullParserException("Missing value name");
+                    }
+                    switch (valueName[0]) {
+                        case XML_TAG_CONFIG_KEY:
+                            configKeyInData = (String) value;
+                            break;
+                        case XML_TAG_SSID:
+                            configuration.SSID = (String) value;
+                            break;
+                        case XML_TAG_BSSID:
+                            configuration.BSSID = (String) value;
+                            break;
+                        case XML_TAG_PRE_SHARED_KEY:
+                            configuration.preSharedKey = (String) value;
+                            break;
+                        case XML_TAG_WEP_KEYS:
+                            populateWepKeysFromXmlValue(value, configuration.wepKeys);
+                            break;
+                        case XML_TAG_WEP_TX_KEY_INDEX:
+                            configuration.wepTxKeyIndex = (int) value;
+                            break;
+                        case XML_TAG_HIDDEN_SSID:
+                            configuration.hiddenSSID = (boolean) value;
+                            break;
+                        case XML_TAG_REQUIRE_PMF:
+                            configuration.requirePMF = (boolean) value;
+                            break;
+                        case XML_TAG_ALLOWED_KEY_MGMT:
+                            byte[] allowedKeyMgmt = (byte[]) value;
+                            configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt);
+                            break;
+                        case XML_TAG_ALLOWED_PROTOCOLS:
+                            byte[] allowedProtocols = (byte[]) value;
+                            configuration.allowedProtocols = BitSet.valueOf(allowedProtocols);
+                            break;
+                        case XML_TAG_ALLOWED_AUTH_ALGOS:
+                            byte[] allowedAuthAlgorithms = (byte[]) value;
+                            configuration.allowedAuthAlgorithms = BitSet.valueOf(
+                                    allowedAuthAlgorithms);
+                            break;
+                        case XML_TAG_ALLOWED_GROUP_CIPHERS:
+                            byte[] allowedGroupCiphers = (byte[]) value;
+                            configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers);
+                            break;
+                        case XML_TAG_ALLOWED_PAIRWISE_CIPHERS:
+                            byte[] allowedPairwiseCiphers = (byte[]) value;
+                            configuration.allowedPairwiseCiphers =
+                                    BitSet.valueOf(allowedPairwiseCiphers);
+                            break;
+                        case XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS:
+                            byte[] allowedGroupMgmtCiphers = (byte[]) value;
+                            configuration.allowedGroupManagementCiphers =
+                                    BitSet.valueOf(allowedGroupMgmtCiphers);
+                            break;
+                        case XML_TAG_ALLOWED_SUITE_B_CIPHERS:
+                            byte[] allowedSuiteBCiphers = (byte[]) value;
+                            configuration.allowedSuiteBCiphers =
+                                    BitSet.valueOf(allowedSuiteBCiphers);
+                            break;
+                        case XML_TAG_SHARED:
+                            configuration.shared = (boolean) value;
+                            break;
+                        case XML_TAG_STATUS:
+                            int status = (int) value;
+                            // Any network which was CURRENT before reboot needs
+                            // to be restored to ENABLED.
+                            if (status == WifiConfiguration.Status.CURRENT) {
+                                status = WifiConfiguration.Status.ENABLED;
+                            }
+                            configuration.status = status;
+                            break;
+                        case XML_TAG_FQDN:
+                            configuration.FQDN = (String) value;
+                            break;
+                        case XML_TAG_PROVIDER_FRIENDLY_NAME:
+                            configuration.providerFriendlyName = (String) value;
+                            break;
+                        case XML_TAG_LINKED_NETWORKS_LIST:
+                            configuration.linkedConfigurations = (HashMap<String, Integer>) value;
+                            break;
+                        case XML_TAG_DEFAULT_GW_MAC_ADDRESS:
+                            configuration.defaultGwMacAddress = (String) value;
+                            break;
+                        case XML_TAG_VALIDATED_INTERNET_ACCESS:
+                            configuration.validatedInternetAccess = (boolean) value;
+                            break;
+                        case XML_TAG_NO_INTERNET_ACCESS_EXPECTED:
+                            configuration.noInternetAccessExpected = (boolean) value;
+                            break;
+                        case XML_TAG_USER_APPROVED:
+                            configuration.userApproved = (int) value;
+                            break;
+                        case XML_TAG_METERED_HINT:
+                            configuration.meteredHint = (boolean) value;
+                            break;
+                        case XML_TAG_METERED_OVERRIDE:
+                            configuration.meteredOverride = (int) value;
+                            break;
+                        case XML_TAG_USE_EXTERNAL_SCORES:
+                            configuration.useExternalScores = (boolean) value;
+                            break;
+                        case XML_TAG_NUM_ASSOCIATION:
+                            configuration.numAssociation = (int) value;
+                            break;
+                        case XML_TAG_CREATOR_UID:
+                            configuration.creatorUid = (int) value;
+                            break;
+                        case XML_TAG_CREATOR_NAME:
+                            configuration.creatorName = (String) value;
+                            break;
+                        case XML_TAG_CREATION_TIME:
+                            configuration.creationTime = (String) value;
+                            break;
+                        case XML_TAG_LAST_UPDATE_UID:
+                            configuration.lastUpdateUid = (int) value;
+                            break;
+                        case XML_TAG_LAST_UPDATE_NAME:
+                            configuration.lastUpdateName = (String) value;
+                            break;
+                        case XML_TAG_LAST_CONNECT_UID:
+                            configuration.lastConnectUid = (int) value;
+                            break;
+                        case XML_TAG_IS_LEGACY_PASSPOINT_CONFIG:
+                            configuration.isLegacyPasspointConfig = (boolean) value;
+                            break;
+                        case XML_TAG_ROAMING_CONSORTIUM_OIS:
+                            configuration.roamingConsortiumIds = (long[]) value;
+                            break;
+                        case XML_TAG_RANDOMIZED_MAC_ADDRESS:
+                            configuration.setRandomizedMacAddress(
+                                    MacAddress.fromString((String) value));
+                            break;
+                        case XML_TAG_MAC_RANDOMIZATION_SETTING:
+                            configuration.macRandomizationSetting = (int) value;
+                            macRandomizationSettingExists = true;
+                            break;
+                        default:
+                            throw new XmlPullParserException(
+                                  "Unknown value name found: " + valueName[0]);
+                    }
+                } else {
+                    String tagName = in.getName();
+                    if (tagName == null) {
+                        throw new XmlPullParserException("Unexpected null tag found");
+                    }
+                    switch (tagName) {
+                        case XML_TAG_PRE_SHARED_KEY:
+                            if (!shouldExpectEncryptedCredentials || encryptionUtil == null) {
+                                throw new XmlPullParserException(
+                                        "Encrypted preSharedKey section not expected");
+                            }
+                            EncryptedData encryptedData =
+                                    EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                            byte[] preSharedKeyBytes = encryptionUtil.decrypt(encryptedData);
+                            if (preSharedKeyBytes == null) {
+                                Log.wtf(TAG, "Decryption of preSharedKey failed");
+                            } else {
+                                configuration.preSharedKey = new String(preSharedKeyBytes);
+                            }
+                            break;
+                        default:
+                            throw new XmlPullParserException(
+                                  "Unknown tag name found: " + tagName);
+                    }
                 }
             }
             if (!macRandomizationSettingExists) {
@@ -1019,20 +1086,52 @@
         public static final String XML_TAG_REALM = "Realm";
 
         /**
+         * Write password key to the XML stream.
+         *
+         * If encryptionUtil is null or if encryption fails for some reason, the password is stored
+         * in plaintext, else the encrypted psk is stored.
+         */
+        private static void writePasswordToXml(
+                XmlSerializer out, String password,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
+                throws XmlPullParserException, IOException {
+            EncryptedData encryptedData = null;
+            if (encryptionUtil != null) {
+                if (password != null) {
+                    encryptedData = encryptionUtil.encrypt(password.getBytes());
+                    if (encryptedData == null) {
+                        // We silently fail encryption failures!
+                        Log.wtf(TAG, "Encryption of password failed");
+                    }
+                }
+            }
+            if (encryptedData != null) {
+                XmlUtil.writeNextSectionStart(out, XML_TAG_PASSWORD);
+                EncryptedDataXmlUtil.writeToXml(out, encryptedData);
+                XmlUtil.writeNextSectionEnd(out, XML_TAG_PASSWORD);
+            } else {
+                XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, password);
+            }
+        }
+
+        /**
          * Write the WifiEnterpriseConfig data elements from the provided config to the XML
          * stream.
          *
-         * @param out              XmlSerializer instance pointing to the XML stream.
+         * @param out XmlSerializer instance pointing to the XML stream.
          * @param enterpriseConfig WifiEnterpriseConfig object to be serialized.
+         * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
          */
-        public static void writeToXml(XmlSerializer out, WifiEnterpriseConfig enterpriseConfig)
+        public static void writeToXml(XmlSerializer out, WifiEnterpriseConfig enterpriseConfig,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException {
             XmlUtil.writeNextValue(out, XML_TAG_IDENTITY,
                     enterpriseConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY));
             XmlUtil.writeNextValue(out, XML_TAG_ANON_IDENTITY,
                     enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY));
-            XmlUtil.writeNextValue(out, XML_TAG_PASSWORD,
-                    enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY));
+            writePasswordToXml(
+                    out, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY),
+                    encryptionUtil);
             XmlUtil.writeNextValue(out, XML_TAG_CLIENT_CERT,
                     enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY));
             XmlUtil.writeNextValue(out, XML_TAG_CA_CERT,
@@ -1060,15 +1159,170 @@
         /**
          * Parses the data elements from the provided XML stream to a WifiEnterpriseConfig object.
          *
-         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param in XmlPullParser instance pointing to the XML stream.
          * @param outerTagDepth depth of the outer tag in the XML document.
+         * @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not.
+         * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}.
          * @return WifiEnterpriseConfig object if parsing is successful, null otherwise.
          */
-        public static WifiEnterpriseConfig parseFromXml(XmlPullParser in, int outerTagDepth)
+        public static WifiEnterpriseConfig parseFromXml(XmlPullParser in, int outerTagDepth,
+                boolean shouldExpectEncryptedCredentials,
+                @Nullable WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException {
             WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
 
             // Loop through and parse out all the elements from the stream within this section.
+            while (XmlUtils.nextElementWithin(in, outerTagDepth)) {
+                if (in.getAttributeValue(null, "name") != null) {
+                    // Value elements.
+                    String[] valueName = new String[1];
+                    Object value = XmlUtil.readCurrentValue(in, valueName);
+                    if (valueName[0] == null) {
+                        throw new XmlPullParserException("Missing value name");
+                    }
+                    switch (valueName[0]) {
+                        case XML_TAG_IDENTITY:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.IDENTITY_KEY, (String) value);
+                            break;
+                        case XML_TAG_ANON_IDENTITY:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.ANON_IDENTITY_KEY, (String) value);
+                            break;
+                        case XML_TAG_PASSWORD:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.PASSWORD_KEY, (String) value);
+                            if (shouldExpectEncryptedCredentials
+                                    && !TextUtils.isEmpty(enterpriseConfig.getFieldValue(
+                                            WifiEnterpriseConfig.PASSWORD_KEY))) {
+                                // Indicates that encryption of password failed when it was last
+                                // written.
+                                Log.e(TAG, "password value not expected");
+                            }
+                            break;
+                        case XML_TAG_CLIENT_CERT:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.CLIENT_CERT_KEY, (String) value);
+                            break;
+                        case XML_TAG_CA_CERT:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.CA_CERT_KEY, (String) value);
+                            break;
+                        case XML_TAG_SUBJECT_MATCH:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.SUBJECT_MATCH_KEY, (String) value);
+                            break;
+                        case XML_TAG_ENGINE:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.ENGINE_KEY, (String) value);
+                            break;
+                        case XML_TAG_ENGINE_ID:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.ENGINE_ID_KEY, (String) value);
+                            break;
+                        case XML_TAG_PRIVATE_KEY_ID:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, (String) value);
+                            break;
+                        case XML_TAG_ALT_SUBJECT_MATCH:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, (String) value);
+                            break;
+                        case XML_TAG_DOM_SUFFIX_MATCH:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, (String) value);
+                            break;
+                        case XML_TAG_CA_PATH:
+                            enterpriseConfig.setFieldValue(
+                                    WifiEnterpriseConfig.CA_PATH_KEY, (String) value);
+                            break;
+                        case XML_TAG_EAP_METHOD:
+                            enterpriseConfig.setEapMethod((int) value);
+                            break;
+                        case XML_TAG_PHASE2_METHOD:
+                            enterpriseConfig.setPhase2Method((int) value);
+                            break;
+                        case XML_TAG_PLMN:
+                            enterpriseConfig.setPlmn((String) value);
+                            break;
+                        case XML_TAG_REALM:
+                            enterpriseConfig.setRealm((String) value);
+                            break;
+                        default:
+                            throw new XmlPullParserException(
+                                  "Unknown value name found: " + valueName[0]);
+                    }
+                } else {
+                    String tagName = in.getName();
+                    if (tagName == null) {
+                        throw new XmlPullParserException("Unexpected null tag found");
+                    }
+                    switch (tagName) {
+                        case XML_TAG_PASSWORD:
+                            if (!shouldExpectEncryptedCredentials || encryptionUtil == null) {
+                                throw new XmlPullParserException(
+                                        "encrypted password section not expected");
+                            }
+                            EncryptedData encryptedData =
+                                    EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                            byte[] passwordBytes = encryptionUtil.decrypt(encryptedData);
+                            if (passwordBytes == null) {
+                                Log.wtf(TAG, "Decryption of password failed");
+                            } else {
+                                enterpriseConfig.setFieldValue(
+                                        WifiEnterpriseConfig.PASSWORD_KEY,
+                                        new String(passwordBytes));
+                            }
+                            break;
+                        default:
+                            throw new XmlPullParserException(
+                                  "Unknown tag name found: " + tagName);
+                    }
+                }
+            }
+            return enterpriseConfig;
+        }
+    }
+
+    /**
+     * Utility class to serialize and deseriaize {@link EncryptedData} object to XML &
+     * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module.
+     */
+    public static class EncryptedDataXmlUtil {
+        /**
+         * List of XML tags corresponding to EncryptedData object elements.
+         */
+        private static final String XML_TAG_ENCRYPTED_DATA = "EncryptedData";
+        private static final String XML_TAG_IV = "IV";
+
+        /**
+         * Write the NetworkSelectionStatus data elements from the provided status to the XML
+         * stream.
+         *
+         * @param out           XmlSerializer instance pointing to the XML stream.
+         * @param encryptedData EncryptedData object to be serialized.
+         */
+        public static void writeToXml(XmlSerializer out, EncryptedData encryptedData)
+                throws XmlPullParserException, IOException {
+            XmlUtil.writeNextValue(
+                    out, XML_TAG_ENCRYPTED_DATA, encryptedData.getEncryptedData());
+            XmlUtil.writeNextValue(out, XML_TAG_IV, encryptedData.getIv());
+        }
+
+        /**
+         * Parses the EncryptedData data elements from the provided XML stream to a
+         * EncryptedData object.
+         *
+         * @param in            XmlPullParser instance pointing to the XML stream.
+         * @param outerTagDepth depth of the outer tag in the XML document.
+         * @return EncryptedData object if parsing is successful, null otherwise.
+         */
+        public static EncryptedData parseFromXml(XmlPullParser in, int outerTagDepth)
+                throws XmlPullParserException, IOException {
+            byte[] encryptedData = null;
+            byte[] iv = null;
+
+            // Loop through and parse out all the elements from the stream within this section.
             while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
                 String[] valueName = new String[1];
                 Object value = XmlUtil.readCurrentValue(in, valueName);
@@ -1076,72 +1330,18 @@
                     throw new XmlPullParserException("Missing value name");
                 }
                 switch (valueName[0]) {
-                    case XML_TAG_IDENTITY:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.IDENTITY_KEY, (String) value);
+                    case XML_TAG_ENCRYPTED_DATA:
+                        encryptedData = (byte[]) value;
                         break;
-                    case XML_TAG_ANON_IDENTITY:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.ANON_IDENTITY_KEY, (String) value);
-                        break;
-                    case XML_TAG_PASSWORD:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.PASSWORD_KEY, (String) value);
-                        break;
-                    case XML_TAG_CLIENT_CERT:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.CLIENT_CERT_KEY, (String) value);
-                        break;
-                    case XML_TAG_CA_CERT:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.CA_CERT_KEY, (String) value);
-                        break;
-                    case XML_TAG_SUBJECT_MATCH:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.SUBJECT_MATCH_KEY, (String) value);
-                        break;
-                    case XML_TAG_ENGINE:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.ENGINE_KEY, (String) value);
-                        break;
-                    case XML_TAG_ENGINE_ID:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.ENGINE_ID_KEY, (String) value);
-                        break;
-                    case XML_TAG_PRIVATE_KEY_ID:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, (String) value);
-                        break;
-                    case XML_TAG_ALT_SUBJECT_MATCH:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, (String) value);
-                        break;
-                    case XML_TAG_DOM_SUFFIX_MATCH:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, (String) value);
-                        break;
-                    case XML_TAG_CA_PATH:
-                        enterpriseConfig.setFieldValue(
-                                WifiEnterpriseConfig.CA_PATH_KEY, (String) value);
-                        break;
-                    case XML_TAG_EAP_METHOD:
-                        enterpriseConfig.setEapMethod((int) value);
-                        break;
-                    case XML_TAG_PHASE2_METHOD:
-                        enterpriseConfig.setPhase2Method((int) value);
-                        break;
-                    case XML_TAG_PLMN:
-                        enterpriseConfig.setPlmn((String) value);
-                        break;
-                    case XML_TAG_REALM:
-                        enterpriseConfig.setRealm((String) value);
+                    case XML_TAG_IV:
+                        iv = (byte[]) value;
                         break;
                     default:
                         throw new XmlPullParserException(
                                 "Unknown value name found: " + valueName[0]);
                 }
             }
-            return enterpriseConfig;
+            return new EncryptedData(encryptedData, iv);
         }
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
index aa1ab30..afb3ef5 100644
--- a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
@@ -384,7 +384,7 @@
     @Mock AsyncChannel mNullAsyncChannel;
     @Mock CarrierNetworkConfig mCarrierNetworkConfig;
     @Mock Handler mNetworkAgentHandler;
-
+    @Mock ConnectionFailureNotifier mConnectionFailureNotifier;
 
     final ArgumentCaptor<WifiNative.InterfaceCallback> mInterfaceCallbackCaptor =
             ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class);
@@ -441,6 +441,8 @@
         when(mWifiInjector.getWifiScoreCard()).thenReturn(mWifiScoreCard);
         when(mWifiInjector.getWifiLockManager()).thenReturn(mWifiLockManager);
         when(mWifiInjector.getCarrierNetworkConfig()).thenReturn(mCarrierNetworkConfig);
+        when(mWifiInjector.makeConnectionFailureNotifier(any()))
+                .thenReturn(mConnectionFailureNotifier);
         when(mWifiNetworkFactory.getSpecificNetworkRequestUidAndPackageName(any()))
                 .thenReturn(Pair.create(Process.INVALID_UID, ""));
         when(mWifiNative.initialize()).thenReturn(true);
@@ -1033,10 +1035,10 @@
 
     /**
      * Tests anonymous identity is set again whenever a connection is established for the carrier
-     * that supports encrypted IMSI and anonymous identity.
+     * that supports encrypted IMSI and anonymous identity and no real pseudonym was provided.
      */
     @Test
-    public void testSetAnonymousIdentityWhenConnectionIsEstablished() throws Exception {
+    public void testSetAnonymousIdentityWhenConnectionIsEstablishedNoPseudonym() throws Exception {
         mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
                 WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
         when(mDataTelephonyManager.getSimOperator()).thenReturn("123456");
@@ -1047,6 +1049,58 @@
 
         when(mCarrierNetworkConfig.isCarrierEncryptionInfoAvailable()).thenReturn(true);
 
+        // Initial value should be "not set"
+        assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+
+        triggerConnect();
+
+        // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm>
+        assertEquals(expectedAnonymousIdentity,
+                mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
+        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mWifiNative.getEapAnonymousIdentity(anyString()))
+                .thenReturn(expectedAnonymousIdentity);
+
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).getEapAnonymousIdentity(any());
+
+        // Post connection value should remain "not set"
+        assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+        // verify that WifiConfigManager#addOrUpdateNetwork() was called to clear any previously
+        // stored pseudonym. i.e. to enable Encrypted IMSI for subsequent connections.
+        // Note: This test will fail if future logic will have additional conditions that would
+        // trigger "add or update network" operation. The test needs to be updated to account for
+        // this change.
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
+    }
+
+    /**
+     * Tests anonymous identity is set again whenever a connection is established for the carrier
+     * that supports encrypted IMSI and anonymous identity but real pseudonym was provided for
+     * subsequent connections.
+     */
+    @Test
+    public void testSetAnonymousIdentityWhenConnectionIsEstablishedWithPseudonym()
+            throws Exception {
+        mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
+        when(mDataTelephonyManager.getSimOperator()).thenReturn("123456");
+        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
+
+        String expectedAnonymousIdentity = "anonymous@wlan.mnc456.mcc123.3gppnetwork.org";
+        String pseudonym = "83bcca9384fca@wlan.mnc456.mcc123.3gppnetwork.org";
+
+        when(mCarrierNetworkConfig.isCarrierEncryptionInfoAvailable()).thenReturn(true);
+
         triggerConnect();
 
         // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm>
@@ -1059,19 +1113,74 @@
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
         when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
                 getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mWifiNative.getEapAnonymousIdentity(anyString()))
+                .thenReturn(pseudonym);
 
         mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
         mLooper.dispatchAll();
 
-        // verify that WifiNative#getEapAnonymousIdentity() was never called since we are using
-        // encrypted IMSI full authentication and not using pseudonym identity.
-        verify(mWifiNative, never()).getEapAnonymousIdentity(any());
-        // check that the anonymous identity remains anonymous@<realm> for subsequent connections.
-        assertEquals(expectedAnonymousIdentity,
+        verify(mWifiNative).getEapAnonymousIdentity(any());
+        assertEquals(pseudonym,
                 mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+        // Verify that WifiConfigManager#addOrUpdateNetwork() was called if there we received a
+        // real pseudonym to be stored. i.e. Encrypted IMSI will be used once, followed by
+        // pseudonym usage in all subsequent connections.
+        // Note: This test will fail if future logic will have additional conditions that would
+        // trigger "add or update network" operation. The test needs to be updated to account for
+        // this change.
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
     }
 
     /**
+     * Tests anonymous identity is set again whenever a connection is established for the carrier
+     * that supports encrypted IMSI and anonymous identity but real but not decorated pseudonym was
+     * provided for subsequent connections.
+     */
+    @Test
+    public void testSetAnonymousIdentityWhenConnectionIsEstablishedWithNonDecoratedPseudonym()
+            throws Exception {
+        mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork(
+                WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE));
+        when(mDataTelephonyManager.getSimOperator()).thenReturn("123456");
+        when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY);
+        mConnectedNetwork.enterpriseConfig.setAnonymousIdentity("");
+
+        String realm = "wlan.mnc456.mcc123.3gppnetwork.org";
+        String expectedAnonymousIdentity = "anonymous";
+        String pseudonym = "83bcca9384fca";
+
+        when(mCarrierNetworkConfig.isCarrierEncryptionInfoAvailable()).thenReturn(true);
+
+        triggerConnect();
+
+        // CMD_START_CONNECT should have set anonymousIdentity to anonymous@<realm>
+        assertEquals(expectedAnonymousIdentity + "@" + realm,
+                mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+
+        when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
+                .thenReturn(mScanDetailCache);
+        when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq));
+        when(mScanDetailCache.getScanResult(sBSSID)).thenReturn(
+                getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
+        when(mWifiNative.getEapAnonymousIdentity(anyString()))
+                .thenReturn(pseudonym);
+
+        mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).getEapAnonymousIdentity(any());
+        assertEquals(pseudonym + "@" + realm,
+                mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
+        // Verify that WifiConfigManager#addOrUpdateNetwork() was called if there we received a
+        // real pseudonym to be stored. i.e. Encrypted IMSI will be used once, followed by
+        // pseudonym usage in all subsequent connections.
+        // Note: This test will fail if future logic will have additional conditions that would
+        // trigger "add or update network" operation. The test needs to be updated to account for
+        // this change.
+        verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt());
+    }
+    /**
      * Tests the Passpoint information is set in WifiInfo for Passpoint AP connection.
      */
     @Test
@@ -1734,10 +1843,10 @@
     /** Verifies that syncGetSupportedFeatures() masks out capabilities based on system flags. */
     @Test
     public void syncGetSupportedFeatures() {
-        final int featureAware = WifiManager.WIFI_FEATURE_AWARE;
-        final int featureInfra = WifiManager.WIFI_FEATURE_INFRA;
-        final int featureD2dRtt = WifiManager.WIFI_FEATURE_D2D_RTT;
-        final int featureD2apRtt = WifiManager.WIFI_FEATURE_D2AP_RTT;
+        final long featureAware = WifiManager.WIFI_FEATURE_AWARE;
+        final long featureInfra = WifiManager.WIFI_FEATURE_INFRA;
+        final long featureD2dRtt = WifiManager.WIFI_FEATURE_D2D_RTT;
+        final long featureD2apRtt = WifiManager.WIFI_FEATURE_D2AP_RTT;
         final long featureLongBits = 0x1100000000L;
 
         assertEquals(0, testGetSupportedFeaturesCase(0, false));
@@ -1811,15 +1920,15 @@
     @Test
     public void syncRemovePasspointConfig() throws Exception {
         String fqdn = "test.com";
-        when(mPasspointManager.removeProvider(anyInt(), eq(fqdn))).thenReturn(true);
+        when(mPasspointManager.removeProvider(anyInt(), anyBoolean(), eq(fqdn))).thenReturn(true);
         mLooper.startAutoDispatch();
-        assertTrue(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, fqdn));
+        assertTrue(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, true, fqdn));
         mLooper.stopAutoDispatch();
         reset(mPasspointManager);
 
-        when(mPasspointManager.removeProvider(anyInt(), eq(fqdn))).thenReturn(false);
+        when(mPasspointManager.removeProvider(anyInt(), anyBoolean(), eq(fqdn))).thenReturn(false);
         mLooper.startAutoDispatch();
-        assertFalse(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, fqdn));
+        assertFalse(mCmi.syncRemovePasspointConfig(mCmiAsyncChannel, true, fqdn));
         mLooper.stopAutoDispatch();
     }
 
@@ -1847,16 +1956,17 @@
         config.setHomeSp(homeSp);
         expectedConfigs.add(config);
 
-        when(mPasspointManager.getProviderConfigs()).thenReturn(expectedConfigs);
+        when(mPasspointManager.getProviderConfigs(anyInt(), anyBoolean()))
+                .thenReturn(expectedConfigs);
         mLooper.startAutoDispatch();
-        assertEquals(expectedConfigs, mCmi.syncGetPasspointConfigs(mCmiAsyncChannel));
+        assertEquals(expectedConfigs, mCmi.syncGetPasspointConfigs(mCmiAsyncChannel, true));
         mLooper.stopAutoDispatch();
         reset(mPasspointManager);
 
-        when(mPasspointManager.getProviderConfigs())
-                .thenReturn(new ArrayList<PasspointConfiguration>());
+        when(mPasspointManager.getProviderConfigs(anyInt(), anyBoolean()))
+                .thenReturn(new ArrayList<>());
         mLooper.startAutoDispatch();
-        assertTrue(mCmi.syncGetPasspointConfigs(mCmiAsyncChannel).isEmpty());
+        assertTrue(mCmi.syncGetPasspointConfigs(mCmiAsyncChannel, true).isEmpty());
         mLooper.stopAutoDispatch();
     }
 
@@ -2693,6 +2803,59 @@
     }
 
     /**
+     * Verify that we don't crash when WifiNative returns null as the current MAC address.
+     * @throws Exception
+     */
+    @Test
+    public void testMacRandomizationWifiNativeReturningNull() throws Exception {
+        when(mWifiNative.getMacAddress(anyString())).thenReturn(null);
+        initializeAndAddNetworkAndVerifySuccess();
+        assertEquals(ClientModeImpl.CONNECT_MODE, mCmi.getOperationalModeForTest());
+        assertEquals(WifiManager.WIFI_STATE_ENABLED, mCmi.syncGetWifiState());
+
+        connect();
+        verify(mWifiNative).setMacAddress(WIFI_IFACE_NAME, TEST_LOCAL_MAC_ADDRESS);
+    }
+
+    /**
+     * Verifies that a notification is posted when a connection failure happens on a network
+     * in the hotlist. Then verify that tapping on the notification launches an dialog, which
+     * could be used to set the randomization setting for a network to "Trusted".
+     */
+    @Test
+    public void testConnectionFailureSendRandomizationSettingsNotification() throws Exception {
+        when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true);
+        // Setup CONNECT_MODE & a WifiConfiguration
+        initializeAndAddNetworkAndVerifySuccess();
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+                WifiManager.ERROR_AUTH_FAILURE_TIMEOUT);
+        mLooper.dispatchAll();
+
+        WifiConfiguration config = mCmi.getCurrentWifiConfiguration();
+        verify(mConnectionFailureNotifier)
+                .showFailedToConnectDueToNoRandomizedMacSupportNotification(FRAMEWORK_NETWORK_ID);
+    }
+
+    /**
+     * Verifies that a notification is not posted when a wrong password failure happens on a
+     * network in the hotlist.
+     */
+    @Test
+    public void testNotCallingIsInFlakyRandomizationSsidHotlistOnWrongPassword() throws Exception {
+        when(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(anyInt())).thenReturn(true);
+        // Setup CONNECT_MODE & a WifiConfiguration
+        initializeAndAddNetworkAndVerifySuccess();
+        mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, FRAMEWORK_NETWORK_ID, 0, sBSSID);
+        mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
+                WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
+        mLooper.dispatchAll();
+
+        verify(mConnectionFailureNotifier, never())
+                .showFailedToConnectDueToNoRandomizedMacSupportNotification(anyInt());
+    }
+
+    /**
      * Verifies that CMD_START_CONNECT make WifiDiagnostics report
      * CONNECTION_EVENT_STARTED
      * @throws Exception
@@ -3285,7 +3448,7 @@
         when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(newLLStats);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
         mLooper.dispatchAll();
-        verify(mWifiDataStall).checkForDataStall(oldLLStats, newLLStats);
+        verify(mWifiDataStall).checkForDataStall(oldLLStats, newLLStats, mCmi.getWifiInfo());
         verify(mWifiMetrics).incrementWifiLinkLayerUsageStats(newLLStats);
     }
 
@@ -3301,7 +3464,7 @@
 
         WifiLinkLayerStats stats = new WifiLinkLayerStats();
         when(mWifiNative.getWifiLinkLayerStats(any())).thenReturn(stats);
-        when(mWifiDataStall.checkForDataStall(any(), any()))
+        when(mWifiDataStall.checkForDataStall(any(), any(), any()))
                 .thenReturn(WifiIsUnusableEvent.TYPE_UNKNOWN);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
         mLooper.dispatchAll();
@@ -3309,11 +3472,16 @@
         verify(mWifiMetrics, never()).addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
                 eq(anyInt()), eq(-1));
 
-        when(mWifiDataStall.checkForDataStall(any(), any()))
+        when(mWifiDataStall.checkForDataStall(any(), any(), any()))
                 .thenReturn(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
         mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
         mLooper.dispatchAll();
         verify(mWifiMetrics, times(2)).updateWifiUsabilityStatsEntries(any(), eq(stats));
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(10L + ClientModeImpl.DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS);
+        mCmi.sendMessage(ClientModeImpl.CMD_RSSI_POLL, 1);
+        mLooper.dispatchAll();
         verify(mWifiMetrics).addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
                 WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX, -1);
     }
@@ -3484,14 +3652,15 @@
     @Test
     public void testRemovePasspointConfig() throws Exception {
         String fqdn = "test.com";
-        when(mPasspointManager.removeProvider(anyInt(), anyString())).thenReturn(true);
+        when(mPasspointManager.removeProvider(anyInt(), anyBoolean(), anyString()))
+                .thenReturn(true);
 
         // switch to connect mode and verify wifi is reported as enabled
         startSupplicantAndDispatchMessages();
-        mCmi.sendMessage(ClientModeImpl.CMD_REMOVE_PASSPOINT_CONFIG, fqdn);
+        mCmi.sendMessage(ClientModeImpl.CMD_REMOVE_PASSPOINT_CONFIG, TEST_UID, 0, fqdn);
         mLooper.dispatchAll();
 
-        verify(mWifiConfigManager).removePasspointConfiguredNetwork(eq(fqdn));
+        verify(mWifiConfigManager).removePasspointConfiguredNetwork(fqdn);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java
new file mode 100644
index 0000000..8bf07b8
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/ConnectionFailureNotifierTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 com.android.server.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+import android.os.Handler;
+import android.os.Process;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ConnectionFailureNotifier}.
+ */
+@SmallTest
+public class ConnectionFailureNotifierTest {
+    @Mock private Context mContext;
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private Resources mResources;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private WifiConfigManager mWifiConfigManager;
+    @Mock private WifiConnectivityManager mWifiConnectivityManager;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
+    @Mock private Notification mNotification;
+    @Mock private AlertDialog mAlertDialog;
+
+    final ArgumentCaptor<BroadcastReceiver> mBroadCastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+    private ConnectionFailureNotifier mConnectionFailureNotifier;
+    TestLooper mLooper;
+
+    /** Initialize objects before each test run. */
+    @Before
+    public void setUp() throws Exception {
+        // Ensure looper exists
+        mLooper = new TestLooper();
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mWifiInjector.getNotificationManager()).thenReturn(mNotificationManager);
+        when(mWifiInjector.getConnectionFailureNotificationBuilder())
+                .thenReturn(mConnectionFailureNotificationBuilder);
+        when(mConnectionFailureNotificationBuilder
+                .buildNoMacRandomizationSupportNotification(any())).thenReturn(mNotification);
+        when(mConnectionFailureNotificationBuilder.buildChangeMacRandomizationSettingDialog(any(),
+                any())).thenReturn(mAlertDialog);
+        mConnectionFailureNotifier = new ConnectionFailureNotifier(mContext, mWifiInjector,
+                mFrameworkFacade, mWifiConfigManager, mWifiConnectivityManager,
+                new Handler(mLooper.getLooper()));
+
+        verify(mContext).registerReceiver(mBroadCastReceiverCaptor.capture(), any());
+    }
+
+    private class DisableMacRandomizationMatcher implements ArgumentMatcher<WifiConfiguration> {
+        @Override
+        public boolean matches(WifiConfiguration config) {
+            return config.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE;
+        }
+    }
+
+    // Returns an intent that simulates the broadcast which is received when the user tap
+    // on the notification to change MAC randomization settings.
+    private Intent buildBroadcastForRandomizationSettingsDialog(WifiConfiguration config) {
+        Intent intent = mock(Intent.class);
+        when(intent.getAction()).thenReturn(ConnectionFailureNotificationBuilder
+                .ACTION_SHOW_SET_RANDOMIZATION_DETAILS);
+        when(intent.getIntExtra(eq(ConnectionFailureNotificationBuilder
+                .RANDOMIZATION_SETTINGS_NETWORK_ID), anyInt())).thenReturn(config.networkId);
+        when(intent.getStringExtra(
+                eq(ConnectionFailureNotificationBuilder.RANDOMIZATION_SETTINGS_NETWORK_SSID)))
+                .thenReturn(config.getSsidAndSecurityTypeString());
+        return intent;
+    }
+
+    /**
+     * Verify that a notification is posted when a connection failure happens on a network
+     * in the hotlist. Then verify that tapping on the notification launches an dialog, which
+     * could be used to set the randomization setting for a network to "Trusted".
+     */
+    @Test
+    public void testConnectionFailureSendRandomizationSettingsNotification() {
+        // Verify that the network is using randomized MAC at the start.
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        when(mWifiConfigManager.getConfiguredNetwork(config.networkId)).thenReturn(config);
+        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT, config.macRandomizationSetting);
+
+        mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification(
+                config.networkId);
+        // verify that a notification is sent
+        verify(mNotificationManager).notify(
+                eq(ConnectionFailureNotifier.NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID),
+                eq(mNotification));
+
+        // sets up the intent that simulates the user tapping on the notification.
+        Intent intent = buildBroadcastForRandomizationSettingsDialog(config);
+
+        // simulate the user tapping on the notification, then verify the dialog shows up, and
+        // the appropriate callback is registered
+        ArgumentCaptor<DialogInterface.OnClickListener>  onClickListenerArgumentCaptor =
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+        mBroadCastReceiverCaptor.getValue().onReceive(mContext, intent);
+        verify(mConnectionFailureNotificationBuilder).buildChangeMacRandomizationSettingDialog(
+                eq(config.SSID), onClickListenerArgumentCaptor.capture());
+
+        // simulate the user tapping on the option to reset MAC address to factory MAC
+        onClickListenerArgumentCaptor.getValue().onClick(null, 0);
+        mLooper.dispatchAll();
+
+        // verify the WifiConfiguration is updated properly.
+        verify(mWifiConfigManager).addOrUpdateNetwork(
+                argThat(new DisableMacRandomizationMatcher()), eq(Process.SYSTEM_UID));
+        // verify that we try to connect to the updated network.
+        verify(mWifiConnectivityManager).forceConnectivityScan(any());
+    }
+
+    /**
+     * Verify that if the WifiConfiguration if not found (may have been deleted by the timed the
+     * notification is tapped), then the AlertDialog does not show up.
+     */
+    @Test
+    public void testWifiConfigurationMismatch() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        when(mWifiConfigManager.getConfiguredNetwork(config.networkId)).thenReturn(config);
+        mConnectionFailureNotifier.showFailedToConnectDueToNoRandomizedMacSupportNotification(
+                config.networkId);
+        // verify that a notification is sent
+        verify(mNotificationManager).notify(
+                eq(ConnectionFailureNotifier.NO_RANDOMIZED_MAC_SUPPORT_NOTIFICATION_ID),
+                any());
+
+        // sets up the intent that simulates the user tapping on the notification.
+        Intent intent = buildBroadcastForRandomizationSettingsDialog(config);
+
+        // the WifiConfiguration that is found doesn't match with the one received from broadcast.
+        when(mWifiConfigManager.getConfiguredNetwork(anyInt()))
+                .thenReturn(WifiConfigurationTestUtil.createOpenNetwork());
+        mBroadCastReceiverCaptor.getValue().onReceive(mContext, intent);
+
+        // verify that the AlertDialog is not launched in this case
+        verify(mConnectionFailureNotificationBuilder, never())
+                .buildChangeMacRandomizationSettingDialog(any(), any());
+
+        verify(mFrameworkFacade, never()).makeAlertDialogBuilder(any());
+        // instead we are showings a toast due to failing to find the network
+        verify(mFrameworkFacade).showToast(any(), any());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java
index 702aa99..17b9d1c 100644
--- a/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/DeletedEphemeralSsidsStoreDataTest.java
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -78,7 +79,8 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mDeletedEphemeralSsidsStoreData.serializeData(out);
+        mDeletedEphemeralSsidsStoreData.serializeData(
+                out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -94,7 +96,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mDeletedEphemeralSsidsStoreData.deserializeData(in, in.getDepth());
+        mDeletedEphemeralSsidsStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
         return mDeletedEphemeralSsidsStoreData.getSsidToTimeMap();
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
index bb71e4a..36da41b 100644
--- a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
@@ -107,7 +107,7 @@
 
     private class HalDeviceManagerSpy extends HalDeviceManager {
         HalDeviceManagerSpy() {
-            super(mClock);
+            super(mClock, mTestLooper.getLooper());
         }
 
         @Override
diff --git a/tests/wifitests/src/com/android/server/wifi/MacAddressUtilTest.java b/tests/wifitests/src/com/android/server/wifi/MacAddressUtilTest.java
new file mode 100644
index 0000000..7e598db
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/MacAddressUtilTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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 com.android.server.wifi;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.net.MacAddress;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.security.ProviderException;
+import java.util.Random;
+
+import javax.crypto.Mac;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.MacAddressUtil}.
+ */
+@SmallTest
+public class MacAddressUtilTest {
+    private MacAddressUtil mMacAddressUtil;
+
+    @Mock private Mac mMac;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mMacAddressUtil = new MacAddressUtil();
+    }
+
+    /**
+     * Verifies that calculatePersistentMacForConfiguration valid randomized MACs.
+     */
+    @Test
+    public void testCalculatePersistentMacForConfiguration() {
+        // verify null inputs
+        assertNull(mMacAddressUtil.calculatePersistentMacForConfiguration(null, null));
+
+        Random rand = new Random();
+        // Verify that a the MAC address calculated is valid
+        for (int i = 0; i < 10; i++) {
+            WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+
+            byte[] bytes = new byte[32];
+            rand.nextBytes(bytes);
+            when(mMac.doFinal(any())).thenReturn(bytes);
+            MacAddress macAddress = mMacAddressUtil.calculatePersistentMacForConfiguration(
+                    config, mMac);
+            assertTrue(WifiConfiguration.isValidMacAddressForRandomization(macAddress));
+        }
+    }
+
+    /**
+     * Verify the java.security.ProviderException is caught.
+     */
+    @Test
+    public void testCalculatePersistentMacCatchesException() {
+        when(mMac.doFinal(any())).thenThrow(new ProviderException("error occurred"));
+        try {
+            WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+            assertNull(mMacAddressUtil.calculatePersistentMacForConfiguration(config, mMac));
+        } catch (Exception e) {
+            fail("Exception not caught.");
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
index 7336c41..20b6c4f 100644
--- a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
@@ -31,6 +31,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtilTest;
 
 import org.junit.Before;
@@ -213,7 +214,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mNetworkListSharedStoreData.serializeData(out);
+        mNetworkListSharedStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -229,7 +230,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mNetworkListSharedStoreData.deserializeData(in, in.getDepth());
+        mNetworkListSharedStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
         return mNetworkListSharedStoreData.getConfigurations();
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java
index f40f71b..c0f0350 100644
--- a/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/NetworkRequestStoreDataTest.java
@@ -27,6 +27,7 @@
 
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.wifi.WifiNetworkFactory.AccessPoint;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -80,7 +81,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mNetworkRequestStoreData.serializeData(out);
+        mNetworkRequestStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -92,7 +93,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mNetworkRequestStoreData.deserializeData(in, in.getDepth());
+        mNetworkRequestStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
index 5c1dcb4..a35c510 100644
--- a/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
@@ -28,6 +28,7 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.PerAppInfo;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -119,7 +120,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mNetworkSuggestionStoreData.serializeData(out);
+        mNetworkSuggestionStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -131,7 +132,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mNetworkSuggestionStoreData.deserializeData(in, in.getDepth());
+        mNetworkSuggestionStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java
index 4df560f..cdd4e6c 100644
--- a/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/RandomizedMacStoreDataTest.java
@@ -24,6 +24,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,7 +63,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mRandomizedMacStoreData.serializeData(out);
+        mRandomizedMacStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -78,7 +79,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mRandomizedMacStoreData.deserializeData(in, in.getDepth());
+        mRandomizedMacStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
         return mRandomizedMacStoreData.getMacMapping();
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java b/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
index 9857898..38e2eaf 100644
--- a/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
@@ -66,6 +66,11 @@
                 add(new WifiScanner.ScanSettings.HiddenNetwork("test_ssid_2"));
 
             }};
+    private static final List<WifiScanner.ScanSettings.HiddenNetwork> TEST_HIDDEN_NETWORKS_LIST_NS =
+            new ArrayList<WifiScanner.ScanSettings.HiddenNetwork>() {{
+                add(new WifiScanner.ScanSettings.HiddenNetwork("test_ssid_3"));
+                add(new WifiScanner.ScanSettings.HiddenNetwork("test_ssid_4"));
+            }};
 
     @Mock private Context mContext;
     @Mock private AppOpsManager mAppOps;
@@ -77,6 +82,8 @@
     @Mock private WifiMetrics mWifiMetrics;
     @Mock private Clock mClock;
     @Mock private FrameworkFacade mFrameworkFacade;
+    @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
+
     private ArgumentCaptor<WorkSource> mWorkSourceArgumentCaptor =
             ArgumentCaptor.forClass(WorkSource.class);
     private ArgumentCaptor<WifiScanner.ScanSettings> mScanSettingsArgumentCaptor =
@@ -98,7 +105,11 @@
         MockitoAnnotations.initMocks(this);
 
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
+        when(mWifiInjector.getWifiNetworkSuggestionsManager())
+                .thenReturn(mWifiNetworkSuggestionsManager);
         when(mWifiConfigManager.retrieveHiddenNetworkList()).thenReturn(TEST_HIDDEN_NETWORKS_LIST);
+        when(mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList())
+                .thenReturn(TEST_HIDDEN_NETWORKS_LIST_NS);
         doNothing().when(mWifiScanner).registerScanListener(
                 mGlobalScanListenerArgumentCaptor.capture());
         doNothing().when(mWifiScanner).startScan(
@@ -106,7 +117,8 @@
                 mScanRequestListenerArgumentCaptor.capture(),
                 mWorkSourceArgumentCaptor.capture());
 
-        mInOrder = inOrder(mWifiScanner, mWifiConfigManager, mContext);
+        mInOrder = inOrder(mWifiScanner, mWifiConfigManager,
+                mContext, mWifiNetworkSuggestionsManager);
         mTestScanDatas1 =
                 ScanTestUtil.createScanDatas(new int[][]{{ 2417, 2427, 5180, 5170 }},
                         new int[]{0},
@@ -205,8 +217,8 @@
         mInOrder.verify(mWifiScanner).registerScanListener(any());
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
 
-        assertTrue(mWorkSourceArgumentCaptor.getValue().equals(
-                new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1)));
+        assertEquals(mWorkSourceArgumentCaptor.getValue(),
+                new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false, true);
     }
 
@@ -222,10 +234,11 @@
 
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
         mInOrder.verify(mWifiConfigManager, never()).retrieveHiddenNetworkList();
+        mInOrder.verify(mWifiNetworkSuggestionsManager, never()).retrieveHiddenNetworkList();
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
 
-        assertTrue(mWorkSourceArgumentCaptor.getValue().equals(
-                new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1)));
+        assertEquals(mWorkSourceArgumentCaptor.getValue(),
+                new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false);
 
         verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
@@ -242,11 +255,13 @@
         validateScanAvailableBroadcastSent(true);
 
         assertTrue(mScanRequestProxy.startScan(TEST_UID, TEST_PACKAGE_NAME_1));
+
         mInOrder.verify(mWifiConfigManager).retrieveHiddenNetworkList();
+        mInOrder.verify(mWifiNetworkSuggestionsManager).retrieveHiddenNetworkList();
         mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
 
-        assertTrue(mWorkSourceArgumentCaptor.getValue().equals(
-                new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1)));
+        assertEquals(mWorkSourceArgumentCaptor.getValue(),
+                new WorkSource(TEST_UID, TEST_PACKAGE_NAME_1));
         validateScanSettings(mScanSettingsArgumentCaptor.getValue(), true);
 
         verify(mWifiMetrics).incrementExternalAppOneshotScanRequestsCount();
@@ -860,12 +875,15 @@
         }
         assertEquals(WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
                 | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT, scanSettings.reportEvents);
+        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList =
+                new ArrayList<>();
+        hiddenNetworkList.addAll(TEST_HIDDEN_NETWORKS_LIST);
+        hiddenNetworkList.addAll(TEST_HIDDEN_NETWORKS_LIST_NS);
         if (expectHiddenNetworks) {
             assertNotNull(scanSettings.hiddenNetworks);
-            assertEquals(TEST_HIDDEN_NETWORKS_LIST.size(), scanSettings.hiddenNetworks.length);
+            assertEquals(hiddenNetworkList.size(), scanSettings.hiddenNetworks.length);
             for (int i = 0; i < scanSettings.hiddenNetworks.length; i++) {
-                validateHiddenNetworkInList(scanSettings.hiddenNetworks[i],
-                        TEST_HIDDEN_NETWORKS_LIST);
+                validateHiddenNetworkInList(scanSettings.hiddenNetworks[i], hiddenNetworkList);
             }
         } else {
             assertNull(scanSettings.hiddenNetworks);
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index ed70ef5..c08adc1 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -89,6 +89,7 @@
     @Mock WifiApConfigStore mWifiApConfigStore;
     @Mock WifiMetrics mWifiMetrics;
     @Mock SarManager mSarManager;
+    @Mock BaseWifiDiagnostics mWifiDiagnostics;
     final ArgumentCaptor<WifiNative.InterfaceCallback> mWifiNativeInterfaceCallbackCaptor =
             ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class);
     final ArgumentCaptor<WifiNative.SoftApListener> mSoftApListenerCaptor =
@@ -136,7 +137,8 @@
                                                            mWifiApConfigStore,
                                                            config,
                                                            mWifiMetrics,
-                                                           mSarManager);
+                                                           mSarManager,
+                                                           mWifiDiagnostics);
         mLooper.dispatchAll();
 
         return newSoftApManager;
@@ -209,7 +211,8 @@
                                                            mWifiApConfigStore,
                                                            nullApConfig,
                                                            mWifiMetrics,
-                                                           mSarManager);
+                                                           mSarManager,
+                                                           mWifiDiagnostics);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -252,7 +255,8 @@
                                                            mWifiApConfigStore,
                                                            nullApConfig,
                                                            mWifiMetrics,
-                                                           mSarManager);
+                                                           mSarManager,
+                                                           mWifiDiagnostics);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -294,7 +298,8 @@
                                                            mWifiApConfigStore,
                                                            nullApConfig,
                                                            mWifiMetrics,
-                                                           mSarManager);
+                                                           mSarManager,
+                                                           mWifiDiagnostics);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -335,7 +340,8 @@
                 mWifiApConfigStore,
                 softApConfig,
                 mWifiMetrics,
-                mSarManager);
+                mSarManager,
+                mWifiDiagnostics);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -381,7 +387,8 @@
                                                            mWifiApConfigStore,
                                                            softApConfig,
                                                            mWifiMetrics,
-                                                           mSarManager);
+                                                           mSarManager,
+                                                           mWifiDiagnostics);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -497,7 +504,8 @@
                                                            mWifiApConfigStore,
                                                            softApConfig,
                                                            mWifiMetrics,
-                                                           mSarManager);
+                                                           mSarManager,
+                                                           mWifiDiagnostics);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -535,7 +543,8 @@
                                                            mWifiApConfigStore,
                                                            softApModeConfig,
                                                            mWifiMetrics,
-                                                           mSarManager);
+                                                           mSarManager,
+                                                           mWifiDiagnostics);
 
         mLooper.dispatchAll();
         newSoftApManager.start();
@@ -589,6 +598,7 @@
 
         order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
         verify(mSarManager).setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED);
+        verify(mWifiDiagnostics).stopLogging(TEST_INTERFACE_NAME);
         order.verify(mContext).sendStickyBroadcastAsUser(intentCaptor.capture(),
                 eq(UserHandle.ALL));
         checkApStateChangedBroadcast(intentCaptor.getValue(), WIFI_AP_STATE_DISABLED,
@@ -1097,6 +1107,7 @@
         order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
         order.verify(mCallback).onNumClientsChanged(0);
         verify(mSarManager).setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED);
+        verify(mWifiDiagnostics).startLogging(TEST_INTERFACE_NAME);
         verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
                                                              eq(UserHandle.ALL));
         List<Intent> capturedIntents = intentCaptor.getAllValues();
diff --git a/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java
index ac6ae21..feedc0d 100644
--- a/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -29,6 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -80,7 +82,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mSsidSetStoreData.serializeData(out);
+        mSsidSetStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -95,7 +97,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mSsidSetStoreData.deserializeData(in, in.getDepth());
+        mSsidSetStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
index c814aef..df93eb4 100644
--- a/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -28,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import com.google.android.collect.Sets;
 
@@ -74,7 +76,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mWakeupConfigData.serializeData(out);
+        mWakeupConfigData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -88,7 +90,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mWakeupConfigData.deserializeData(in, in.getDepth());
+        mWakeupConfigData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
     }
 
     /**
@@ -177,7 +181,9 @@
      */
     @Test
     public void hasBeenReadIsTrueWhenUserStoreIsLoaded() throws Exception {
-        mWakeupConfigData.deserializeData(null /* in */, 0 /* outerTagDepth */);
+        mWakeupConfigData.deserializeData(null /* in */, 0 /* outerTagDepth */,
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
         assertTrue(mWakeupConfigData.hasBeenRead());
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
index 009429b..a004995 100644
--- a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -38,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wifi.util.ScanResultUtil;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -149,7 +151,9 @@
 
     private void readUserStore() {
         try {
-            mWakeupConfigStoreData.deserializeData(null, 0);
+            mWakeupConfigStoreData.deserializeData(null, 0,
+                    WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                    mock(WifiConfigStoreEncryptionUtil.class));
         } catch (XmlPullParserException | IOException e) {
             // unreachable
         }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index 49302f8..71d06fb 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -42,6 +42,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.TestLooper;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -70,6 +71,7 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -135,7 +137,11 @@
     @Mock private WifiConfigManager.OnSavedNetworkUpdateListener mWcmListener;
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private CarrierNetworkConfig mCarrierNetworkConfig;
+    @Mock private MacAddressUtil mMacAddressUtil;
+    @Mock DeviceConfigFacade mDeviceConfigFacade;
 
+    final ArgumentCaptor<OnPropertiesChangedListener> mOnPropertiesChangedListenerCaptor =
+            ArgumentCaptor.forClass(OnPropertiesChangedListener.class);
     private MockResources mResources;
     private InOrder mContextConfigStoreMockOrder;
     private InOrder mNetworkListStoreDataMockOrder;
@@ -167,7 +173,10 @@
         mResources.setInteger(
                 R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
                 TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN);
+        mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, true);
         when(mContext.getResources()).thenReturn(mResources);
+        when(mDeviceConfigFacade.getRandomizationFlakySsidHotlist()).thenReturn(
+                Collections.emptySet());
 
         // Setup UserManager profiles for the default user.
         setupUserProfiles(TEST_DEFAULT_USER);
@@ -215,6 +224,9 @@
         when(mWifiInjector.getWifiLastResortWatchdog().shouldIgnoreSsidUpdate())
                 .thenReturn(false);
         when(mWifiInjector.getCarrierNetworkConfig()).thenReturn(mCarrierNetworkConfig);
+        when(mWifiInjector.getMacAddressUtil()).thenReturn(mMacAddressUtil);
+        when(mMacAddressUtil.calculatePersistentMacForConfiguration(any(), any()))
+                .thenReturn(TEST_RANDOMIZED_MAC);
         when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(true);
 
         createWifiConfigManager();
@@ -232,13 +244,12 @@
         // static mocking
         mSession = ExtendedMockito.mockitoSession()
                 .mockStatic(WifiConfigStore.class, withSettings().lenient())
-                .spyStatic(WifiConfigurationUtil.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
-        when(WifiConfigStore.createUserFiles(anyInt())).thenReturn(mock(List.class));
+        when(WifiConfigStore.createUserFiles(anyInt(), anyBoolean())).thenReturn(mock(List.class));
         when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager);
-        when(WifiConfigurationUtil.calculatePersistentMacForConfiguration(any(), any()))
-                .thenReturn(TEST_RANDOMIZED_MAC);
+        verify(mDeviceConfigFacade).addOnPropertiesChangedListener(any(),
+                mOnPropertiesChangedListenerCaptor.capture());
     }
 
     /**
@@ -297,8 +308,7 @@
      */
     @Test
     public void testRandomizedMacIsGeneratedEvenIfKeyStoreFails() {
-        when(WifiConfigurationUtil.calculatePersistentMacForConfiguration(
-                any(), any())).thenReturn(null);
+        when(mMacAddressUtil.calculatePersistentMacForConfiguration(any(), any())).thenReturn(null);
 
         // Try adding a network.
         WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
@@ -308,8 +318,11 @@
         List<WifiConfiguration> retrievedNetworks =
                 mWifiConfigManager.getConfiguredNetworksWithPasswords();
 
-        // Verify that despite KeyStore returning null, we are still getting a valid MAC address.
+        // Verify that we have attempted to generate the MAC address twice (1 retry)
+        verify(mMacAddressUtil, times(2)).calculatePersistentMacForConfiguration(any(), any());
         assertEquals(1, retrievedNetworks.size());
+
+        // Verify that despite KeyStore returning null, we are still getting a valid MAC address.
         assertNotEquals(WifiInfo.DEFAULT_MAC_ADDRESS,
                 retrievedNetworks.get(0).getRandomizedMacAddress().toString());
     }
@@ -1984,6 +1997,44 @@
     }
 
     /**
+     * Verifies that macRandomizationSetting is not masked out when MAC randomization is supported.
+     */
+    @Test
+    public void testGetConfiguredNetworksNotMaskMacRandomizationSetting() {
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(config);
+
+        MacAddress testMac = MacAddress.createRandomUnicastAddress();
+        mWifiConfigManager.setNetworkRandomizedMacAddress(result.getNetworkId(), testMac);
+
+        // Verify macRandomizationSetting is not masked out when feature is supported.
+        List<WifiConfiguration> configs = mWifiConfigManager.getSavedNetworks(Process.WIFI_UID);
+        assertEquals(1, configs.size());
+        assertEquals(WifiConfiguration.RANDOMIZATION_PERSISTENT,
+                configs.get(0).macRandomizationSetting);
+    }
+
+    /**
+     * Verifies that macRandomizationSetting is masked out to WifiConfiguration.RANDOMIZATION_NONE
+     * when MAC randomization is not supported on the device.
+     */
+    @Test
+    public void testGetConfiguredNetworksMasksMacRandomizationSetting() {
+        mResources.setBoolean(R.bool.config_wifi_connected_mac_randomization_supported, false);
+        createWifiConfigManager();
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(config);
+
+        MacAddress testMac = MacAddress.createRandomUnicastAddress();
+        mWifiConfigManager.setNetworkRandomizedMacAddress(result.getNetworkId(), testMac);
+
+        // Verify macRandomizationSetting is masked out when feature is unsupported.
+        List<WifiConfiguration> configs = mWifiConfigManager.getSavedNetworks(Process.WIFI_UID);
+        assertEquals(1, configs.size());
+        assertEquals(WifiConfiguration.RANDOMIZATION_NONE, configs.get(0).macRandomizationSetting);
+    }
+
+    /**
      * Verifies that passwords are masked out when we return external configs except when
      * explicitly asked for them.
      */
@@ -4622,7 +4673,7 @@
                         mWifiPermissionsUtil, mWifiPermissionsWrapper, mWifiInjector,
                         mNetworkListSharedStoreData, mNetworkListUserStoreData,
                         mDeletedEphemeralSsidsStoreData, mRandomizedMacStoreData,
-                        mFrameworkFacade, mLooper.getLooper());
+                        mFrameworkFacade, mLooper.getLooper(), mDeviceConfigFacade);
         mWifiConfigManager.enableVerboseLogging(1);
     }
 
@@ -5351,4 +5402,32 @@
         assertFalse(mWifiConfigManager.getConfiguredNetwork(networkId)
                     .getNetworkSelectionStatus().isNetworkTemporaryDisabled());
     }
+
+    /**
+     * Verifies that isInFlakyRandomizationSsidHotlist returns true if the network's SSID is in
+     * the hotlist and the network is using randomized MAC.
+     */
+    @Test
+    public void testFlakyRandomizationSsidHotlist() {
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
+        int networkId = result.getNetworkId();
+
+        // should return false when there is nothing in the hotlist
+        assertFalse(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(networkId));
+
+        // add the network's SSID to the hotlist and verify the method returns true
+        Set<String> ssidHotlist = new HashSet<>();
+        ssidHotlist.add(openNetwork.SSID);
+        when(mDeviceConfigFacade.getRandomizationFlakySsidHotlist()).thenReturn(ssidHotlist);
+        mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null);
+        assertTrue(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(networkId));
+
+        // Now change the macRandomizationSetting to "trusted" and then verify
+        // isInFlakyRandomizationSsidHotlist returns false
+        openNetwork.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+        NetworkUpdateResult networkUpdateResult = updateNetworkToWifiConfigManager(openNetwork);
+        assertNotEquals(WifiConfiguration.INVALID_NETWORK_ID, networkUpdateResult.getNetworkId());
+        assertFalse(mWifiConfigManager.isInFlakyRandomizationSsidHotlist(networkId));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
index 64e762b..efa2d43 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
@@ -31,8 +31,12 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.wifi.WifiConfigStore.StoreData;
 import com.android.server.wifi.WifiConfigStore.StoreFile;
+import com.android.server.wifi.util.EncryptedData;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.XmlUtil;
 
+import libcore.util.HexEncoding;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -49,6 +53,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Random;
 
 /**
  * Unit tests for {@link com.android.server.wifi.WifiConfigStore}.
@@ -64,7 +69,7 @@
     private static final String TEST_DATA_XML_STRING_FORMAT =
             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                     + "<WifiConfigStoreData>\n"
-                    + "<int name=\"Version\" value=\"1\" />\n"
+                    + "<int name=\"Version\" value=\"3\" />\n"
                     + "<NetworkList>\n"
                     + "<Network>\n"
                     + "<WifiConfiguration>\n"
@@ -127,19 +132,29 @@
                     + "</DeletedEphemeralSSIDList>\n"
                     + "</WifiConfigStoreData>\n";
 
-    private static final String TEST_DATA_XML_STRING_FORMAT_WITH_ONE_DATA_SOURCE =
+    private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE =
             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                     + "<WifiConfigStoreData>\n"
                     + "<int name=\"Version\" value=\"1\" />\n"
                     + "<%s/>n"
                     + "</WifiConfigStoreData>\n";
-    private static final String TEST_DATA_XML_STRING_FORMAT_WITH_TWO_DATA_SOURCE =
+    private static final String TEST_DATA_XML_STRING_FORMAT_V1_WITH_TWO_DATA_SOURCE =
             "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                     + "<WifiConfigStoreData>\n"
                     + "<int name=\"Version\" value=\"1\" />\n"
                     + "<%s/>n"
                     + "<%s/>n"
                     + "</WifiConfigStoreData>\n";
+    private static final String TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE =
+            "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                    + "<WifiConfigStoreData>\n"
+                    + "<int name=\"Version\" value=\"2\" />\n"
+                    + "<Integrity>\n"
+                    + "<byte-array name=\"EncryptedData\" num=\"48\">%s</byte-array>\n"
+                    + "<byte-array name=\"IV\" num=\"12\">%s</byte-array>\n"
+                    + "</Integrity>\n"
+                    + "<%s />\n"
+                    + "</WifiConfigStoreData>\n";
     // Test mocks
     @Mock private Context mContext;
     @Mock private PackageManager mPackageManager;
@@ -147,6 +162,7 @@
     private TestLooper mLooper;
     @Mock private Clock mClock;
     @Mock private WifiMetrics mWifiMetrics;
+    @Mock private WifiConfigStoreEncryptionUtil mEncryptionUtil;
     private MockStoreFile mSharedStore;
     private MockStoreFile mUserStore;
     private MockStoreFile mUserNetworkSuggestionsStore;
@@ -170,6 +186,10 @@
                 .thenReturn(mAlarmManager.getAlarmManager());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getNameForUid(anyInt())).thenReturn(TEST_CREATOR_NAME);
+        when(mEncryptionUtil.encrypt(any(byte[].class)))
+                .thenReturn(new EncryptedData(new byte[0], new byte[0]));
+        when(mEncryptionUtil.decrypt(any(EncryptedData.class)))
+                .thenReturn(new byte[0]);
         mSharedStore = new MockStoreFile(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
         mUserStore = new MockStoreFile(WifiConfigStore.STORE_FILE_USER_GENERAL);
         mUserNetworkSuggestionsStore =
@@ -402,9 +422,9 @@
 
         // Ensure that we got the call to deserialize empty shared data, but no user data.
         verify(sharedStoreData).resetData();
-        verify(sharedStoreData).deserializeData(eq(null), anyInt());
+        verify(sharedStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
         verify(userStoreData, never()).resetData();
-        verify(userStoreData, never()).deserializeData(any(), anyInt());
+        verify(userStoreData, never()).deserializeData(any(), anyInt(), anyInt(), any());
     }
 
     /**
@@ -432,9 +452,9 @@
 
         // Ensure that we got the call to deserialize empty shared & user data.
         verify(userStoreData).resetData();
-        verify(userStoreData).deserializeData(eq(null), anyInt());
+        verify(userStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
         verify(sharedStoreData).resetData();
-        verify(sharedStoreData).deserializeData(eq(null), anyInt());
+        verify(sharedStoreData).deserializeData(eq(null), anyInt(), anyInt(), any());
     }
 
     /**
@@ -591,11 +611,11 @@
         assertTrue(mWifiConfigStore.registerStoreData(storeData2));
 
         String fileContentsXmlStringWithOnlyStoreData1 =
-                String.format(TEST_DATA_XML_STRING_FORMAT_WITH_ONE_DATA_SOURCE, storeData1Name);
+                String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, storeData1Name);
         String fileContentsXmlStringWithOnlyStoreData2 =
-                String.format(TEST_DATA_XML_STRING_FORMAT_WITH_ONE_DATA_SOURCE, storeData2Name);
+                String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE, storeData2Name);
         String fileContentsXmlStringWithStoreData1AndStoreData2 =
-                String.format(TEST_DATA_XML_STRING_FORMAT_WITH_TWO_DATA_SOURCE,
+                String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_TWO_DATA_SOURCE,
                         storeData1Name, storeData2Name);
 
         // Scenario 1: StoreData1 in shared store file.
@@ -609,9 +629,9 @@
         mUserStore.storeRawDataToWrite(null);
 
         mWifiConfigStore.read();
-        verify(storeData1).deserializeData(notNull(), anyInt());
-        verify(storeData1, never()).deserializeData(eq(null), anyInt());
-        verify(storeData2).deserializeData(eq(null), anyInt());
+        verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
+        verify(storeData2).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
 
         // Scenario 2: StoreData2 in user store file.
@@ -625,9 +645,9 @@
         mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes());
 
         mWifiConfigStore.read();
-        verify(storeData1).deserializeData(eq(null), anyInt());
-        verify(storeData2).deserializeData(notNull(), anyInt());
-        verify(storeData2, never()).deserializeData(eq(null), anyInt());
+        verify(storeData1).deserializeData(eq(null), anyInt(), anyInt(), any());
+        verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
 
         // Scenario 3: StoreData1 in shared store file & StoreData2 in user store file.
@@ -641,10 +661,10 @@
         mUserStore.storeRawDataToWrite(fileContentsXmlStringWithOnlyStoreData2.getBytes());
 
         mWifiConfigStore.read();
-        verify(storeData1).deserializeData(notNull(), anyInt());
-        verify(storeData1, never()).deserializeData(eq(null), anyInt());
-        verify(storeData2).deserializeData(notNull(), anyInt());
-        verify(storeData2, never()).deserializeData(eq(null), anyInt());
+        verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
+        verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
 
         // Scenario 4: StoreData1 & StoreData2 in shared store file.
@@ -659,10 +679,10 @@
         mUserStore.storeRawDataToWrite(null);
 
         mWifiConfigStore.read();
-        verify(storeData1).deserializeData(notNull(), anyInt());
-        verify(storeData1, never()).deserializeData(eq(null), anyInt());
-        verify(storeData2).deserializeData(notNull(), anyInt());
-        verify(storeData2, never()).deserializeData(eq(null), anyInt());
+        verify(storeData1).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData1, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
+        verify(storeData2).deserializeData(notNull(), anyInt(), anyInt(), any());
+        verify(storeData2, never()).deserializeData(eq(null), anyInt(), anyInt(), any());
         reset(storeData1, storeData2);
     }
 
@@ -709,9 +729,9 @@
         verify(userStoreNetworkSuggestionsData).hasNewDataToSerialize();
 
         // Verify that we serialized data from the first 2 data source, but not from the last one.
-        verify(sharedStoreData).serializeData(any());
-        verify(userStoreData).serializeData(any());
-        verify(userStoreNetworkSuggestionsData, never()).serializeData(any());
+        verify(sharedStoreData).serializeData(any(), any());
+        verify(userStoreData).serializeData(any(), any());
+        verify(userStoreNetworkSuggestionsData, never()).serializeData(any(), any());
     }
 
     /**
@@ -753,6 +773,98 @@
     }
 
     /**
+     * Tests the read API behaviour when the config store file is version 1.
+     * Expected behaviour: The read should be successful and send the data to the corresponding
+     *                     {@link StoreData} instance.
+     */
+    @Test
+    public void testReadVersion1StoreFile() throws Exception {
+        // Register data container.
+        StoreData sharedStoreData = mock(StoreData.class);
+        when(sharedStoreData.getStoreFileId())
+                .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
+        when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA);
+        StoreData userStoreData = mock(StoreData.class);
+        when(userStoreData.getStoreFileId())
+                .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
+        when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
+        mWifiConfigStore.registerStoreData(sharedStoreData);
+        mWifiConfigStore.registerStoreData(userStoreData);
+
+        // Read both share and user config store.
+        mWifiConfigStore.setUserStores(mUserStores);
+
+        // Now store some content in the shared and user data files.
+        mUserStore.storeRawDataToWrite(
+                String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE,
+                        TEST_USER_DATA).getBytes());
+        mSharedStore.storeRawDataToWrite(
+                String.format(TEST_DATA_XML_STRING_FORMAT_V1_WITH_ONE_DATA_SOURCE,
+                        TEST_SHARE_DATA).getBytes());
+
+        // Read and verify the data content in the store file (metadata stripped out) has been sent
+        // to the corresponding store data when integrity check passes.
+        mWifiConfigStore.read();
+        verify(sharedStoreData, times(1)).deserializeData(
+                any(XmlPullParser.class), anyInt(),
+                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any());
+        verify(userStoreData, times(1)).deserializeData(
+                any(XmlPullParser.class), anyInt(),
+                eq(WifiConfigStore.INITIAL_CONFIG_STORE_DATA_VERSION), any());
+    }
+
+    /**
+     * Tests the read API behaviour to ensure that the integrity data is parsed from the file.
+     */
+    @Test
+    public void testReadVersion2StoreFile() throws Exception {
+        byte[] encryptedData = new byte[0];
+        byte[] iv = new byte[0];
+        Random random = new Random();
+        random.nextBytes(encryptedData);
+        random.nextBytes(iv);
+
+        // Register data container.
+        StoreData sharedStoreData = mock(StoreData.class);
+        when(sharedStoreData.getStoreFileId())
+                .thenReturn(WifiConfigStore.STORE_FILE_SHARED_GENERAL);
+        when(sharedStoreData.getName()).thenReturn(TEST_SHARE_DATA);
+        when(sharedStoreData.hasNewDataToSerialize()).thenReturn(true);
+        StoreData userStoreData = mock(StoreData.class);
+        when(userStoreData.getStoreFileId())
+                .thenReturn(WifiConfigStore.STORE_FILE_USER_GENERAL);
+        when(userStoreData.getName()).thenReturn(TEST_USER_DATA);
+        when(userStoreData.hasNewDataToSerialize()).thenReturn(true);
+        mWifiConfigStore.registerStoreData(sharedStoreData);
+        mWifiConfigStore.registerStoreData(userStoreData);
+
+        // Read both share and user config store.
+        mWifiConfigStore.setUserStores(mUserStores);
+
+        // Now store some content in the shared and user data files with encrypted data from above.
+        mUserStore.storeRawDataToWrite(
+                String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE,
+                        HexEncoding.encodeToString(encryptedData),
+                        HexEncoding.encodeToString(iv),
+                        TEST_USER_DATA).getBytes());
+        mSharedStore.storeRawDataToWrite(
+                String.format(TEST_DATA_XML_STRING_FORMAT_V2_WITH_ONE_DATA_SOURCE,
+                        HexEncoding.encodeToString(encryptedData),
+                        HexEncoding.encodeToString(iv),
+                        TEST_SHARE_DATA).getBytes());
+
+        // Read and verify the data content in the store file (metadata stripped out) has been sent
+        // to the corresponding store data.
+        mWifiConfigStore.read();
+        verify(sharedStoreData, times(1))
+                .deserializeData(any(XmlPullParser.class), anyInt(),
+                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any());
+        verify(userStoreData, times(1))
+                .deserializeData(any(XmlPullParser.class), anyInt(),
+                        eq(WifiConfigStore.INTEGRITY_CONFIG_STORE_DATA_VERSION), any());
+    }
+
+    /**
      * Mock Store File to redirect all file writes from WifiConfigStore to local buffers.
      * This can be used to examine the data output by WifiConfigStore.
      */
@@ -761,7 +873,7 @@
         private boolean mStoreWritten;
 
         MockStoreFile(@WifiConfigStore.StoreFileId int fileId) {
-            super(new File("MockStoreFile"), fileId);
+            super(new File("MockStoreFile"), fileId, mEncryptionUtil);
         }
 
         @Override
@@ -812,13 +924,14 @@
         }
 
         @Override
-        public void serializeData(XmlSerializer out)
+        public void serializeData(XmlSerializer out, WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException {
             XmlUtil.writeNextValue(out, XML_TAG_TEST_DATA, mData);
         }
 
         @Override
-        public void deserializeData(XmlPullParser in, int outerTagDepth)
+        public void deserializeData(XmlPullParser in, int outerTagDepth, int version,
+                WifiConfigStoreEncryptionUtil encryptionUtil)
                 throws XmlPullParserException, IOException {
             if (in == null) {
                 return;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 7f6c1bc..c1686b4 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -96,6 +96,10 @@
         mWifiConnectivityHelper = mockWifiConnectivityHelper();
         mWifiNS = mockWifiNetworkSelector();
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
+        when(mWifiInjector.getWifiNetworkSuggestionsManager())
+                .thenReturn(mWifiNetworkSuggestionsManager);
+        when(mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList())
+                .thenReturn(new ArrayList<>());
         mWifiConnectivityManager = createConnectivityManager();
         verify(mWifiConfigManager).setOnSavedNetworkUpdateListener(anyObject());
         mWifiConnectivityManager.setTrustedConnectionAllowed(true);
@@ -140,6 +144,7 @@
     @Mock private CarrierNetworkConfig mCarrierNetworkConfig;
     @Mock private WifiMetrics mWifiMetrics;
     @Mock private WifiNetworkScoreCache mScoreCache;
+    @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     @Captor ArgumentCaptor<ScanResult> mCandidateScanResultCaptor;
     @Captor ArgumentCaptor<ArrayList<String>> mBssidBlacklistCaptor;
     @Captor ArgumentCaptor<ArrayList<String>> mSsidWhitelistCaptor;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java b/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
index a5f6852..71b6589 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
@@ -17,14 +17,17 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.net.wifi.WifiInfo;
+import android.os.Looper;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
@@ -33,6 +36,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -46,9 +50,15 @@
     @Mock FrameworkFacade mFacade;
     @Mock WifiMetrics mWifiMetrics;
     WifiDataStall mWifiDataStall;
+    @Mock Clock mClock;
+    @Mock DeviceConfigFacade mDeviceConfigFacade;
+    @Mock Looper mClientModeImplLooper;
+    @Mock WifiInfo mWifiInfo;
 
     private final WifiLinkLayerStats mOldLlStats = new WifiLinkLayerStats();
     private final WifiLinkLayerStats mNewLlStats = new WifiLinkLayerStats();
+    final ArgumentCaptor<OnPropertiesChangedListener> mOnPropertiesChangedListenerCaptor =
+            ArgumentCaptor.forClass(OnPropertiesChangedListener.class);
 
     /**
      * Sets up for unit test
@@ -64,21 +74,38 @@
                 Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
                 WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT))
                 .thenReturn(WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT);
+        when(mDeviceConfigFacade.getDataStallDurationMs()).thenReturn(
+                DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        when(mDeviceConfigFacade.getDataStallTxTputThrMbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_DATA_STALL_TX_TPUT_THR_MBPS);
+        when(mDeviceConfigFacade.getDataStallRxTputThrMbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_DATA_STALL_RX_TPUT_THR_MBPS);
+        when(mDeviceConfigFacade.getDataStallTxPerThr()).thenReturn(
+                DeviceConfigFacade.DEFAULT_DATA_STALL_TX_PER_THR);
+        when(mDeviceConfigFacade.getDataStallCcaLevelThr()).thenReturn(
+                DeviceConfigFacade.DEFAULT_DATA_STALL_CCA_LEVEL_THR);
+        when(mWifiInfo.getLinkSpeed()).thenReturn(100);
+        when(mWifiInfo.getRxLinkSpeedMbps()).thenReturn(100);
+        when(mWifiInfo.getFrequency()).thenReturn(5850);
+        when(mWifiInfo.getBSSID()).thenReturn("5G_WiFi");
 
-        mWifiDataStall = new WifiDataStall(mContext, mFacade, mWifiMetrics);
+        mWifiDataStall = new WifiDataStall(mContext, mFacade, mWifiMetrics, mDeviceConfigFacade,
+                mClientModeImplLooper, mClock);
 
         mOldLlStats.txmpdu_be = 1000;
-        mOldLlStats.retries_be = 2000;
+        mOldLlStats.retries_be = 1000;
         mOldLlStats.lostmpdu_be = 3000;
         mOldLlStats.rxmpdu_be = 4000;
         mOldLlStats.timeStampInMs = 10000;
 
-        mNewLlStats.txmpdu_be = mOldLlStats.txmpdu_be;
-        mNewLlStats.retries_be = mOldLlStats.retries_be;
+        mNewLlStats.txmpdu_be = 2 * mOldLlStats.txmpdu_be;
+        mNewLlStats.retries_be = 10 * mOldLlStats.retries_be;
         mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be;
         mNewLlStats.rxmpdu_be = mOldLlStats.rxmpdu_be;
         mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs
                 + WifiDataStall.MAX_MS_DELTA_FOR_DATA_STALL - 1;
+        verify(mDeviceConfigFacade).addOnPropertiesChangedListener(any(),
+                mOnPropertiesChangedListenerCaptor.capture());
     }
 
     /**
@@ -98,23 +125,53 @@
      */
     @Test
     public void verifyDataStallTxFailure() throws Exception {
-        mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be + WifiDataStall.MIN_TX_BAD_DEFAULT;
-        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verify(mWifiMetrics).logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
     }
 
     /**
+     * Verify there is no data stall from tx failures if tx failures are not consecutively bad
+     */
+    @Test
+    public void verifyNoDataStallWhenTxFailureIsNotConsecutive() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
+        verifyUpdateWifiIsUnusableLinkLayerStats();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        mNewLlStats.retries_be = 2 * mOldLlStats.retries_be;
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(
+                WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
+    }
+
+    /**
      * Verify there is data stall from rx failures
      */
     @Test
     public void verifyDataStallRxFailure() throws Exception {
-        mNewLlStats.txmpdu_be =
-                mOldLlStats.txmpdu_be + WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT;
-        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+        when(mWifiInfo.getRxLinkSpeedMbps()).thenReturn(1);
+        mNewLlStats.retries_be = 2 * mOldLlStats.retries_be;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verify(mWifiMetrics).logWifiIsUnusableEvent(
                 WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX);
     }
@@ -124,58 +181,61 @@
      */
     @Test
     public void verifyDataStallBothTxRxFailure() throws Exception {
-        mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be + WifiDataStall.MIN_TX_BAD_DEFAULT;
-        mNewLlStats.txmpdu_be =
-                mOldLlStats.txmpdu_be + WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT;
-        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+        when(mWifiInfo.getRxLinkSpeedMbps()).thenReturn(1);
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        assertEquals(WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verify(mWifiMetrics).logWifiIsUnusableEvent(WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH);
     }
 
     /**
-     * Verify that we can change the minimum number of tx failures
-     * to trigger data stall from settings
+     * Verify that we can change the duration of evaluating Wifi conditions
+     * to trigger data stall from DeviceConfigFacade
      */
     @Test
-    public void verifyDataStallTxFailureSettingsChange() throws Exception {
-        when(mFacade.getIntegerSetting(mContext,
-                Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD,
-                WifiDataStall.MIN_TX_BAD_DEFAULT))
-                .thenReturn(WifiDataStall.MIN_TX_BAD_DEFAULT + 1);
-        mWifiDataStall.loadSettings();
-        verify(mWifiMetrics).setWifiDataStallMinTxBad(WifiDataStall.MIN_TX_BAD_DEFAULT + 1);
-        verify(mWifiMetrics, times(2)).setWifiDataStallMinRxWithoutTx(
-                WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT);
+    public void verifyDataStallDurationDeviceConfigChange() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+        when(mDeviceConfigFacade.getDataStallDurationMs()).thenReturn(
+                DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS + 1);
+        mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null);
 
-        mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be + WifiDataStall.MIN_TX_BAD_DEFAULT;
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(
+                WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
     }
 
     /**
-     * Verify that we can change the minimum number of tx successes when rx success is 0
-     * to trigger data stall from settings
+     * Verify that we can change the threshold of Tx packet error rate to trigger a data stall
+     * from DeviceConfigFacade
      */
     @Test
-    public void verifyDataStallRxFailureSettingsChange() throws Exception {
-        when(mFacade.getIntegerSetting(mContext,
-                Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
-                WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT))
-                .thenReturn(WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT + 1);
-        mWifiDataStall.loadSettings();
-        verify(mWifiMetrics, times(2)).setWifiDataStallMinTxBad(WifiDataStall.MIN_TX_BAD_DEFAULT);
-        verify(mWifiMetrics).setWifiDataStallMinRxWithoutTx(
-                WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT + 1);
+    public void verifyDataStallTxPerThrDeviceConfigChange() throws Exception {
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(10L);
+        when(mDeviceConfigFacade.getDataStallTxPerThr()).thenReturn(
+                DeviceConfigFacade.DEFAULT_DATA_STALL_TX_PER_THR + 1);
+        mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null);
 
-        mNewLlStats.txmpdu_be =
-                mOldLlStats.txmpdu_be + WifiDataStall.MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT;
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
-        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                10L + DeviceConfigFacade.DEFAULT_DATA_STALL_DURATION_MS);
+        assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
+        verify(mWifiMetrics, never()).logWifiIsUnusableEvent(
+                WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX);
     }
 
     /**
@@ -184,7 +244,7 @@
     @Test
     public void verifyNoDataStallWhenNoFail() throws Exception {
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verify(mWifiMetrics, never()).resetWifiIsUnusableLinkLayerStats();
         verifyUpdateWifiIsUnusableLinkLayerStats();
         verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
@@ -196,11 +256,10 @@
      */
     @Test
     public void verifyNoDataStallBigTimeGap() throws Exception {
-        mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be + WifiDataStall.MIN_TX_BAD_DEFAULT;
         mNewLlStats.timeStampInMs = mOldLlStats.timeStampInMs
                 + WifiDataStall.MAX_MS_DELTA_FOR_DATA_STALL + 1;
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verifyUpdateWifiIsUnusableLinkLayerStats();
         verify(mWifiMetrics, never()).logWifiIsUnusableEvent(anyInt());
     }
@@ -212,7 +271,7 @@
     public void verifyReset() throws Exception {
         mNewLlStats.lostmpdu_be = mOldLlStats.lostmpdu_be - 1;
         assertEquals(WifiIsUnusableEvent.TYPE_UNKNOWN,
-                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats));
+                mWifiDataStall.checkForDataStall(mOldLlStats, mNewLlStats, mWifiInfo));
         verify(mWifiMetrics).resetWifiIsUnusableLinkLayerStats();
         verify(mWifiMetrics, never()).updateWifiIsUnusableLinkLayerStats(
                 anyLong(), anyLong(), anyLong(), anyLong(), anyLong());
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
index 6b63137..9883737 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiDiagnosticsTest.java
@@ -74,6 +74,8 @@
     WifiDiagnostics mWifiDiagnostics;
 
     private static final String FAKE_RING_BUFFER_NAME = "fake-ring-buffer";
+    private static final String STA_IF_NAME = "wlan0";
+    private static final String AP_IF_NAME = "wlan1";
     private static final int SMALL_RING_BUFFER_SIZE_KB = 32;
     private static final int LARGE_RING_BUFFER_SIZE_KB = 1024;
     private static final int BYTES_PER_KBYTE = 1024;
@@ -137,8 +139,8 @@
     /** Verifies that startLogging() registers a logging event handler. */
     @Test
     public void startLoggingRegistersLogEventHandler() throws Exception {
-        final boolean verbosityToggle = false;  // even default mode registers handler
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(false);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
     }
 
@@ -152,12 +154,15 @@
         final boolean verbosityToggle = false;  // even default mode registers handler
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(false);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
+        mWifiDiagnostics.stopLogging(STA_IF_NAME);
         reset(mWifiNative);
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
     }
 
@@ -165,14 +170,14 @@
     @Test
     public void startLoggingDoesNotRegisterLogEventHandlerIfPriorAttemptSucceeded()
             throws Exception {
-        final boolean verbosityToggle = false;  // even default mode registers handler
-
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(false);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         verify(mWifiNative).setLoggingEventHandler(anyObject());
         reset(mWifiNative);
 
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(false);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         verify(mWifiNative, never()).setLoggingEventHandler(anyObject());
     }
 
@@ -186,7 +191,8 @@
     @Test
     public void startLoggingStopsAndRestartsRingBufferLoggingInVerboseMode() throws Exception {
         final boolean verbosityToggle = true;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         verify(mWifiNative).startLoggingRingBuffer(
                 eq(WifiDiagnostics.VERBOSE_NO_LOG), anyInt(), anyInt(), anyInt(),
                 eq(FAKE_RING_BUFFER_NAME));
@@ -198,7 +204,8 @@
     @Test
     public void startLoggingStopsAndThenStartRingBufferLoggingInNormalMode() throws Exception {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         verify(mWifiNative).startLoggingRingBuffer(
                 eq(WifiDiagnostics.VERBOSE_NO_LOG), anyInt(), anyInt(), anyInt(),
                 eq(FAKE_RING_BUFFER_NAME));
@@ -212,18 +219,19 @@
     public void stopLoggingResetsLogHandlerIfHandlerWasRegistered() throws Exception {
         final boolean verbosityToggle = false;  // even default mode registers handler
 
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         reset(mWifiNative);
 
-        mWifiDiagnostics.stopLogging();
+        mWifiDiagnostics.stopLogging(STA_IF_NAME);
         verify(mWifiNative).resetLogHandler();
     }
 
     /** Verifies that, if a log handler is not registered, stopLogging() skips resetLogHandler(). */
     @Test
     public void stopLoggingOnlyResetsLogHandlerIfHandlerWasRegistered() throws Exception {
-        mWifiDiagnostics.stopLogging();
+        mWifiDiagnostics.stopLogging(STA_IF_NAME);
         verify(mWifiNative, never()).resetLogHandler();
     }
 
@@ -233,15 +241,16 @@
         final boolean verbosityToggle = false;  // even default mode registers handler
 
         when(mWifiNative.setLoggingEventHandler(anyObject())).thenReturn(true);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         reset(mWifiNative);
 
         when(mWifiNative.resetLogHandler()).thenReturn(true);
-        mWifiDiagnostics.stopLogging();
+        mWifiDiagnostics.stopLogging(STA_IF_NAME);
         verify(mWifiNative).resetLogHandler();
         reset(mWifiNative);
 
-        mWifiDiagnostics.stopLogging();
+        mWifiDiagnostics.stopLogging(STA_IF_NAME);
         verify(mWifiNative, never()).resetLogHandler();
     }
 
@@ -251,7 +260,8 @@
     @Test
     public void canCaptureAndStoreRingBufferData() throws Exception {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
 
         final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
@@ -269,7 +279,8 @@
     @Test
     public void loggerDiscardsExtraneousData() throws Exception {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
 
         final byte[] data1 = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         final byte[] data2 = {1, 2, 3};
@@ -282,34 +293,11 @@
         assertArrayEquals(data2, ringBufferData[0]);
     }
 
-    /**
-     * Verifies that, when verbose mode is not enabled, startLogging() calls
-     * startPktFateMonitoring(any()).
-     */
+    // Verifies that startPktFateMonitoring(any()) reports failure to start packet fate
     @Test
-    public void startLoggingStartsPacketFateWithoutVerboseMode() {
-        final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
-        verify(mWifiNative).startPktFateMonitoring(any());
-    }
-
-    /**
-     * Verifies that, when verbose mode is enabled, startLogging() calls
-     * startPktFateMonitoring(any()).
-     */
-    @Test
-    public void startLoggingStartsPacketFateInVerboseMode() {
-        final boolean verbosityToggle = true;
-        mWifiDiagnostics.startLogging(verbosityToggle);
-        verify(mWifiNative).startPktFateMonitoring(any());
-    }
-
-    // Verifies that startLogging() reports failure of startPktFateMonitoring(any()).
-    @Test
-    public void startLoggingReportsFailureOfStartPktFateMonitoring() {
-        final boolean verbosityToggle = true;
+    public void startPktFateMonitoringReportsStartFailures() {
         when(mWifiNative.startPktFateMonitoring(any())).thenReturn(false);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         verify(mLog).wC(contains("Failed"));
     }
 
@@ -320,7 +308,8 @@
     @Test
     public void reportConnectionFailureIsIgnoredWithoutVerboseMode() {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(any(), anyObject());
         verify(mWifiNative).getRxPktFates(any(), anyObject());
@@ -332,7 +321,8 @@
     @Test
     public void reportConnectionFailureFetchesFatesInVerboseMode() {
         final boolean verbosityToggle = true;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(any(), anyObject());
         verify(mWifiNative).getRxPktFates(any(), anyObject());
@@ -341,7 +331,8 @@
     @Test
     public void reportConnectionEventPropagatesStartToLastMileLogger() {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED);
         verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED);
     }
@@ -349,7 +340,8 @@
     @Test
     public void reportConnectionEventPropagatesSuccessToLastMileLogger() {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
         verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
     }
@@ -357,7 +349,8 @@
     @Test
     public void reportConnectionEventPropagatesFailureToLastMileLogger() {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
     }
@@ -368,7 +361,8 @@
     @Test
     public void reportConnectionEventPropagatesTimeoutToLastMileLogger() {
         final boolean verbosityToggle = true;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
         verify(mLastMileLogger).reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
     }
@@ -380,7 +374,8 @@
     public void loggerFetchesTxFatesEvenIfFetchingRxFatesFails() {
         final boolean verbosityToggle = true;
         when(mWifiNative.getRxPktFates(any(), anyObject())).thenReturn(false);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(any(), anyObject());
         verify(mWifiNative).getRxPktFates(any(), anyObject());
@@ -393,7 +388,8 @@
     public void loggerFetchesRxFatesEvenIfFetchingTxFatesFails() {
         final boolean verbosityToggle = true;
         when(mWifiNative.getTxPktFates(any(), anyObject())).thenReturn(false);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(any(), anyObject());
         verify(mWifiNative).getRxPktFates(any(), anyObject());
@@ -405,7 +401,8 @@
         final boolean verbosityToggle = false;
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.dump(new FileDescriptor(), pw, new String[]{"bogus", "args"});
         verify(mWifiNative).getTxPktFates(any(), anyObject());
         verify(mWifiNative).getRxPktFates(any(), anyObject());
@@ -435,7 +432,8 @@
     @Test
     public void dumpSucceedsWhenFatesHaveBeenFetchedButAreEmpty() {
         final boolean verbosityToggle = true;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
         verify(mWifiNative).getTxPktFates(any(), anyObject());
         verify(mWifiNative).getRxPktFates(any(), anyObject());
@@ -452,7 +450,9 @@
     }
 
     private String getDumpString(boolean verbose) {
-        mWifiDiagnostics.startLogging(verbose);
+        mWifiDiagnostics.enableVerboseLogging(verbose);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiNative.enableVerboseLogging(verbose ? 1 : 0);
         when(mWifiNative.getTxPktFates(any(), anyObject())).then(new AnswerWithArguments() {
             public boolean answer(String ifaceName, WifiNative.TxFateReport[] fates) {
@@ -586,7 +586,8 @@
     @Test
     public void dumpOmitsFatesIfVerboseIsDisabledAfterFetch() {
         final boolean verbosityToggle = true;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startPktFateMonitoring(STA_IF_NAME);
         when(mWifiNative.getTxPktFates(any(), anyObject())).then(new AnswerWithArguments() {
             public boolean answer(String ifaceName, WifiNative.TxFateReport[] fates) {
                 fates[0] = new WifiNative.TxFateReport(
@@ -610,7 +611,8 @@
         verify(mWifiNative).getRxPktFates(any(), anyObject());
 
         final boolean newVerbosityToggle = false;
-        mWifiDiagnostics.startLogging(newVerbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(newVerbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
@@ -626,7 +628,8 @@
     @Test
     public void ringBufferSizeIsSmallByDefault() throws Exception {
         final boolean verbosityToggle = false;
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
@@ -641,7 +644,8 @@
         when(mBuildProperties.isUserdebugBuild()).thenReturn(true);
         when(mBuildProperties.isEngBuild()).thenReturn(false);
         when(mBuildProperties.isUserBuild()).thenReturn(false);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
@@ -656,7 +660,8 @@
         when(mBuildProperties.isEngBuild()).thenReturn(true);
         when(mBuildProperties.isUserdebugBuild()).thenReturn(false);
         when(mBuildProperties.isUserBuild()).thenReturn(false);
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
@@ -668,7 +673,8 @@
     public void ringBufferSizeIsLargeInVerboseMode() throws Exception {
         final boolean verbosityToggle = true;
 
-        mWifiDiagnostics.startLogging(verbosityToggle);
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
@@ -678,8 +684,10 @@
     /** Verifies that we use large ring buffers when switched from normal to verbose mode. */
     @Test
     public void startLoggingGrowsRingBuffersIfNeeded() throws Exception {
-        mWifiDiagnostics.startLogging(false  /* verbose disabled */);
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(false /* verbose disabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
@@ -691,12 +699,14 @@
     @Test
     public void startLoggingShrinksRingBuffersIfNeeded() throws Exception {
 
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.onRingBufferData(
                 mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
 
         // Existing data is nuked (too large).
-        mWifiDiagnostics.startLogging(false  /* verbose disabled */);
+        mWifiDiagnostics.enableVerboseLogging(false /* verbose disabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
 
@@ -734,7 +744,8 @@
     /** Verifies that we capture the firmware and driver dumps if verbose is enabled. */
     @Test
     public void captureBugReportTakesFirmwareAndDriverDumpsInVerboseMode() {
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
         verify(mWifiNative).getDriverStateDump();
@@ -745,7 +756,8 @@
     public void dumpIncludesDriverStateDumpIfAvailable() {
         when(mWifiNative.getDriverStateDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getDriverStateDump();
 
@@ -758,7 +770,8 @@
     /** Verifies that the dump skips driver state, if driver state was not provided by HAL. */
     @Test
     public void dumpOmitsDriverStateDumpIfUnavailable() {
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getDriverStateDump();
 
@@ -773,11 +786,13 @@
     public void dumpOmitsDriverStateDumpIfVerboseDisabledAfterCapture() {
         when(mWifiNative.getDriverStateDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getDriverStateDump();
 
-        mWifiDiagnostics.startLogging(false  /* verbose no longer enabled */);
+        mWifiDiagnostics.enableVerboseLogging(false /* verbose disabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
@@ -790,7 +805,8 @@
     public void dumpIncludesFirmwareMemoryDumpIfAvailable() {
         when(mWifiNative.getFwMemoryDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
 
@@ -803,7 +819,8 @@
     /** Verifies that the dump skips firmware memory, if firmware memory was not provided by HAL. */
     @Test
     public void dumpOmitsFirmwareMemoryDumpIfUnavailable() {
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
 
@@ -818,11 +835,13 @@
     public void dumpOmitsFirmwareMemoryDumpIfVerboseDisabledAfterCapture() {
         when(mWifiNative.getFwMemoryDump()).thenReturn(new byte[]{0, 1, 2});
 
-        mWifiDiagnostics.startLogging(true  /* verbose enabled */);
+        mWifiDiagnostics.enableVerboseLogging(true /* verbose enabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
         verify(mWifiNative).getFwMemoryDump();
 
-        mWifiDiagnostics.startLogging(false  /* verbose no longer enabled */);
+        mWifiDiagnostics.enableVerboseLogging(false /* verbose disabled */);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
 
         StringWriter sw = new StringWriter();
         PrintWriter pw = new PrintWriter(sw);
@@ -890,4 +909,130 @@
         mWifiDiagnostics.captureAlertData(NON_FATAL_FW_ALART, ALERT_DATA);
         verify(mWifiNative, never()).flushRingBufferData();
     }
+
+    /**
+     * Verifies that we can capture ring-buffer data in SoftAp mode
+     */
+    @Test
+    public void canCaptureAndStoreRingBufferDataInSoftApMode() throws Exception {
+        final boolean verbosityToggle = false;
+
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(AP_IF_NAME);
+
+        final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+
+        byte[][] ringBufferData = getLoggerRingBufferData();
+        assertEquals(1, ringBufferData.length);
+        assertArrayEquals(data, ringBufferData[0]);
+    }
+
+    /**
+     * Verifies that we capture ring-buffer data in Station + SoftAp
+     * Concurrency mode.
+     */
+    @Test
+    public void canCaptureAndStoreRingBufferDataInConcurrencyMode() throws Exception {
+        final boolean verbosityToggle = false;
+
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
+        mWifiDiagnostics.startLogging(AP_IF_NAME);
+
+        final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+
+        byte[][] ringBufferData = getLoggerRingBufferData();
+        assertEquals(1, ringBufferData.length);
+        assertArrayEquals(data, ringBufferData[0]);
+    }
+
+    /**
+     * Verifies that we can continue to capture ring-buffer data
+     * after WiFi station is turned off in concurrency mode.
+     */
+    @Test
+    public void canCaptureAndStoreRingBufferDataAfterStaIsTurnedOffInConcurrencyMode()
+            throws Exception {
+        final boolean verbosityToggle = false;
+        final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
+
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
+        mWifiDiagnostics.startLogging(AP_IF_NAME);
+
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+
+        byte[][] ringBufferData0 = getLoggerRingBufferData();
+        assertEquals(1, ringBufferData0.length);
+        assertArrayEquals(data, ringBufferData0[0]);
+
+        mWifiDiagnostics.stopLogging(STA_IF_NAME);
+
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+
+        byte[][] ringBufferData1 = getLoggerRingBufferData();
+        assertEquals(1, ringBufferData1.length);
+        assertArrayEquals(data, ringBufferData1[0]);
+    }
+
+    /**
+     * Verifies that we can continue to capture ring-buffer data
+     * after SoftAp is turned off in concurrency mode.
+     */
+    @Test
+    public void canCaptureAndStoreRingBufferDataAfterSoftApIsTurnedOffInConcurrencyMode()
+            throws Exception {
+        final boolean verbosityToggle = false;
+        final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
+
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
+        mWifiDiagnostics.startLogging(AP_IF_NAME);
+
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+
+        byte[][] ringBufferData0 = getLoggerRingBufferData();
+        assertEquals(1, ringBufferData0.length);
+        assertArrayEquals(data, ringBufferData0[0]);
+
+        mWifiDiagnostics.stopLogging(AP_IF_NAME);
+
+        mWifiDiagnostics.onRingBufferData(mFakeRbs, data);
+        mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_NONE);
+
+        byte[][] ringBufferData1 = getLoggerRingBufferData();
+        assertEquals(1, ringBufferData1.length);
+        assertArrayEquals(data, ringBufferData1[0]);
+    }
+
+    /** Verifies that stoplogging on both the interfaces clean up
+     *  all the resources.
+     */
+    @Test
+    public void verifyStopLoggingOnAllInterfacesClearTheResources() throws Exception {
+        final boolean verbosityToggle = false;
+
+        mWifiDiagnostics.enableVerboseLogging(verbosityToggle);
+        when(mWifiNative.setLoggingEventHandler(any())).thenReturn(true);
+        when(mWifiNative.resetLogHandler()).thenReturn(true);
+
+        mWifiDiagnostics.startLogging(STA_IF_NAME);
+        verify(mWifiNative).setLoggingEventHandler(any());
+
+        mWifiDiagnostics.startLogging(AP_IF_NAME);
+
+        mWifiDiagnostics.stopLogging(STA_IF_NAME);
+        verify(mWifiNative, never()).resetLogHandler();
+
+        mWifiDiagnostics.stopLogging(AP_IF_NAME);
+
+        verify(mWifiNative).resetLogHandler();
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
index 9ae3826..7c55228 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
@@ -21,16 +21,20 @@
 import static org.mockito.Mockito.*;
 import static org.mockito.MockitoAnnotations.*;
 
+import android.content.Context;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiSsid;
+import android.os.Handler;
 import android.os.test.TestLooper;
+import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
@@ -42,6 +46,8 @@
  */
 @SmallTest
 public class WifiLastResortWatchdogTest {
+    final ArgumentCaptor<OnPropertiesChangedListener> mOnPropertiesChangedListenerCaptor =
+            ArgumentCaptor.forClass(OnPropertiesChangedListener.class);
     WifiLastResortWatchdog mLastResortWatchdog;
     @Mock WifiInjector mWifiInjector;
     @Mock WifiMetrics mWifiMetrics;
@@ -49,6 +55,8 @@
     @Mock ClientModeImpl mClientModeImpl;
     @Mock Clock mClock;
     @Mock WifiInfo mWifiInfo;
+    @Mock Context mContext;
+    @Mock DeviceConfigFacade mDeviceConfigFacade;
 
     private String[] mSsids = {"\"test1\"", "\"test2\"", "\"test3\"", "\"test4\""};
     private String[] mBssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "de:ad:ba:b1:e5:55",
@@ -61,17 +69,24 @@
     private boolean[] mHasEverConnected = {false, false, false, false};
     private TestLooper mLooper;
     private static final String TEST_NETWORK_SSID = "\"test_ssid\"";
+    private static final int DEFAULT_ABNORMAL_CONNECTION_DURATION_MS = 30000;
 
     @Before
     public void setUp() throws Exception {
         initMocks(this);
         mLooper = new TestLooper();
         when(mWifiInjector.getSelfRecovery()).thenReturn(mSelfRecovery);
-        mLastResortWatchdog = new WifiLastResortWatchdog(mWifiInjector, mClock, mWifiMetrics,
-                mClientModeImpl, mLooper.getLooper());
+        when(mDeviceConfigFacade.isAbnormalConnectionBugreportEnabled()).thenReturn(true);
+        when(mDeviceConfigFacade.getAbnormalConnectionDurationMs()).thenReturn(
+                        DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
+        mLastResortWatchdog = new WifiLastResortWatchdog(mWifiInjector, mContext, mClock,
+                mWifiMetrics, mClientModeImpl, mLooper.getLooper(), mDeviceConfigFacade);
         mLastResortWatchdog.setBugReportProbability(1);
         when(mClientModeImpl.getWifiInfo()).thenReturn(mWifiInfo);
         when(mWifiInfo.getSSID()).thenReturn(TEST_NETWORK_SSID);
+        when(mWifiInjector.getClientModeImplHandler()).thenReturn(mLastResortWatchdog.getHandler());
+        verify(mDeviceConfigFacade).addOnPropertiesChangedListener(any(),
+                mOnPropertiesChangedListenerCaptor.capture());
     }
 
     private List<Pair<ScanDetail, WifiConfiguration>> createFilteredQnsCandidates(String[] ssids,
@@ -2152,4 +2167,91 @@
         verify(mWifiMetrics, times(1)).incrementNumLastResortWatchdogSuccesses();
     }
 
+    /**
+     * Verifies that when a connection takes too long (time difference between
+     * StaEvent.TYPE_CMD_START_CONNECT and StaEvent.TYPE_NETWORK_CONNECTION_EVENT) a bugreport is
+     * taken.
+     */
+    @Test
+    public void testAbnormalConnectionTimeTriggersBugreport() throws Exception {
+        // first verifies that bugreports are not taken when connection takes less than
+        // DEFAULT_ABNORMAL_CONNECTION_DURATION_MS
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1L);
+        mLastResortWatchdog.noteStartConnectTime();
+        mLooper.dispatchAll();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                (long) DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
+        Handler handler = mLastResortWatchdog.getHandler();
+        handler.sendMessage(
+                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        mLooper.dispatchAll();
+        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+
+        // Now verify that bugreport is taken
+        mLastResortWatchdog.noteStartConnectTime();
+        mLooper.dispatchAll();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                2L * DEFAULT_ABNORMAL_CONNECTION_DURATION_MS + 1);
+        handler.sendMessage(
+                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        mLooper.dispatchAll();
+        verify(mClientModeImpl).takeBugReport(anyString(), anyString());
+
+        // Verify additional connections (without more TYPE_CMD_START_CONNECT) don't trigger more
+        // bugreports.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                4L * DEFAULT_ABNORMAL_CONNECTION_DURATION_MS);
+        handler.sendMessage(
+                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        mLooper.dispatchAll();
+        verify(mClientModeImpl).takeBugReport(anyString(), anyString());
+    }
+
+    /**
+     * Changes |mAbnormalConnectionDurationMs| to a new value, and then verify that a bugreport is
+     * taken for a connection that takes longer than the new threshold.
+     * @throws Exception
+     */
+    @Test
+    public void testGServicesSetDuration() throws Exception {
+        final int testDurationMs = 10 * 1000; // 10 seconds
+        // changes the abnormal connection duration to |testDurationMs|.
+        when(mDeviceConfigFacade.getAbnormalConnectionDurationMs()).thenReturn(testDurationMs);
+        mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null);
+
+        // verifies that bugreport is taken for connections that take longer than |testDurationMs|.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1L);
+        mLastResortWatchdog.noteStartConnectTime();
+        mLooper.dispatchAll();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn((long) testDurationMs + 2);
+        Handler handler = mLastResortWatchdog.getHandler();
+        handler.sendMessage(
+                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        mLooper.dispatchAll();
+        verify(mClientModeImpl).takeBugReport(anyString(), anyString());
+    }
+
+    /**
+     * Verifies that bugreports are not triggered even when conditions are met after the
+     * |mAbnormalConnectionBugreportEnabled| flag is changed to false.
+     * @throws Exception
+     */
+    @Test
+    public void testGServicesFlagDisable() throws Exception {
+        // changes |mAbnormalConnectionBugreportEnabled| to false.
+        when(mDeviceConfigFacade.isAbnormalConnectionBugreportEnabled()).thenReturn(false);
+        mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null);
+
+        // verifies that bugreports are not taken.
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(1L);
+        mLastResortWatchdog.noteStartConnectTime();
+        mLooper.dispatchAll();
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(
+                (long) DEFAULT_ABNORMAL_CONNECTION_DURATION_MS + 2);
+        Handler handler = mLastResortWatchdog.getHandler();
+        handler.sendMessage(
+                handler.obtainMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null));
+        mLooper.dispatchAll();
+        verify(mClientModeImpl, never()).takeBugReport(anyString(), anyString());
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
index a40de55..255073b 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
@@ -161,17 +161,6 @@
     }
 
     /**
-     * Test to verify that the lock mode is verified before adding a lock.
-     *
-     * Steps: call acquireWifiLock with an invalid lock mode.
-     * Expected: the call should throw an IllegalArgumentException.
-     */
-    @Test(expected = IllegalArgumentException.class)
-    public void acquireWifiLockShouldThrowExceptionOnInivalidLockMode() throws Exception {
-        mWifiLockManager.acquireWifiLock(WIFI_LOCK_MODE_INVALID, "", mBinder, mWorkSource);
-    }
-
-    /**
      * Test that a call to acquireWifiLock with valid parameters works.
      *
      * Steps: call acquireWifiLock on the empty WifiLockManager.
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index e45906a..1176957 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -58,6 +58,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiSsid;
@@ -1371,10 +1372,17 @@
         when(networkDetail.getDtimInterval()).thenReturn(NETWORK_DETAIL_DTIM);
         ScanResult scanResult = mock(ScanResult.class);
         scanResult.level = SCAN_RESULT_LEVEL;
+        scanResult.capabilities = "EAP";
         WifiConfiguration config = mock(WifiConfiguration.class);
         config.SSID = "\"" + SSID + "\"";
         config.dtimInterval = CONFIG_DTIM;
         config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT;
+        config.allowedKeyManagement = new BitSet();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+        config.enterpriseConfig = new WifiEnterpriseConfig();
+        config.enterpriseConfig.setEapMethod(WifiEnterpriseConfig.Eap.TTLS);
+        config.enterpriseConfig.setPhase2Method(WifiEnterpriseConfig.Phase2.MSCHAPV2);
         WifiConfiguration.NetworkSelectionStatus networkSelectionStat =
                 mock(WifiConfiguration.NetworkSelectionStatus.class);
         when(networkSelectionStat.getCandidate()).thenReturn(scanResult);
@@ -1395,7 +1403,9 @@
                 WifiMetricsProto.ConnectionEvent.HLF_NONE,
                 WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
 
+        //Change configuration to open without randomization
         config.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE;
+        scanResult.capabilities = "";
         //Create a connection event using the config and a scan detail
         mWifiMetrics.startConnectionEvent(config, "Green",
                 WifiMetricsProto.ConnectionEvent.ROAM_NONE);
@@ -1411,8 +1421,20 @@
         //Check that the correct values are being flowed through
         assertEquals(2, mDecodedProto.connectionEvent.length);
         assertEquals(CONFIG_DTIM, mDecodedProto.connectionEvent[0].routerFingerprint.dtim);
+        assertEquals(WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE,
+                mDecodedProto.connectionEvent[0].routerFingerprint.authentication);
+        assertEquals(WifiMetricsProto.RouterFingerPrint.TYPE_EAP_TTLS,
+                mDecodedProto.connectionEvent[0].routerFingerprint.eapMethod);
+        assertEquals(WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_MSCHAPV2,
+                mDecodedProto.connectionEvent[0].routerFingerprint.authPhase2Method);
         assertEquals(SCAN_RESULT_LEVEL, mDecodedProto.connectionEvent[0].signalStrength);
         assertEquals(NETWORK_DETAIL_DTIM, mDecodedProto.connectionEvent[1].routerFingerprint.dtim);
+        assertEquals(WifiMetricsProto.RouterFingerPrint.AUTH_OPEN,
+                mDecodedProto.connectionEvent[1].routerFingerprint.authentication);
+        assertEquals(WifiMetricsProto.RouterFingerPrint.TYPE_EAP_UNKNOWN,
+                mDecodedProto.connectionEvent[1].routerFingerprint.eapMethod);
+        assertEquals(WifiMetricsProto.RouterFingerPrint.TYPE_PHASE2_NONE,
+                mDecodedProto.connectionEvent[1].routerFingerprint.authPhase2Method);
         assertEquals(SCAN_RESULT_LEVEL, mDecodedProto.connectionEvent[1].signalStrength);
         assertEquals(NETWORK_DETAIL_WIFIMODE,
                 mDecodedProto.connectionEvent[1].routerFingerprint.routerTechnology);
@@ -2577,6 +2599,45 @@
     }
 
     /**
+     * Verify that Tx and Rx per-band LinkSpeedCounts are correctly logged in metrics
+     */
+    @Test
+    public void testTxRxLinkSpeedBandCounts() throws Exception {
+        when(mFacade.getIntegerSetting(eq(mContext),
+                eq(Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED), anyInt())).thenReturn(1);
+        mWifiMetrics.loadSettings();
+        for (int i = 0; i < NUM_LINK_SPEED_LEVELS_TO_INCREMENT; i++) {
+            for (int j = 0; j <= i; j++) {
+                mWifiMetrics.incrementTxLinkSpeedBandCount(
+                        WifiMetrics.MIN_LINK_SPEED_MBPS + i, RSSI_POLL_FREQUENCY);
+                mWifiMetrics.incrementRxLinkSpeedBandCount(
+                        WifiMetrics.MIN_LINK_SPEED_MBPS + i + 1, RSSI_POLL_FREQUENCY);
+            }
+        }
+        dumpProtoAndDeserialize();
+        assertEquals(0, mDecodedProto.txLinkSpeedCount2G.length);
+        assertEquals(0, mDecodedProto.rxLinkSpeedCount2G.length);
+        assertEquals(NUM_LINK_SPEED_LEVELS_TO_INCREMENT,
+                mDecodedProto.txLinkSpeedCount5GLow.length);
+        assertEquals(NUM_LINK_SPEED_LEVELS_TO_INCREMENT,
+                mDecodedProto.rxLinkSpeedCount5GLow.length);
+        assertEquals(0, mDecodedProto.txLinkSpeedCount5GMid.length);
+        assertEquals(0, mDecodedProto.rxLinkSpeedCount5GMid.length);
+        assertEquals(0, mDecodedProto.txLinkSpeedCount5GHigh.length);
+        assertEquals(0, mDecodedProto.rxLinkSpeedCount5GHigh.length);
+        for (int i = 0; i < NUM_LINK_SPEED_LEVELS_TO_INCREMENT; i++) {
+            assertEquals("Incorrect Tx link speed", WifiMetrics.MIN_LINK_SPEED_MBPS + i,
+                    mDecodedProto.txLinkSpeedCount5GLow[i].key);
+            assertEquals("Incorrect Rx link speed", WifiMetrics.MIN_LINK_SPEED_MBPS + i + 1,
+                    mDecodedProto.rxLinkSpeedCount5GLow[i].key);
+            assertEquals("Incorrect count of Tx link speed",
+                    i + 1, mDecodedProto.txLinkSpeedCount5GLow[i].count);
+            assertEquals("Incorrect count of Rx link speed",
+                    i + 1, mDecodedProto.rxLinkSpeedCount5GLow[i].count);
+        }
+    }
+
+    /**
      * Verify that LinkSpeedCounts is not logged when disabled in settings
      */
     @Test
@@ -2588,11 +2649,19 @@
             for (int j = 0; j <= i; j++) {
                 mWifiMetrics.incrementLinkSpeedCount(
                         WifiMetrics.MIN_LINK_SPEED_MBPS + i, TEST_RSSI_LEVEL);
+                mWifiMetrics.incrementTxLinkSpeedBandCount(
+                        WifiMetrics.MIN_LINK_SPEED_MBPS - i, RSSI_POLL_FREQUENCY);
+                mWifiMetrics.incrementRxLinkSpeedBandCount(
+                        WifiMetrics.MIN_LINK_SPEED_MBPS - i, RSSI_POLL_FREQUENCY);
             }
         }
         dumpProtoAndDeserialize();
         assertEquals("LinkSpeedCounts should not be logged when disabled in settings",
                 0, mDecodedProto.linkSpeedCounts.length);
+        assertEquals("Tx LinkSpeedCounts should not be logged when disabled in settings",
+                0, mDecodedProto.txLinkSpeedCount5GLow.length);
+        assertEquals("Rx LinkSpeedCounts should not be logged when disabled in settings",
+                0, mDecodedProto.rxLinkSpeedCount5GLow.length);
     }
 
     /**
@@ -2608,6 +2677,10 @@
         for (int i = 1; i < NUM_OUT_OF_BOUND_ENTRIES; i++) {
             mWifiMetrics.incrementLinkSpeedCount(
                     WifiMetrics.MIN_LINK_SPEED_MBPS - i, MIN_RSSI_LEVEL);
+            mWifiMetrics.incrementTxLinkSpeedBandCount(
+                    WifiMetrics.MIN_LINK_SPEED_MBPS - i, RSSI_POLL_FREQUENCY);
+            mWifiMetrics.incrementRxLinkSpeedBandCount(
+                    WifiMetrics.MIN_LINK_SPEED_MBPS - i, RSSI_POLL_FREQUENCY);
         }
         for (int i = 1; i < NUM_OUT_OF_BOUND_ENTRIES; i++) {
             mWifiMetrics.incrementLinkSpeedCount(
@@ -2620,6 +2693,10 @@
         dumpProtoAndDeserialize();
         assertEquals("LinkSpeedCounts should not be logged for out of bound values",
                 0, mDecodedProto.linkSpeedCounts.length);
+        assertEquals("Tx LinkSpeedCounts should not be logged for out of bound values",
+                0, mDecodedProto.txLinkSpeedCount5GLow.length);
+        assertEquals("Rx LinkSpeedCounts should not be logged for out of bound values",
+                0, mDecodedProto.rxLinkSpeedCount5GLow.length);
     }
 
     private int nextRandInt() {
@@ -2944,9 +3021,10 @@
         dumpProtoAndDeserialize();
         assertEquals(2 * WifiMetrics.MAX_WIFI_USABILITY_STATS_PER_TYPE_TO_UPLOAD,
                 mDecodedProto.wifiUsabilityStatsList.length);
-        for (int i = 0; i < mDecodedProto.wifiUsabilityStatsList.length; i++) {
+        for (int i = 0; i < WifiMetrics.MAX_WIFI_USABILITY_STATS_PER_TYPE_TO_UPLOAD; i++) {
             assertEquals(WifiMetrics.MAX_WIFI_USABILITY_STATS_ENTRIES_LIST_SIZE,
-                    mDecodedProto.wifiUsabilityStatsList[i].stats.length);
+                    mDecodedProto.wifiUsabilityStatsList[2 * i].stats.length);
+            assertEquals(2, mDecodedProto.wifiUsabilityStatsList[2 * i + 1].stats.length);
         }
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
index d4e6594..d20c99c 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
@@ -61,11 +61,14 @@
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
+import android.util.Xml;
 
 import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.FastXmlSerializer;
 import com.android.server.wifi.WifiNetworkFactory.AccessPoint;
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.util.ScanResultUtil;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import org.junit.After;
@@ -76,9 +79,17 @@
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -146,6 +157,7 @@
 
     private WifiNetworkFactory mWifiNetworkFactory;
     private NetworkRequestStoreData.DataSource mDataSource;
+    private NetworkRequestStoreData mNetworkRequestStoreData;
 
     /**
      * Setup the mocks.
@@ -164,7 +176,8 @@
                 .thenReturn(mConnectivityManager);
         when(mPackageManager.getNameForUid(TEST_UID_1)).thenReturn(TEST_PACKAGE_NAME_1);
         when(mPackageManager.getNameForUid(TEST_UID_2)).thenReturn(TEST_PACKAGE_NAME_2);
-        when(mPackageManager.getApplicationInfo(any(), anyInt())).thenReturn(new ApplicationInfo());
+        when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(new ApplicationInfo());
         when(mPackageManager.getApplicationLabel(any())).thenReturn(TEST_APP_NAME);
         when(mActivityManager.getPackageImportance(TEST_PACKAGE_NAME_1))
                 .thenReturn(IMPORTANCE_FOREGROUND_SERVICE);
@@ -174,6 +187,7 @@
         when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl);
         when(mWifiConfigManager.addOrUpdateNetwork(any(), anyInt(), anyString()))
                 .thenReturn(new NetworkUpdateResult(TEST_NETWORK_ID_1));
+        when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList());
 
         mWifiNetworkFactory = new WifiNetworkFactory(mLooper.getLooper(), mContext,
                 mNetworkCapabilities, mActivityManager, mAlarmManager, mAppOpsManager, mClock,
@@ -185,6 +199,7 @@
         verify(mWifiInjector).makeNetworkRequestStoreData(dataSourceArgumentCaptor.capture());
         mDataSource = dataSourceArgumentCaptor.getValue();
         assertNotNull(mDataSource);
+        mNetworkRequestStoreData = new NetworkRequestStoreData(mDataSource);
 
         // Register and establish full connection to connectivity manager.
         mWifiNetworkFactory.register();
@@ -940,16 +955,11 @@
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
-        ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
-                ArgumentCaptor.forClass(List.class);
-        verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
-
-        assertNotNull(matchedScanResultsCaptor.getValue());
         // We expect no network match in this case.
-        assertEquals(0, matchedScanResultsCaptor.getValue().size());
+        verify(mNetworkRequestMatchCallback, never()).onMatch(any());
 
-        verify(mWifiMetrics).incrementNetworkRequestApiMatchSizeHistogram(
-                matchedScanResultsCaptor.getValue().size());
+        // Don't increment metrics until we have a match
+        verify(mWifiMetrics, never()).incrementNetworkRequestApiMatchSizeHistogram(anyInt());
     }
 
     /**
@@ -985,13 +995,8 @@
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
-        ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
-                ArgumentCaptor.forClass(List.class);
-        verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
-
-        assertNotNull(matchedScanResultsCaptor.getValue());
         // We expect no network match in this case.
-        assertEquals(0, matchedScanResultsCaptor.getValue().size());
+        verify(mNetworkRequestMatchCallback, never()).onMatch(any());
     }
 
     /**
@@ -1083,6 +1088,73 @@
     }
 
     /**
+     * Verify when number of user approved access points exceed the capacity, framework should trim
+     * the Set by removing the least recently used elements.
+     */
+    @Test
+    public void testNetworkSpecifierHandleUserSelectionConnectToNetworkExceedApprovedListCapacity()
+            throws Exception {
+        int userApproveAccessPointCapacity = mWifiNetworkFactory.NUM_OF_ACCESS_POINT_LIMIT_PER_APP;
+        int numOfApPerSsid = userApproveAccessPointCapacity / 2 + 1;
+        String[] testIds = new String[]{TEST_SSID_1, TEST_SSID_2};
+
+        // Setup up scan data
+        setupScanDataSameSsidWithDiffBssid(SCAN_RESULT_TYPE_WPA_PSK, numOfApPerSsid, testIds);
+
+        // Setup network specifier for WPA-PSK networks.
+        PatternMatcher ssidPatternMatch =
+                new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_PREFIX);
+        Pair<MacAddress, MacAddress> bssidPatternMatch =
+                Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS);
+        WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createPskNetwork();
+        wifiConfiguration.preSharedKey = TEST_WPA_PRESHARED_KEY;
+        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+                ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
+                TEST_PACKAGE_NAME_1);
+
+        // request network, trigger scan and get matched set.
+        mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
+                TEST_CALLBACK_IDENTIFIER);
+        verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
+                mNetworkRequestUserSelectionCallback.capture());
+
+        verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
+
+        INetworkRequestUserSelectionCallback networkRequestUserSelectionCallback =
+                mNetworkRequestUserSelectionCallback.getValue();
+        assertNotNull(networkRequestUserSelectionCallback);
+
+        // Now trigger user selection to one of the network.
+        mSelectedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        mSelectedNetwork.SSID = "\"" + mTestScanDatas[0].getResults()[0].SSID + "\"";
+        networkRequestUserSelectionCallback.select(mSelectedNetwork);
+        mLooper.dispatchAll();
+
+        // Verifier num of Approved access points.
+        assertEquals(mWifiNetworkFactory.mUserApprovedAccessPointMap
+                .get(TEST_PACKAGE_NAME_1).size(), numOfApPerSsid);
+
+        // Now trigger user selection to another network with different SSID.
+        mSelectedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        mSelectedNetwork.SSID = "\"" + mTestScanDatas[0].getResults()[numOfApPerSsid].SSID + "\"";
+        networkRequestUserSelectionCallback.select(mSelectedNetwork);
+        mLooper.dispatchAll();
+
+        // Verify triggered trim when user Approved Access Points exceed capacity.
+        Set<AccessPoint> userApprovedAccessPoints = mWifiNetworkFactory.mUserApprovedAccessPointMap
+                .get(TEST_PACKAGE_NAME_1);
+        assertEquals(userApprovedAccessPoints.size(), userApproveAccessPointCapacity);
+        long numOfSsid1Aps = userApprovedAccessPoints
+                .stream()
+                .filter(a->a.ssid.equals(TEST_SSID_1))
+                .count();
+        assertEquals(numOfSsid1Aps, userApproveAccessPointCapacity - numOfApPerSsid);
+    }
+
+    /**
      * Verify handling of user selection to trigger connection to an existing saved network.
      */
     @Test
@@ -1537,9 +1609,17 @@
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
 
         // Now release the network request.
+        WifiConfiguration wcmNetwork = new WifiConfiguration(mSelectedNetwork);
+        wcmNetwork.networkId = TEST_NETWORK_ID_1;
+        wcmNetwork.creatorUid = TEST_UID_1;
+        wcmNetwork.fromWifiNetworkSpecifier = true;
+        wcmNetwork.ephemeral = true;
+        when(mWifiConfigManager.getConfiguredNetwork(mSelectedNetwork.configKey()))
+                .thenReturn(wcmNetwork);
         mWifiNetworkFactory.releaseNetworkFor(mNetworkRequest);
         // Verify that we triggered a disconnect.
         verify(mClientModeImpl, times(2)).disconnectCommand();
+        verify(mWifiConfigManager).removeNetwork(TEST_NETWORK_ID_1, TEST_UID_1);
         // Re-enable connectivity manager .
         verify(mWifiConnectivityManager).setSpecificNetworkRequestInProgress(false);
     }
@@ -1598,6 +1678,7 @@
         mLooper.dispatchAll();
 
         verify(mNetworkRequestMatchCallback).onAbort();
+        verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any());
         verifyUnfullfillableDispatched(mConnectivityMessenger);
 
@@ -1639,6 +1720,7 @@
         mLooper.dispatchAll();
 
         verify(mNetworkRequestMatchCallback).onAbort();
+        verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any());
         verify(mAlarmManager).cancel(mPeriodicScanListenerArgumentCaptor.getValue());
         verifyUnfullfillableDispatched(mConnectivityMessenger);
@@ -1673,6 +1755,7 @@
 
         verify(mNetworkRequestMatchCallback).onAbort();
         verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true);
+        verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any());
         verify(mAlarmManager).cancel(mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
 
@@ -1712,6 +1795,7 @@
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
 
         verify(mWifiConnectivityManager, times(1)).setSpecificNetworkRequestInProgress(true);
+        verify(mWifiScanner, times(2)).getSingleScanResults();
         verify(mWifiScanner, times(2)).startScan(any(), any(), any());
         // we shouldn't disconnect until the user accepts the next request.
         verify(mClientModeImpl, times(1)).disconnectCommand();
@@ -2004,10 +2088,10 @@
 
     /**
      * Verify the user approval bypass for a specific request for an access point that was already
-     * approved previously.
+     * approved previously with no cached scan results matching.
      */
     @Test
-    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchPreviouslyApproved()
+    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithNoCache()
             throws Exception {
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
@@ -2027,6 +2111,9 @@
                 WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
         mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+        validateUiStartParams(true);
+
         mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
                 TEST_CALLBACK_IDENTIFIER);
         // Trigger scan results & ensure we triggered a connect.
@@ -2048,8 +2135,7 @@
      * approved previously, but then the user forgot it sometime after.
      */
     @Test
-    public void
-            testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchPreviouslyApprovedNForgot()
+    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedNForgot()
             throws Exception {
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
@@ -2093,7 +2179,7 @@
      * not approved previously.
      */
     @Test
-    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchNotPreviouslyApproved()
+    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchNotApproved()
             throws Exception {
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
@@ -2134,7 +2220,7 @@
      * (not access point) that was approved previously.
      */
     @Test
-    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchPreviouslyApproved()
+    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchApproved()
             throws Exception {
         // 1. First request (no user approval bypass)
         sendNetworkRequestAndSetupForConnectionStatus();
@@ -2322,6 +2408,188 @@
         verify(mClientModeImpl).sendMessage(any());
     }
 
+    /**
+     * Verify the config store save and load could preserve the elements order.
+     */
+    @Test
+    public void testStoreConfigSaveAndLoadPreserveOrder() throws Exception {
+        LinkedHashSet<AccessPoint> approvedApSet = new LinkedHashSet<>();
+        approvedApSet.add(new AccessPoint(TEST_SSID_1,
+                MacAddress.fromString(TEST_BSSID_1), WifiConfiguration.SECURITY_TYPE_PSK));
+        approvedApSet.add(new AccessPoint(TEST_SSID_2,
+                MacAddress.fromString(TEST_BSSID_2), WifiConfiguration.SECURITY_TYPE_PSK));
+        approvedApSet.add(new AccessPoint(TEST_SSID_3,
+                MacAddress.fromString(TEST_BSSID_3), WifiConfiguration.SECURITY_TYPE_PSK));
+        approvedApSet.add(new AccessPoint(TEST_SSID_4,
+                MacAddress.fromString(TEST_BSSID_4), WifiConfiguration.SECURITY_TYPE_PSK));
+        mWifiNetworkFactory.mUserApprovedAccessPointMap.put(TEST_PACKAGE_NAME_1,
+                new LinkedHashSet<>(approvedApSet));
+        // Save config.
+        byte[] xmlData = serializeData();
+        mWifiNetworkFactory.mUserApprovedAccessPointMap.clear();
+        // Load config.
+        deserializeData(xmlData);
+
+        LinkedHashSet<AccessPoint> storedApSet = mWifiNetworkFactory
+                .mUserApprovedAccessPointMap.get(TEST_PACKAGE_NAME_1);
+        // Check load config success and order preserved.
+        assertNotNull(storedApSet);
+        assertArrayEquals(approvedApSet.toArray(), storedApSet.toArray());
+    }
+
+    /**
+     * Verify the user approval bypass for a specific request for an access point that was already
+     * approved previously and the scan result is present in the cached scan results.
+     */
+    @Test
+    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithCache()
+            throws Exception {
+        // 1. First request (no user approval bypass)
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+
+        // 2. Second request for the same access point (user approval bypass).
+        ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0];
+        // simulate no cache expiry
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
+        // Simulate the cached results matching.
+        when(mWifiScanner.getSingleScanResults())
+                .thenReturn(Arrays.asList(mTestScanDatas[0].getResults()));
+
+        PatternMatcher ssidPatternMatch =
+                new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL);
+        Pair<MacAddress, MacAddress> bssidPatternMatch =
+                Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
+                        MacAddress.BROADCAST_ADDRESS);
+        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+                ssidPatternMatch, bssidPatternMatch,
+                WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
+        mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+        // Verify we did not trigger the UI for the second request.
+        verify(mContext, times(1)).startActivityAsUser(any(), any());
+        // Verify we did not trigger a scan.
+        verify(mWifiScanner, never()).startScan(any(), any(), any());
+        // Verify we did not trigger the match callback.
+        verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
+        // Verify that we sent a connection attempt to ClientModeImpl
+        verify(mClientModeImpl).sendMessage(any());
+
+        verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
+    }
+
+    /**
+     * Verify the user approval bypass for a specific request for an access point that was already
+     * approved previously and the scan result is present in the cached scan results, but the
+     * results are stale.
+     */
+    @Test
+    public void
+            testNetworkSpecifierMatchSuccessUsingLiteralSsidAndBssidMatchApprovedWithStaleCache()
+            throws Exception {
+        // 1. First request (no user approval bypass)
+        sendNetworkRequestAndSetupForConnectionStatus();
+
+        mWifiNetworkFactory.removeCallback(TEST_CALLBACK_IDENTIFIER);
+        reset(mNetworkRequestMatchCallback, mWifiScanner, mAlarmManager, mClientModeImpl);
+
+        long scanResultsTimestampInUs = 39484839202L;
+        mTestScanDatas[0].getResults()[0].timestamp = scanResultsTimestampInUs;
+        mTestScanDatas[0].getResults()[1].timestamp = scanResultsTimestampInUs;
+        mTestScanDatas[0].getResults()[2].timestamp = scanResultsTimestampInUs;
+        mTestScanDatas[0].getResults()[3].timestamp = scanResultsTimestampInUs;
+
+        // 2. Second request for the same access point (user approval bypass).
+        ScanResult matchingScanResult = mTestScanDatas[0].getResults()[0];
+        // simulate cache expiry
+        when(mClock.getElapsedSinceBootMillis())
+                .thenReturn(Long.valueOf(
+                        scanResultsTimestampInUs / 1000
+                        + WifiNetworkFactory.CACHED_SCAN_RESULTS_MAX_AGE_IN_MILLIS + 1));
+        // Simulate the cached results matching.
+        when(mWifiScanner.getSingleScanResults())
+                .thenReturn(Arrays.asList(mTestScanDatas[0].getResults()));
+
+        PatternMatcher ssidPatternMatch =
+                new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL);
+        Pair<MacAddress, MacAddress> bssidPatternMatch =
+                Pair.create(MacAddress.fromString(matchingScanResult.BSSID),
+                        MacAddress.BROADCAST_ADDRESS);
+        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+                ssidPatternMatch, bssidPatternMatch,
+                WifiConfigurationTestUtil.createPskNetwork(), TEST_UID_1, TEST_PACKAGE_NAME_1);
+        mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+        // Ensure we brought up the UI while the scan is ongoing.
+        validateUiStartParams(true);
+
+        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
+                TEST_CALLBACK_IDENTIFIER);
+        // Trigger scan results & ensure we triggered a connect.
+        verify(mWifiScanner).startScan(any(), mScanListenerArgumentCaptor.capture(), any());
+        ScanListener scanListener = mScanListenerArgumentCaptor.getValue();
+        assertNotNull(scanListener);
+        scanListener.onResults(mTestScanDatas);
+
+        // Verify we did not trigger the match callback.
+        verify(mNetworkRequestMatchCallback, never()).onMatch(anyList());
+        // Verify that we sent a connection attempt to ClientModeImpl
+        verify(mClientModeImpl).sendMessage(any());
+
+        verify(mWifiMetrics).incrementNetworkRequestApiNumUserApprovalBypass();
+    }
+
+    /**
+     * Verify network specifier matching for a specifier containing a specific SSID match using
+     * 4 WPA_PSK scan results, each with unique SSID when the UI callback registration is delayed.
+     */
+    @Test
+    public void testNetworkSpecifierMatchSuccessUsingLiteralSsidMatchCallbackRegistrationDelayed()
+            throws Exception {
+        // Setup scan data for open networks.
+        setupScanData(SCAN_RESULT_TYPE_WPA_PSK,
+                TEST_SSID_1, TEST_SSID_2, TEST_SSID_3, TEST_SSID_4);
+
+        // Setup network specifier for open networks.
+        PatternMatcher ssidPatternMatch =
+                new PatternMatcher(TEST_SSID_1, PatternMatcher.PATTERN_LITERAL);
+        Pair<MacAddress, MacAddress> bssidPatternMatch =
+                Pair.create(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS);
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+        WifiNetworkSpecifier specifier = new WifiNetworkSpecifier(
+                ssidPatternMatch, bssidPatternMatch, wifiConfiguration, TEST_UID_1,
+                TEST_PACKAGE_NAME_1);
+
+        mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+        validateUiStartParams(true);
+
+        verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
+
+        // Ensure we did not send any match callbacks, until the callback is registered
+        verify(mNetworkRequestMatchCallback, never()).onMatch(any());
+
+        // Register the callback & ensure we triggered the on match callback.
+        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
+                TEST_CALLBACK_IDENTIFIER);
+        ArgumentCaptor<List<ScanResult>> matchedScanResultsCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mNetworkRequestMatchCallback).onMatch(matchedScanResultsCaptor.capture());
+
+        assertNotNull(matchedScanResultsCaptor.getValue());
+        // We only expect 1 network match in this case.
+        validateScanResults(matchedScanResultsCaptor.getValue(), mTestScanDatas[0].getResults()[0]);
+
+        verify(mWifiMetrics).incrementNetworkRequestApiMatchSizeHistogram(
+                matchedScanResultsCaptor.getValue().size());
+    }
+
     private Messenger sendNetworkRequestAndSetupForConnectionStatus() throws RemoteException {
         return sendNetworkRequestAndSetupForConnectionStatus(TEST_SSID_1);
     }
@@ -2391,6 +2659,8 @@
         mNetworkRequest.networkCapabilities.setNetworkSpecifier(specifier);
         mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
 
+        validateUiStartParams(true);
+
         mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
                 TEST_CALLBACK_IDENTIFIER);
         verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
@@ -2398,7 +2668,7 @@
 
         verifyPeriodicScans(0, PERIODIC_SCAN_INTERVAL_MS);
 
-        verify(mNetworkRequestMatchCallback).onMatch(anyList());
+        verify(mNetworkRequestMatchCallback, atLeastOnce()).onMatch(anyList());
     }
 
     // Simulates the periodic scans performed to find a matching network.
@@ -2413,6 +2683,10 @@
         ScanListener scanListener = null;
 
         mInOrder = inOrder(mWifiScanner, mAlarmManager);
+
+        // Before we start scans, ensure that we look at the latest cached scan results.
+        mInOrder.verify(mWifiScanner).getSingleScanResults();
+
         for (int i = 0; i < expectedIntervalsInSeconds.length - 1; i++) {
             long expectedCurrentIntervalInMs = expectedIntervalsInSeconds[i];
             long expectedNextIntervalInMs = expectedIntervalsInSeconds[i + 1];
@@ -2590,7 +2864,7 @@
 
     private void validateUiStartParams(boolean expectedIsReqForSingeNetwork) {
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext).startActivityAsUser(
+        verify(mContext, atLeastOnce()).startActivityAsUser(
                 intentArgumentCaptor.capture(), eq(UserHandle.getUserHandleForUid(TEST_UID_1)));
         Intent intent = intentArgumentCaptor.getValue();
         assertNotNull(intent);
@@ -2621,4 +2895,61 @@
         expectedWifiConfiguration.fromWifiNetworkSpecifier = true;
         WifiConfigurationTestUtil.assertConfigurationEqual(expectedWifiConfiguration, network);
     }
+
+    /**
+     * Create a test scan data for target SSID list with specified number and encryption type
+     * @param scanResultType   network encryption type
+     * @param nums             Number of results with different BSSIDs for one SSID
+     * @param ssids            target SSID list
+     */
+    private void setupScanDataSameSsidWithDiffBssid(int scanResultType, int nums, String[] ssids) {
+        String baseBssid = "11:34:56:78:90:";
+        int[] freq = new int[nums * ssids.length];
+        for (int i = 0; i < nums; i++) {
+            freq[i] = 2417 + i;
+        }
+        mTestScanDatas = ScanTestUtil.createScanDatas(new int[][]{ freq });
+        assertEquals(1, mTestScanDatas.length);
+        ScanResult[] scanResults = mTestScanDatas[0].getResults();
+        assertEquals(nums * ssids.length, scanResults.length);
+        String caps = getScanResultCapsForType(scanResultType);
+        for (int i = 0; i < ssids.length; i++) {
+            for (int j = i * nums; j < (i + 1) * nums; j++) {
+                scanResults[j].SSID = ssids[i];
+                scanResults[j].BSSID = baseBssid + Integer.toHexString(16 + j);
+                scanResults[j].capabilities = caps;
+                scanResults[j].level = -45;
+            }
+        }
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     *
+     * @return byte[] of the XML data
+     * @throws Exception
+     */
+    private byte[] serializeData() throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mNetworkRequestStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     *
+     * @param data XML data to parse from
+     * @throws Exception
+     */
+    private void deserializeData(byte[] data) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mNetworkRequestStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
index 71a7aaf..151ac02 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
@@ -20,7 +20,7 @@
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE;
 import static android.app.AppOpsManager.OP_CHANGE_WIFI_STATE;
-import static android.app.Notification.EXTRA_TEXT;
+import static android.app.Notification.EXTRA_BIG_TEXT;
 
 import static com.android.server.wifi.WifiNetworkSuggestionsManager.NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION;
 import static com.android.server.wifi.WifiNetworkSuggestionsManager.NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION;
@@ -48,6 +48,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSuggestion;
+import android.net.wifi.WifiScanner;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -154,10 +155,12 @@
         when(mContext.getApplicationInfo()).thenReturn(ourAppInfo);
         // test app info
         ApplicationInfo appInfO1 = new ApplicationInfo();
-        when(mPackageManager.getApplicationInfo(TEST_PACKAGE_1, 0)).thenReturn(appInfO1);
+        when(mPackageManager.getApplicationInfoAsUser(eq(TEST_PACKAGE_1), eq(0), anyInt()))
+            .thenReturn(appInfO1);
         when(mPackageManager.getApplicationLabel(appInfO1)).thenReturn(TEST_APP_NAME_1);
         ApplicationInfo appInfO2 = new ApplicationInfo();
-        when(mPackageManager.getApplicationInfo(TEST_PACKAGE_2, 0)).thenReturn(appInfO2);
+        when(mPackageManager.getApplicationInfoAsUser(eq(TEST_PACKAGE_2), eq(0), anyInt()))
+            .thenReturn(appInfO2);
         when(mPackageManager.getApplicationLabel(appInfO2)).thenReturn(TEST_APP_NAME_2);
 
         mWifiNetworkSuggestionsManager =
@@ -250,16 +253,16 @@
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
                         TEST_PACKAGE_1));
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_1,
                         TEST_PACKAGE_2));
 
         // Now remove all of them.
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.remove(networkSuggestionList1, TEST_UID_1,
-                        TEST_PACKAGE_1));
+                mWifiNetworkSuggestionsManager.remove(networkSuggestionList1,
+                        TEST_UID_1, TEST_PACKAGE_1));
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
-                mWifiNetworkSuggestionsManager.remove(networkSuggestionList2, TEST_UID_2,
-                        TEST_PACKAGE_2));
+                mWifiNetworkSuggestionsManager.remove(networkSuggestionList2,
+                        TEST_UID_1, TEST_PACKAGE_2));
 
         assertTrue(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions().isEmpty());
 
@@ -876,7 +879,7 @@
         validatePostConnectionBroadcastSent(TEST_PACKAGE_1, networkSuggestion);
 
         // Verify no more broadcast were sent out.
-        verifyNoMoreInteractions(mContext);
+        mInorder.verifyNoMoreInteractions();
     }
 
     /**
@@ -905,7 +908,11 @@
         verify(mWifiMetrics).incrementNetworkSuggestionApiNumConnectFailure();
 
         // Verify no more broadcast were sent out.
-        verifyNoMoreInteractions(mContext);
+        mInorder.verify(mWifiPermissionsUtil, never()).enforceCanAccessScanResults(
+                anyString(), anyInt());
+        mInorder.verify(mContext,  never()).sendBroadcastAsUser(
+                any(), any());
+
     }
 
     /**
@@ -966,7 +973,7 @@
         }
 
         // Verify no more broadcast were sent out.
-        verifyNoMoreInteractions(mContext);
+        mInorder.verifyNoMoreInteractions();
     }
 
     /**
@@ -1028,7 +1035,7 @@
         }
 
         // Verify no more broadcast were sent out.
-        verifyNoMoreInteractions(mContext);
+        mInorder.verifyNoMoreInteractions();
     }
 
     /**
@@ -1091,7 +1098,7 @@
         }
 
         // Verify no more broadcast were sent out.
-        verifyNoMoreInteractions(mContext);
+        mInorder.verifyNoMoreInteractions();
     }
 
     /**
@@ -1123,7 +1130,10 @@
                 TEST_BSSID);
 
         // Verify no broadcast was sent out.
-        verifyNoMoreInteractions(mContext, mWifiPermissionsUtil);
+        mInorder.verify(mWifiPermissionsUtil, never()).enforceCanAccessScanResults(
+                anyString(), anyInt());
+        mInorder.verify(mContext,  never()).sendBroadcastAsUser(
+                any(), any());
     }
 
     /**
@@ -1154,7 +1164,10 @@
                 TEST_BSSID);
 
         // Verify no broadcast was sent out.
-        verifyNoMoreInteractions(mContext, mWifiPermissionsUtil);
+        mInorder.verify(mWifiPermissionsUtil, never()).enforceCanAccessScanResults(
+                anyString(), anyInt());
+        mInorder.verify(mContext,  never()).sendBroadcastAsUser(
+                any(), any());
     }
 
     /**
@@ -1191,7 +1204,7 @@
                 .enforceCanAccessScanResults(TEST_PACKAGE_1, TEST_UID_1);
 
         // Verify no broadcast was sent out.
-        verifyNoMoreInteractions(mContext, mWifiPermissionsUtil);
+        mInorder.verifyNoMoreInteractions();
     }
 
     /**
@@ -1363,10 +1376,11 @@
 
     /**
      * Verify that we don't disconnect from the network if the only network suggestion matching the
-     * connected network is removed.
+     * connected network is removed when App doesn't have NetworkCarrierProvisioningPermission.
      */
     @Test
-    public void testRemoveNetworkSuggestionsMatchingConnectionSuccessWithOneMatch() {
+    public void
+            testRemoveNetworkSuggestionsMatchingConnectionSuccessWithOneMatchNoCarrierProvision() {
         WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
                 WifiConfigurationTestUtil.createOpenNetwork(), false, false, TEST_UID_1,
                 TEST_PACKAGE_1);
@@ -1374,6 +1388,8 @@
                 new ArrayList<WifiNetworkSuggestion>() {{
                     add(networkSuggestion);
                 }};
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(false);
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1));
@@ -1392,6 +1408,73 @@
     }
 
     /**
+     * Verify that we will disconnect from the network if the only network suggestion matching the
+     * connected network is removed when App has NetworkCarrierProvisioningPermission.
+     */
+    @Test
+    public void
+            testRemoveNetworkSuggestionsMatchingConnectionSuccessWithOneMatchCarrierProvision() {
+        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), false, false, TEST_UID_1,
+                TEST_PACKAGE_1);
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+
+        // Simulate connecting to the network.
+        mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, networkSuggestion.wifiConfiguration,
+                TEST_BSSID);
+
+        // Now remove the network suggestion and ensure we did trigger a disconnect.
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.remove(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1));
+        verify(mClientModeImpl).disconnectCommand();
+    }
+
+    /**
+     * Verify that we will disconnect from network when App has NetworkCarrierProvisioningPermission
+     * and removed all its suggestions by remove empty list.
+     */
+    @Test
+    public void
+            testRemoveAllNetworkSuggestionsMatchingConnectionSuccessWithOneMatchCarrierProvision() {
+        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), false, false, TEST_UID_1,
+                TEST_PACKAGE_1);
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(TEST_UID_1))
+                .thenReturn(true);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+
+        // Simulate connecting to the network.
+        mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE, networkSuggestion.wifiConfiguration,
+                TEST_BSSID);
+
+        // Now remove all network suggestion and ensure we did trigger a disconnect.
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.remove(new ArrayList<>(), TEST_UID_1,
+                        TEST_PACKAGE_1));
+        verify(mClientModeImpl).disconnectCommand();
+    }
+
+
+    /**
      * Verify that we do not disconnect from the network if there are network suggestion from
      * multiple apps matching the connected network when one of the apps is removed.
      */
@@ -1811,10 +1894,11 @@
     }
 
     /**
-     * Verify handling of user dismissal of the user approval notification.
+     * Verify user dismissal notification when first time add suggestions and dismissal the user
+     * approval notification when framework gets scan results.
      */
     @Test
-    public void testUserApprovalNotificationDismissal() {
+    public void testUserApprovalNotificationDismissalWhenGetScanResult() {
         WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
                 WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_1,
                 TEST_PACKAGE_1);
@@ -1825,6 +1909,11 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1));
+        validateUserApprovalNotification(TEST_APP_NAME_1);
+        // Simulate user dismissal notification.
+        sendBroadcastForUserAction(
+                NOTIFICATION_USER_DISMISSED_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
+        reset(mNotificationManger);
 
         // Simulate finding the network in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
@@ -1846,10 +1935,11 @@
     }
 
     /**
-     * Verify handling of user clicking allow on the user approval notification.
+     * Verify user dismissal notification when first time add suggestions and click on allow on
+     * the user approval notification when framework gets scan results.
      */
     @Test
-    public void testUserApprovalNotificationClickOnAllow() {
+    public void testUserApprovalNotificationClickOnAllowWhenGetScanResult() {
         WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
                 WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_1,
                 TEST_PACKAGE_1);
@@ -1860,6 +1950,12 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
                         TEST_PACKAGE_1));
+        validateUserApprovalNotification(TEST_APP_NAME_1);
+
+        // Simulate user dismissal notification.
+        sendBroadcastForUserAction(
+                NOTIFICATION_USER_DISMISSED_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
+        reset(mNotificationManger);
 
         // Simulate finding the network in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
@@ -1885,10 +1981,11 @@
     }
 
     /**
-     * Verify handling of user clicking disallow on the user approval notification.
+     * Verify user dismissal notification when first time add suggestions and click on disallow on
+     * the user approval notification when framework gets scan results.
      */
     @Test
-    public void testUserApprovalNotificationClickOnDisallow() {
+    public void testUserApprovalNotificationClickOnDisallowWhenGetScanResult() {
         WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
                 WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_1,
                 TEST_PACKAGE_1);
@@ -1901,6 +1998,12 @@
                         TEST_PACKAGE_1));
         verify(mAppOpsManager).startWatchingMode(eq(OPSTR_CHANGE_WIFI_STATE),
                 eq(TEST_PACKAGE_1), mAppOpChangedListenerCaptor.capture());
+        validateUserApprovalNotification(TEST_APP_NAME_1);
+
+        // Simulate user dismissal notification.
+        sendBroadcastForUserAction(
+                NOTIFICATION_USER_DISMISSED_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
+        reset(mNotificationManger);
 
         // Simulate finding the network in scan results.
         mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
@@ -1979,6 +2082,134 @@
     }
 
     /**
+     * Verify get hidden networks from All user approve network suggestions
+     */
+    @Test
+    public void testGetHiddenNetworks() {
+
+        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_1,
+                TEST_PACKAGE_1);
+        WifiNetworkSuggestion hiddenNetworkSuggestion1 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskHiddenNetwork(), true, false, TEST_UID_1,
+                TEST_PACKAGE_1);
+        WifiNetworkSuggestion hiddenNetworkSuggestion2 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createPskHiddenNetwork(), true, false, TEST_UID_2,
+                TEST_PACKAGE_2);
+        List<WifiNetworkSuggestion> networkSuggestionList1 =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                    add(hiddenNetworkSuggestion1);
+                }};
+        List<WifiNetworkSuggestion> networkSuggestionList2 =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(hiddenNetworkSuggestion2);
+                }};
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
+                        TEST_PACKAGE_1));
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
+                        TEST_PACKAGE_2));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(false, TEST_PACKAGE_2);
+        List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks =
+                mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList();
+        assertEquals(1, hiddenNetworks.size());
+        assertEquals(hiddenNetworkSuggestion1.wifiConfiguration.SSID, hiddenNetworks.get(0).ssid);
+    }
+
+    /**
+     * Verify handling of user clicking allow on the user approval notification when first time
+     * add suggestions.
+     */
+    @Test
+    public void testUserApprovalNotificationClickOnAllowDuringAddingSuggestions() {
+        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_1,
+                TEST_PACKAGE_1);
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1));
+        validateUserApprovalNotification(TEST_APP_NAME_1);
+
+        // Simulate user clicking on allow in the notification.
+        sendBroadcastForUserAction(
+                NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
+        // Cancel the notification.
+        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+
+        // Verify config store interactions.
+        verify(mWifiConfigManager, times(2)).saveToStore(true);
+        assertTrue(mDataSource.hasNewDataToSerialize());
+
+        reset(mNotificationManger);
+        // We should not resend the notification next time the network is found in scan results.
+        mWifiNetworkSuggestionsManager.getNetworkSuggestionsForScanDetail(
+                createScanDetailForNetwork(networkSuggestion.wifiConfiguration));
+        verifyNoMoreInteractions(mNotificationManger);
+    }
+
+    /**
+     * Verify handling of user clicking Disallow on the user approval notification when first time
+     * add suggestions.
+     */
+    @Test
+    public void testUserApprovalNotificationClickOnDisallowWhenAddSuggestions() {
+        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_1,
+                TEST_PACKAGE_1);
+        List<WifiNetworkSuggestion> networkSuggestionList =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+                }};
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1));
+        verify(mAppOpsManager).startWatchingMode(eq(OPSTR_CHANGE_WIFI_STATE),
+                eq(TEST_PACKAGE_1), mAppOpChangedListenerCaptor.capture());
+        validateUserApprovalNotification(TEST_APP_NAME_1);
+
+        // Simulate user clicking on disallow in the notification.
+        sendBroadcastForUserAction(
+                NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION, TEST_PACKAGE_1, TEST_UID_1);
+        // Ensure we turn off CHANGE_WIFI_STATE app-ops.
+        verify(mAppOpsManager).setMode(
+                OP_CHANGE_WIFI_STATE, TEST_UID_1,
+                TEST_PACKAGE_1, MODE_IGNORED);
+        // Cancel the notification.
+        verify(mNotificationManger).cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
+
+        // Verify config store interactions.
+        verify(mWifiConfigManager, times(2)).saveToStore(true);
+        assertTrue(mDataSource.hasNewDataToSerialize());
+
+        reset(mNotificationManger);
+
+        // Now trigger the app-ops callback to ensure we remove all of their suggestions.
+        AppOpsManager.OnOpChangedListener listener = mAppOpChangedListenerCaptor.getValue();
+        assertNotNull(listener);
+        when(mAppOpsManager.unsafeCheckOpNoThrow(
+                OPSTR_CHANGE_WIFI_STATE, TEST_UID_1,
+                TEST_PACKAGE_1))
+                .thenReturn(MODE_IGNORED);
+        listener.onOpChanged(OPSTR_CHANGE_WIFI_STATE, TEST_PACKAGE_1);
+        mLooper.dispatchAll();
+        assertTrue(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions().isEmpty());
+
+        // Assuming the user re-enabled the app again & added the same suggestions back.
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList, TEST_UID_1,
+                        TEST_PACKAGE_1));
+        validateUserApprovalNotification(TEST_APP_NAME_1);
+        verifyNoMoreInteractions(mNotificationManger);
+    }
+
+    /**
      * Creates a scan detail corresponding to the provided network values.
      */
     private ScanDetail createScanDetailForNetwork(WifiConfiguration configuration) {
@@ -2007,7 +2238,7 @@
 
     private boolean checkUserApprovalNotificationParams(
             Notification notification, String expectedAppName) {
-        if (!notification.extras.getString(EXTRA_TEXT).contains(expectedAppName)) return false;
+        if (!notification.extras.getString(EXTRA_BIG_TEXT).contains(expectedAppName)) return false;
         return true;
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 73b2ee1..b22ed91 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -174,6 +174,7 @@
     private static final String WIFI_IFACE_NAME2 = "wlan1";
     private static final String TEST_COUNTRY_CODE = "US";
     private static final String TEST_FACTORY_MAC = "10:22:34:56:78:92";
+    private static final String TEST_FQDN = "testfqdn";
     private static final List<WifiConfiguration> TEST_WIFI_CONFIGURATION_LIST = Arrays.asList(
             WifiConfigurationTestUtil.generateWifiConfig(
                     0, 1000000, "\"red\"", true, true, null, null),
@@ -350,7 +351,8 @@
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        when(mPackageManager.getApplicationInfo(any(), anyInt())).thenReturn(mApplicationInfo);
+        when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(mApplicationInfo);
         when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
         doNothing().when(mFrameworkFacade).registerContentObserver(eq(mContext), any(),
                 anyBoolean(), any());
@@ -546,7 +548,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
         when(mSettingsStore.handleWifiToggled(anyBoolean())).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
 
@@ -565,7 +567,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(false);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
 
@@ -602,6 +604,44 @@
     }
 
     /**
+     * Verify that wifi can be enabled by the DO apps targeting Q SDK.
+     */
+    @Test
+    public void testSetWifiEnabledSuccessForDOAppsTargetingQSDK() throws Exception {
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
+        when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(
+                Process.myUid(), DeviceAdminInfo.USES_POLICY_DEVICE_OWNER))
+                .thenReturn(true);
+
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that wifi can be enabled by the system apps targeting Q SDK.
+     */
+    @Test
+    public void testSetWifiEnabledSuccessForSystemAppsTargetingQSDK() throws Exception {
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
      * Verify that wifi can be enabled by the apps targeting pre-Q SDK.
      */
     @Test
@@ -609,7 +649,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
 
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
@@ -619,6 +659,20 @@
     }
 
     /**
+     * Verify that wifi is not enabled when wificontroller is not started.
+     */
+    @Test
+    public void testSetWifiEnabledFailureWhenInCryptDebounce() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(true);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true));
+        verifyZeroInteractions(mWifiController);
+    }
+
+    /**
      * Verify that wifi cannot be enabled by the apps targeting Q SDK.
      */
     @Test
@@ -626,7 +680,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(false);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
 
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
@@ -643,7 +697,7 @@
         doThrow(new SecurityException()).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         try {
             mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true);
@@ -663,7 +717,7 @@
         doReturn(AppOpsManager.MODE_IGNORED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
 
         mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, true);
@@ -695,7 +749,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
         when(mSettingsStore.handleWifiToggled(eq(true))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
         when(mContext.checkPermission(
@@ -739,7 +793,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
         when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
         mWifiServiceImpl.checkAndStartWifi();
 
@@ -814,6 +868,45 @@
     }
 
     /**
+     * Verify that wifi can be disabled by the PO apps targeting Q SDK.
+     */
+    @Test
+    public void testSetWifiDisabledSuccessForPOAppsTargetingQSDK() throws Exception {
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
+        when(mDevicePolicyManagerInternal.isActiveAdminWithPolicy(
+                Process.myUid(), DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))
+                .thenReturn(true);
+
+        when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
+
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+    /**
+     * Verify that wifi can be disabled by the system apps targeting Q SDK.
+     */
+    @Test
+    public void testSetWifiDisabledSuccessForSystemAppsTargetingQSDK() throws Exception {
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+                .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
+
+        verify(mWifiController).sendMessage(eq(CMD_WIFI_TOGGLED));
+    }
+
+
+    /**
      * Verify that wifi can be disabled by the apps targeting pre-Q SDK.
      */
     @Test
@@ -821,7 +914,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
 
         when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
@@ -831,6 +924,20 @@
     }
 
     /**
+     * Verify that wifi is not disabled when wificontroller is not started.
+     */
+    @Test
+    public void testSetWifiDisabledFailureWhenInCryptDebounce() throws Exception {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(true);
+        when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(false);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        assertFalse(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
+        verifyZeroInteractions(mWifiController);
+    }
+
+    /**
      * Verify that wifi cannot be disabled by the apps targeting Q SDK.
      */
     @Test
@@ -838,7 +945,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(false);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(false);
 
         when(mSettingsStore.handleWifiToggled(eq(false))).thenReturn(true);
         when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
@@ -1087,6 +1194,19 @@
     }
 
     /**
+     * Verify does not start softap when wificontroller is not started.
+     */
+    @Test
+    public void testStartSoftApWhenInCryptDebounce() {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(true);
+
+        WifiConfiguration config = createValidSoftApConfiguration();
+        boolean result = mWifiServiceImpl.startSoftAp(config);
+        assertFalse(result);
+        verifyZeroInteractions(mWifiController);
+    }
+
+    /**
      * Verify a SecurityException is thrown when a caller without the correct permission attempts to
      * start softap.
      */
@@ -1110,6 +1230,18 @@
     }
 
     /**
+     * Verify does not stop softap when wificontroller is not started.
+     */
+    @Test
+    public void testStopSoftApWhenInCryptDebounce() {
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(true);
+
+        boolean result = mWifiServiceImpl.stopSoftAp();
+        assertFalse(result);
+        verifyZeroInteractions(mWifiController);
+    }
+
+    /**
      * Verify SecurityException is thrown when a caller without the correct permission attempts to
      * stop softap.
      */
@@ -1464,6 +1596,19 @@
     }
 
     /**
+     * Only start LocalOnlyHotspot if device is in crypt debounce mode.
+     */
+    @Test
+    public void testStartLocalOnlyHotspotFailsIfInCryptDebounce() throws Exception {
+        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
+        when(mFrameworkFacade.isAppForeground(anyInt())).thenReturn(true);
+        when(mFrameworkFacade.inStorageManagerCryptKeeperBounce()).thenReturn(true);
+        int result = mWifiServiceImpl.startLocalOnlyHotspot(mAppMessenger, mAppBinder,
+                TEST_PACKAGE_NAME);
+        assertEquals(LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE, result);
+    }
+
+    /**
      * Only start LocalOnlyHotspot if we are not tethering.
      */
     @Test
@@ -1481,7 +1626,6 @@
 
         // Start another session without a stop, that should fail.
         assertFalse(mWifiServiceImpl.startSoftAp(createValidSoftApConfiguration()));
-
         verifyNoMoreInteractions(mWifiController);
     }
 
@@ -2518,9 +2662,9 @@
         PackageManager pm = mock(PackageManager.class);
         when(pm.hasSystemFeature(PackageManager.FEATURE_WIFI_PASSPOINT)).thenReturn(true);
         when(mContext.getPackageManager()).thenReturn(pm);
-        when(pm.getApplicationInfo(any(), anyInt())).thenReturn(mApplicationInfo);
+        when(pm.getApplicationInfoAsUser(any(), anyInt(), anyInt())).thenReturn(mApplicationInfo);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
 
         when(mClientModeImpl.syncAddOrUpdatePasspointConfig(any(),
                 any(PasspointConfiguration.class), anyInt(), eq(TEST_PACKAGE_NAME))).thenReturn(
@@ -2647,77 +2791,57 @@
     }
 
     /**
-     * Verify that the call to getPasspointConfigurations is not redirected to specific API
-     * syncGetPasspointConfigs when the caller doesn't have NETWORK_SETTINGS permissions and
-     * NETWORK_SETUP_WIZARD.
-     */
-    @Test(expected = SecurityException.class)
-    public void testGetPasspointConfigurationsWithOutPermissions() {
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
-        when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false);
-
-        mWifiServiceImpl.getPasspointConfigurations(TEST_PACKAGE_NAME);
-    }
-
-    /**
-     * Verify that getPasspointConfigurations called by apps that has invalid package will
-     * throw {@link SecurityException}.
-     */
-    @Test(expected = SecurityException.class)
-    public void testGetPasspointConfigurationWithInvalidPackage() {
-        doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(),
-                eq(TEST_PACKAGE_NAME));
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
-        when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(true);
-
-        mWifiServiceImpl.getPasspointConfigurations(TEST_PACKAGE_NAME);
-    }
-
-    /**
-     * Verify that getPasspointConfigurations called by apps targeting below Q SDK will return
-     * empty list if the caller doesn't have NETWORK_SETTINGS permissions and NETWORK_SETUP_WIZARD.
+     * Verify the call to getPasspointConfigurations when the caller doesn't have
+     * NETWORK_SETTINGS and NETWORK_SETUP_WIZARD permissions.
      */
     @Test
-    public void testGetPasspointConfigurationForAppsTargetingBelowQSDK() {
+    public void testGetPasspointConfigurationsWithOutPrivilegedPermissions() {
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
         when(mWifiPermissionsUtil.checkNetworkSetupWizardPermission(anyInt())).thenReturn(false);
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(eq(TEST_PACKAGE_NAME),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
 
-        List<PasspointConfiguration> result = mWifiServiceImpl.getPasspointConfigurations(
-                TEST_PACKAGE_NAME);
-        assertNotNull(result);
-        assertEquals(0, result.size());
+        mWifiServiceImpl.getPasspointConfigurations(TEST_PACKAGE_NAME);
+
+        verify(mClientModeImpl).syncGetPasspointConfigs(any(), eq(false));
     }
 
     /**
-     * Verify that the call to removePasspointConfiguration is not redirected to specific API
-     * syncRemovePasspointConfig when the caller doesn't have NETWORK_SETTINGS and
+     * Verify that the call to getPasspointConfigurations when the caller does have
+     * NETWORK_SETTINGS permission.
+     */
+    @Test
+    public void testGetPasspointConfigurationsWithPrivilegedPermissions() {
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+
+        mWifiServiceImpl.getPasspointConfigurations(TEST_PACKAGE_NAME);
+
+        verify(mClientModeImpl).syncGetPasspointConfigs(any(), eq(true));
+    }
+
+    /**
+     * Verify the call to removePasspointConfigurations when the caller doesn't have
+     * NETWORK_SETTINGS and NETWORK_CARRIER_PROVISIONING permissions.
+     */
+    @Test
+    public void testRemovePasspointConfigurationWithOutPrivilegedPermissions() {
+        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(anyInt())).thenReturn(
+                false);
+
+        mWifiServiceImpl.removePasspointConfiguration(TEST_FQDN, TEST_PACKAGE_NAME);
+        verify(mClientModeImpl).syncRemovePasspointConfig(any(), eq(false), eq(TEST_FQDN));
+    }
+
+    /**
+     * Verify the call to removePasspointConfigurations when the caller does have
      * NETWORK_CARRIER_PROVISIONING permission.
      */
-    @Test(expected = SecurityException.class)
-    public void testRemovePasspointConfigurationWithOutPermissions() {
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
-        when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(anyInt())).thenReturn(
-                false);
-
-        mWifiServiceImpl.removePasspointConfiguration(null, null);
-    }
-
-    /**
-     * Verify that the call to removePasspointConfiguration for apps targeting below Q SDK will
-     * return false if the caller doesn't have NETWORK_SETTINGS and NETWORK_CARRIER_PROVISIONING
-     * permission.
-     */
     @Test
-    public void testRemovePasspointConfigurationForAppsTargetingBelowQSDK() {
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
+    public void testRemovePasspointConfigurationWithPrivilegedPermissions() {
         when(mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(anyInt())).thenReturn(
-                false);
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(isNull(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                true);
 
-        assertFalse(mWifiServiceImpl.removePasspointConfiguration(null, null));
+        mWifiServiceImpl.removePasspointConfiguration(TEST_FQDN, TEST_PACKAGE_NAME);
+        verify(mClientModeImpl).syncRemovePasspointConfig(any(), eq(true), eq(TEST_FQDN));
     }
 
     /**
@@ -3308,6 +3432,25 @@
     }
 
     /**
+     * Verifies that entering airplane mode does not reset country code.
+     */
+    @Test
+    public void testEnterAirplaneModeNotResetCountryCode() {
+        mWifiServiceImpl.checkAndStartWifi();
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                (IntentFilter) argThat((IntentFilter filter) ->
+                        filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)));
+
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
+
+        // Send the broadcast
+        Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
+
+        verifyNoMoreInteractions(mWifiCountryCode);
+    }
+
+    /**
      * Verify calls to notify users of a softap config change check the NETWORK_SETTINGS permission.
      */
     @Test
@@ -3513,7 +3656,8 @@
         mWifiServiceImpl.mClientModeImplChannel = mAsyncChannel;
         when(mClientModeImpl.syncGetConfiguredNetworks(anyInt(), any(), anyInt()))
                 .thenReturn(Arrays.asList(openNetwork, eapNetwork));
-        when(mClientModeImpl.syncGetPasspointConfigs(any())).thenReturn(Arrays.asList(config));
+        when(mClientModeImpl.syncGetPasspointConfigs(any(), anyBoolean()))
+                .thenReturn(Arrays.asList(config));
 
         mWifiServiceImpl.factoryReset(TEST_PACKAGE_NAME);
         mLooper.dispatchAll();
@@ -3522,7 +3666,7 @@
         verify(mClientModeImpl).syncRemoveNetwork(mAsyncChannel, openNetwork.networkId);
         verify(mClientModeImpl).syncRemoveNetwork(mAsyncChannel, eapNetwork.networkId);
         verify(mWifiKeyStore).removeKeys(eapNetwork.enterpriseConfig, true);
-        verify(mClientModeImpl).syncRemovePasspointConfig(mAsyncChannel, fqdn);
+        verify(mClientModeImpl).syncRemovePasspointConfig(mAsyncChannel, true, fqdn);
         verify(mWifiConfigManager).clearDeletedEphemeralNetworks();
         verify(mClientModeImpl).clearNetworkRequestUserApprovedAccessPoints();
         verify(mWifiNetworkSuggestionsManager).clear();
@@ -3545,8 +3689,9 @@
         mLooper.dispatchAll();
 
         verify(mClientModeImpl).syncGetConfiguredNetworks(anyInt(), any(), anyInt());
-        verify(mClientModeImpl, never()).syncGetPasspointConfigs(any());
-        verify(mClientModeImpl, never()).syncRemovePasspointConfig(any(), anyString());
+        verify(mClientModeImpl, never()).syncGetPasspointConfigs(any(), anyBoolean());
+        verify(mClientModeImpl, never()).syncRemovePasspointConfig(
+                any(), anyBoolean(), anyString());
         verify(mWifiConfigManager).clearDeletedEphemeralNetworks();
         verify(mClientModeImpl).clearNetworkRequestUserApprovedAccessPoints();
         verify(mWifiNetworkSuggestionsManager).clear();
@@ -3569,7 +3714,7 @@
         } catch (SecurityException e) {
         }
         verify(mClientModeImpl, never()).syncGetConfiguredNetworks(anyInt(), any(), anyInt());
-        verify(mClientModeImpl, never()).syncGetPasspointConfigs(any());
+        verify(mClientModeImpl, never()).syncGetPasspointConfigs(any(), eq(false));
     }
 
     /**
@@ -3600,7 +3745,7 @@
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mClientModeImpl.syncAddOrUpdateNetwork(any(), any())).thenReturn(0);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
 
         WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
         assertEquals(0, mWifiServiceImpl.addOrUpdateNetwork(config, TEST_PACKAGE_NAME));
@@ -3743,7 +3888,7 @@
         doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
                 .noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, Process.myUid(), TEST_PACKAGE_NAME);
         when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(),
-                eq(Build.VERSION_CODES.Q))).thenReturn(true);
+                eq(Build.VERSION_CODES.Q), anyInt())).thenReturn(true);
 
         mWifiServiceImpl.enableNetwork(TEST_NETWORK_ID, true, TEST_PACKAGE_NAME);
 
@@ -3817,8 +3962,8 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL,
                 mWifiServiceImpl.removeNetworkSuggestions(mock(List.class), TEST_PACKAGE_NAME));
 
-        verify(mWifiNetworkSuggestionsManager, times(2)).remove(
-                any(), eq(Binder.getCallingUid()), eq(TEST_PACKAGE_NAME));
+        verify(mWifiNetworkSuggestionsManager, times(2)).remove(any(), anyInt(),
+                eq(TEST_PACKAGE_NAME));
     }
 
     /**
@@ -4135,4 +4280,17 @@
         } catch (RemoteException e) {
         }
     }
+
+    /**
+     * Test to verify that the lock mode is verified before dispatching the operation
+     *
+     * Steps: call acquireWifiLock with an invalid lock mode.
+     * Expected: the call should throw an IllegalArgumentException.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void acquireWifiLockShouldThrowExceptionOnInvalidLockMode() throws Exception {
+        final int wifiLockModeInvalid = -1;
+
+        mWifiServiceImpl.acquireWifiLock(mAppBinder, wifiLockModeInvalid, "", null);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
index 5a95b95..c33a4d5 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
@@ -730,7 +730,7 @@
                 IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN
                 | IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
             );
-        int expected = (
+        long expected = (
                 WifiManager.WIFI_FEATURE_SCANNER
                 | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS);
         assertEquals(expected, mWifiVendorHal.wifiFeatureMaskFromStaCapabilities(caps));
@@ -749,7 +749,7 @@
                         | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT
                         | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2AP_RTT
         );
-        int expected = (
+        long expected = (
                 WifiManager.WIFI_FEATURE_TX_POWER_LIMIT
                         | WifiManager.WIFI_FEATURE_D2D_RTT
                         | WifiManager.WIFI_FEATURE_D2AP_RTT
@@ -768,7 +768,7 @@
                 android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.SET_LATENCY_MODE
                         | android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT
         );
-        int expected = (
+        long expected = (
                 WifiManager.WIFI_FEATURE_LOW_LATENCY
                         | WifiManager.WIFI_FEATURE_D2D_RTT
         );
@@ -794,7 +794,7 @@
                 add(IfaceType.STA);
                 add(IfaceType.P2P);
             }};
-        int expectedFeatureSet = (
+        long expectedFeatureSet = (
                 WifiManager.WIFI_FEATURE_SCANNER
                         | WifiManager.WIFI_FEATURE_LINK_LAYER_STATS
                         | WifiManager.WIFI_FEATURE_TX_POWER_LIMIT
@@ -3110,6 +3110,7 @@
         radioModeInfos.add(radioModeInfo1);
 
         mIWifiChipEventCallbackV12.onRadioModeChange(radioModeInfos);
+        mLooper.dispatchAll();
         verify(mVendorHalRadioModeChangeHandler).onDbs();
 
         verifyNoMoreInteractions(mVendorHalRadioModeChangeHandler);
@@ -3142,6 +3143,7 @@
         radioModeInfos.add(radioModeInfo1);
 
         mIWifiChipEventCallbackV12.onRadioModeChange(radioModeInfos);
+        mLooper.dispatchAll();
         verify(mVendorHalRadioModeChangeHandler).onSbs(WifiScanner.WIFI_BAND_5_GHZ);
 
         verifyNoMoreInteractions(mVendorHalRadioModeChangeHandler);
@@ -3171,6 +3173,7 @@
         radioModeInfos.add(radioModeInfo0);
 
         mIWifiChipEventCallbackV12.onRadioModeChange(radioModeInfos);
+        mLooper.dispatchAll();
         verify(mVendorHalRadioModeChangeHandler).onScc(WifiScanner.WIFI_BAND_5_GHZ);
 
         verifyNoMoreInteractions(mVendorHalRadioModeChangeHandler);
@@ -3200,6 +3203,7 @@
         radioModeInfos.add(radioModeInfo0);
 
         mIWifiChipEventCallbackV12.onRadioModeChange(radioModeInfos);
+        mLooper.dispatchAll();
         verify(mVendorHalRadioModeChangeHandler).onMcc(WifiScanner.WIFI_BAND_BOTH);
 
         verifyNoMoreInteractions(mVendorHalRadioModeChangeHandler);
@@ -3230,6 +3234,7 @@
         radioModeInfos.add(radioModeInfo1);
 
         mIWifiChipEventCallbackV12.onRadioModeChange(radioModeInfos);
+        mLooper.dispatchAll();
         // Ignored....
 
         verifyNoMoreInteractions(mVendorHalRadioModeChangeHandler);
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
index f2cc45f..35916d2 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -161,7 +161,8 @@
 
         // by default pretend to be an old API: i.e. allow Responders configured as *ANY*. This
         // allows older (more extrensive) tests to run.
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt())).thenReturn(true);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
+            .thenReturn(true);
         when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
 
         mDut = new WifiAwareStateManager();
@@ -1017,7 +1018,8 @@
      */
     @Test
     public void testDataPathResonderMacPassphraseNoPeerIdSuccessNonLegacy() throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
+            .thenReturn(false);
         testDataPathResponderUtility(false, false, false, true, true);
     }
 
@@ -1028,7 +1030,8 @@
     @Test
     public void testDataPathResonderMacOpenNoPeerIdNoPmkPassphraseSuccessNonLegacy()
             throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
+            .thenReturn(false);
         testDataPathResponderUtility(false, false, false, false, true);
     }
 
@@ -1074,7 +1077,8 @@
      */
     @Test
     public void testDataPathResonderDirectNoMacPassphraseSuccessNonLegacy() throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
+            .thenReturn(false);
         testDataPathResponderUtility(true, false, false, true, true);
     }
 
@@ -1084,7 +1088,8 @@
      */
     @Test
     public void testDataPathResonderDirectNoMacNoPmkPassphraseSuccessNonLegacy() throws Exception {
-        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.isTargetSdkLessThan(anyString(), anyInt(), anyInt()))
+            .thenReturn(false);
         testDataPathResponderUtility(true, false, false, false, true);
     }
 
@@ -1473,7 +1478,7 @@
         InOrder inOrderM = inOrder(mAwareMetricsMock);
 
         boolean isLegacy = mWifiPermissionsUtil.isTargetSdkLessThan("anything",
-                Build.VERSION_CODES.P);
+                Build.VERSION_CODES.P, 0);
 
         if (providePmk) {
             when(mPermissionsWrapperMock.getUidPermission(
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPMatcherTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPMatcherTest.java
index 4d4ea44..df9c332 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPMatcherTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPMatcherTest.java
@@ -33,7 +33,6 @@
 import com.android.server.wifi.hotspot2.anqp.ThreeGPPNetworkElement;
 import com.android.server.wifi.hotspot2.anqp.eap.AuthParam;
 import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
-import com.android.server.wifi.hotspot2.anqp.eap.InnerAuthEAP;
 import com.android.server.wifi.hotspot2.anqp.eap.NonEAPInnerAuth;
 
 import org.junit.Test;
@@ -102,7 +101,7 @@
      */
     @Test
     public void matchRoamingConsortiumWithNullElement() throws Exception {
-        assertFalse(ANQPMatcher.matchRoamingConsortium(null, new long[0]));
+        assertFalse(ANQPMatcher.matchRoamingConsortium(null, new long[0], false));
     }
 
     /**
@@ -116,23 +115,22 @@
         long oi = 0x1234L;
         RoamingConsortiumElement element =
                 new RoamingConsortiumElement(Arrays.asList(new Long[] {oi}));
-        assertTrue(ANQPMatcher.matchRoamingConsortium(element, new long[] {oi}));
+        assertTrue(ANQPMatcher.matchRoamingConsortium(element, new long[] {oi}, false));
     }
 
     /**
-     * Verify that an indeterminate match will be returned when matching a null NAI Realm
+     * Verify that no match will be returned when matching a null NAI Realm
      * ANQP element.
      *
      * @throws Exception
      */
     @Test
     public void matchNAIRealmWithNullElement() throws Exception {
-        assertEquals(AuthMatch.INDETERMINATE, ANQPMatcher.matchNAIRealm(null, "test.com",
-                EAPConstants.EAP_TLS, new InnerAuthEAP(EAPConstants.EAP_TTLS)));
+        assertFalse(ANQPMatcher.matchNAIRealm(null, "test.com"));
     }
 
     /**
-     * Verify that an indeterminate match will be returned when matching a NAI Realm
+     * Verify that no match will be returned when matching a NAI Realm
      * ANQP element contained no NAI realm data.
      *
      * @throws Exception
@@ -140,8 +138,7 @@
     @Test
     public void matchNAIRealmWithEmtpyRealmData() throws Exception {
         NAIRealmElement element = new NAIRealmElement(new ArrayList<NAIRealmData>());
-        assertEquals(AuthMatch.INDETERMINATE, ANQPMatcher.matchNAIRealm(element, "test.com",
-                EAPConstants.EAP_TLS, null));
+        assertFalse(ANQPMatcher.matchNAIRealm(element, "test.com"));
     }
 
     /**
@@ -157,38 +154,11 @@
                 Arrays.asList(new String[] {realm}), new ArrayList<EAPMethod>());
         NAIRealmElement element = new NAIRealmElement(
                 Arrays.asList(new NAIRealmData[] {realmData}));
-        assertEquals(AuthMatch.REALM, ANQPMatcher.matchNAIRealm(element, realm,
-                EAPConstants.EAP_TLS, null));
+        assertTrue(ANQPMatcher.matchNAIRealm(element, realm));
     }
 
     /**
-     * Verify that method match will be returned when the specified EAP
-     * method only matches a eap method in the NAI Realm ANQP element if the element does not have
-     * auth params.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void matchNAIRealmWithMethodMatch() throws Exception {
-        // Test data.
-        String providerRealm = "test.com";
-        String anqpRealm = "test2.com";
-        NonEAPInnerAuth authParam = new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_MSCHAP);
-        int eapMethodID = EAPConstants.EAP_TLS;
-
-        // Setup NAI Realm element that has EAP method and no auth params.
-        EAPMethod method = new EAPMethod(eapMethodID, new HashMap<Integer, Set<AuthParam>>());
-        NAIRealmData realmData = new NAIRealmData(
-                Arrays.asList(new String[]{anqpRealm}), Arrays.asList(new EAPMethod[]{method}));
-        NAIRealmElement element = new NAIRealmElement(
-                Arrays.asList(new NAIRealmData[]{realmData}));
-
-        assertEquals(AuthMatch.METHOD,
-                ANQPMatcher.matchNAIRealm(element, providerRealm, eapMethodID, authParam));
-    }
-
-    /**
-     * Verify that a realm and method match will be returned when the specified realm and EAP
+     * Verify that a realm match will be returned when the specified realm and EAP
      * method matches a realm in the NAI Realm ANQP element.
      *
      * @throws Exception
@@ -206,12 +176,11 @@
         NAIRealmElement element = new NAIRealmElement(
                 Arrays.asList(new NAIRealmData[] {realmData}));
 
-        assertEquals(AuthMatch.REALM | AuthMatch.METHOD,
-                ANQPMatcher.matchNAIRealm(element, realm, eapMethodID, null));
+        assertTrue(ANQPMatcher.matchNAIRealm(element, realm));
     }
 
     /**
-     * Verify that an exact match will be returned when the specified realm, EAP
+     * Verify that a realm match will be returned when the specified realm, EAP
      * method, and the authentication parameter matches a realm with the associated EAP method and
      * authentication parameter in the NAI Realm ANQP element.
      *
@@ -235,12 +204,11 @@
         NAIRealmElement element = new NAIRealmElement(
                 Arrays.asList(new NAIRealmData[] {realmData}));
 
-        assertEquals(AuthMatch.EXACT,
-                ANQPMatcher.matchNAIRealm(element, realm, eapMethodID, authParam));
+        assertTrue(ANQPMatcher.matchNAIRealm(element, realm));
     }
 
     /**
-     * Verify that a mismatch (AuthMatch.NONE) will be returned when the specified EAP method
+     * Verify that a realm match will be returned when the specified EAP method
      * doesn't match with the corresponding EAP method in the NAI Realm ANQP element.
      *
      * @throws Exception
@@ -263,12 +231,11 @@
         NAIRealmElement element = new NAIRealmElement(
                 Arrays.asList(new NAIRealmData[] {realmData}));
 
-        assertEquals(AuthMatch.NONE,
-                ANQPMatcher.matchNAIRealm(element, realm, EAPConstants.EAP_TLS, null));
+        assertTrue(ANQPMatcher.matchNAIRealm(element, realm));
     }
 
     /**
-     * Verify that a mismatch (AuthMatch.NONE) will be returned when the specified authentication
+     * Verify that a realm match will be returned when the specified authentication
      * parameter doesn't match with the corresponding authentication parameter in the NAI Realm
      * ANQP element.
      *
@@ -292,10 +259,8 @@
         NAIRealmElement element = new NAIRealmElement(
                 Arrays.asList(new NAIRealmData[] {realmData}));
 
-        // Mismatch in authentication type.
-        assertEquals(AuthMatch.NONE,
-                ANQPMatcher.matchNAIRealm(element, realm, EAPConstants.EAP_TTLS,
-                        new NonEAPInnerAuth(NonEAPInnerAuth.AUTH_TYPE_PAP)));
+        // Mismatch in authentication type which we ignore.
+        assertTrue(ANQPMatcher.matchNAIRealm(element, realm));
     }
 
     /**
@@ -458,4 +423,64 @@
         assertEquals(-1,
                 ANQPMatcher.getCarrierEapMethodFromMatchingNAIRealm(TEST_3GPP_FQDN, element));
     }
+
+    /**
+     * Verify that match is found when HomeOI contains some of the RCOIs advertised by an AP marked
+     * as not required.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAnyHomeOi() throws Exception {
+        long[] providerOis = new long[] {0x1234L, 0x5678L, 0xabcdL};
+        Long[] anqpOis = new Long[] {0x1234L, 0x5678L, 0xdeadL, 0xf0cdL};
+        RoamingConsortiumElement element =
+                new RoamingConsortiumElement(Arrays.asList(anqpOis));
+        assertTrue(ANQPMatcher.matchRoamingConsortium(element, providerOis, false));
+    }
+
+    /**
+     * Verify that no match is found when HomeOI does not contain any of the RCOIs advertised by an
+     * AP marked as not required.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAnyHomeOiNegative() throws Exception {
+        long[] providerOis = new long[] {0x1234L, 0x5678L, 0xabcdL};
+        Long[] anqpOis = new Long[] {0xabc2L, 0x1232L};
+        RoamingConsortiumElement element =
+                new RoamingConsortiumElement(Arrays.asList(anqpOis));
+        assertFalse(ANQPMatcher.matchRoamingConsortium(element, providerOis, false));
+    }
+
+    /**
+     * Verify that match is found when HomeOI contains all of the RCOIs advertised by an AP marked
+     * as required.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAllHomeOi() throws Exception {
+        long[] providerOis = new long[] {0x1234L, 0x5678L, 0xabcdL};
+        Long[] anqpOis = new Long[] {0x1234L, 0x5678L, 0xabcdL, 0xdeadL, 0xf0cdL};
+        RoamingConsortiumElement element =
+                new RoamingConsortiumElement(Arrays.asList(anqpOis));
+        assertTrue(ANQPMatcher.matchRoamingConsortium(element, providerOis, true));
+    }
+
+    /**
+     * Verify that match is not found when HomeOI does not contain all of the RCOIs advertised by an
+     * AP marked as required.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAllHomeOiNegative() throws Exception {
+        long[] providerOis = new long[] {0x1234L, 0x5678L, 0xabcdL};
+        Long[] anqpOis = new Long[] {0x1234L, 0x5678L, 0xdeadL, 0xf0cdL};
+        RoamingConsortiumElement element =
+                new RoamingConsortiumElement(Arrays.asList(anqpOis));
+        assertFalse(ANQPMatcher.matchRoamingConsortium(element, providerOis, true));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
index c5baac7..9fa92c9 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuServerConnectionTest.java
@@ -81,20 +81,21 @@
  */
 @SmallTest
 public class OsuServerConnectionTest {
-    private static final String TEST_VALID_URL = "http://www.google.com";
+    private static final String TEST_VALID_URL = "https://www.google.com";
+    private static final String TEST_INVALID_URL = "http://www.google.com";
     private static final String AUTH_TYPE = "ECDHE_RSA";
     private static final String PROVIDER_NAME_VALID = "Boingo";
     private static final String PROVIDER_NAME_INVALID = "Boingo1";
+    private static final String TEST_PROVIDER_CHINESE_NAME = "宝音阁";
     private static final int ENABLE_VERBOSE_LOGGING = 1;
     private static final int TEST_SESSION_ID = 1;
 
     private TestLooper mLooper = new TestLooper();
     private OsuServerConnection mOsuServerConnection;
-    private URL mValidServerUrl;
+    private URL mServerUrl;
     private List<Pair<Locale, String>> mProviderIdentities = new ArrayList<>();
     private ArgumentCaptor<TrustManager[]> mTrustManagerCaptor =
             ArgumentCaptor.forClass(TrustManager[].class);
-
     private Map<Integer, Map<String, byte[]>> mTrustCertsInfo = new HashMap<>();
 
     @Mock PasspointProvisioner.OsuServerCallbacks mOsuServerCallbacks;
@@ -114,7 +115,7 @@
         mOsuServerConnection = new OsuServerConnection(mLooper.getLooper());
         mOsuServerConnection.enableVerboseLogging(ENABLE_VERBOSE_LOGGING);
         mProviderIdentities.add(Pair.create(Locale.US, PROVIDER_NAME_VALID));
-        mValidServerUrl = new URL(TEST_VALID_URL);
+        mServerUrl = new URL(TEST_VALID_URL);
         when(mWfaKeyStore.get()).thenReturn(mKeyStore);
         when(mOsuServerCallbacks.getSessionId()).thenReturn(TEST_SESSION_ID);
         when(mNetwork.openConnection(any(URL.class))).thenReturn(mUrlConnection);
@@ -144,7 +145,85 @@
             trustManager.checkServerTrusted(new X509Certificate[1], AUTH_TYPE);
 
             verify(mOsuServerCallbacks).onServerValidationStatus(anyInt(), eq(true));
-            assertTrue(mOsuServerConnection.validateProvider(Locale.US, PROVIDER_NAME_VALID));
+            Map<String, String> providerNames = new HashMap<>();
+            providerNames.put(Locale.US.getISO3Language(), PROVIDER_NAME_VALID);
+            assertTrue(mOsuServerConnection.validateProvider(providerNames));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verifies multiple languages of OsuProvider names are matched with cert
+     */
+    @Test
+    public void verifyValidateProviderWithMultipleProviderLangs() throws Exception {
+        // static mocking
+        MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
+                ServiceProviderVerifier.class).startMocking();
+        try {
+            when(ServiceProviderVerifier.getProviderNames(any(X509Certificate.class))).thenReturn(
+                    mProviderIdentities);
+            establishServerConnection();
+            TrustManager[] trustManagers = mTrustManagerCaptor.getValue();
+            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+            trustManager.checkServerTrusted(new X509Certificate[1], AUTH_TYPE);
+            Map<String, String> friendlyNames = new HashMap<>();
+            friendlyNames.put(
+                    Locale.SIMPLIFIED_CHINESE.getISO3Language(), TEST_PROVIDER_CHINESE_NAME);
+            friendlyNames.put(Locale.US.getISO3Language(), PROVIDER_NAME_VALID);
+
+            assertTrue(mOsuServerConnection.validateProvider(friendlyNames));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verifies wrong language of OsuProvider name is mismatched with cert
+     */
+    @Test
+    public void verifyValidateProviderWithMismatchedProviderLang() throws Exception {
+        // static mocking
+        MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
+                ServiceProviderVerifier.class).startMocking();
+        try {
+            when(ServiceProviderVerifier.getProviderNames(any(X509Certificate.class))).thenReturn(
+                    mProviderIdentities);
+            establishServerConnection();
+            TrustManager[] trustManagers = mTrustManagerCaptor.getValue();
+            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+            trustManager.checkServerTrusted(new X509Certificate[1], AUTH_TYPE);
+            Map<String, String> friendlyNames = new HashMap<>();
+            friendlyNames.put(
+                    Locale.SIMPLIFIED_CHINESE.getISO3Language(), TEST_PROVIDER_CHINESE_NAME);
+
+            assertFalse(mOsuServerConnection.validateProvider(friendlyNames));
+        } finally {
+            session.finishMocking();
+        }
+    }
+
+    /**
+     * Verifies same language from different regions.
+     */
+    @Test
+    public void verifyValidateProviderWithSameLangButDifferentRegion() throws Exception {
+        // static mocking
+        MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(
+                ServiceProviderVerifier.class).startMocking();
+        try {
+            when(ServiceProviderVerifier.getProviderNames(any(X509Certificate.class))).thenReturn(
+                    mProviderIdentities);
+            establishServerConnection();
+            TrustManager[] trustManagers = mTrustManagerCaptor.getValue();
+            X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
+            trustManager.checkServerTrusted(new X509Certificate[1], AUTH_TYPE);
+            Map<String, String> friendlyNames = new HashMap<>();
+            friendlyNames.put(
+                    Locale.CANADA.getISO3Language(), PROVIDER_NAME_VALID);
+
+            assertTrue(mOsuServerConnection.validateProvider(friendlyNames));
         } finally {
             session.finishMocking();
         }
@@ -185,7 +264,7 @@
         mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
 
         assertTrue(mOsuServerConnection.canValidateServer());
-        assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
+        assertTrue(mOsuServerConnection.connect(mServerUrl, mNetwork));
 
         mLooper.dispatchAll();
 
@@ -203,7 +282,7 @@
         mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
 
         assertTrue(mOsuServerConnection.canValidateServer());
-        assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
+        assertTrue(mOsuServerConnection.connect(mServerUrl, mNetwork));
 
         mLooper.dispatchAll();
 
@@ -216,13 +295,16 @@
     @Test
     public void verifyInitAndConnectCertValidationFailure() throws Exception {
         establishServerConnection();
+        List<X509Certificate> certificateList = PasspointProvisioningTestUtil.getOsuCertsForTest();
+        X509Certificate[] certificates = new X509Certificate[1];
+        certificates[0] = certificateList.get(0);
         TrustManager[] trustManagers = mTrustManagerCaptor.getValue();
         X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
         doThrow(new CertificateException()).when(mDelegate)
                 .getTrustedChainForServer(any(X509Certificate[].class), anyString(),
                         (Socket) isNull());
 
-        trustManager.checkServerTrusted(new X509Certificate[1], AUTH_TYPE);
+        trustManager.checkServerTrusted(certificates, AUTH_TYPE);
 
         verify(mOsuServerCallbacks).onServerValidationStatus(anyInt(), eq(false));
     }
@@ -247,7 +329,9 @@
             trustManager.checkServerTrusted(new X509Certificate[1], AUTH_TYPE);
 
             verify(mOsuServerCallbacks).onServerValidationStatus(anyInt(), eq(true));
-            assertFalse(mOsuServerConnection.validateProvider(Locale.US, PROVIDER_NAME_INVALID));
+            Map<String, String> providerNames = new HashMap<>();
+            providerNames.put(Locale.US.getISO3Language(), PROVIDER_NAME_INVALID);
+            assertFalse(mOsuServerConnection.validateProvider(providerNames));
         } finally {
             session.finishMocking();
         }
@@ -475,13 +559,26 @@
         }
     }
 
+    /**
+     * Verifies initialization and opening URL connection failure for an HTTP URL (not HTTPS)
+     */
+    @Test
+    public void verifyInitAndNetworkOpenURLConnectionFailedWithHttpUrl() throws Exception {
+        mServerUrl = new URL(TEST_INVALID_URL);
+        mOsuServerConnection.init(mTlsContext, mDelegate);
+        mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
+
+        assertTrue(mOsuServerConnection.canValidateServer());
+        assertFalse(mOsuServerConnection.connect(mServerUrl, mNetwork));
+    }
+
     private void establishServerConnection() throws Exception {
         mOsuServerConnection.init(mTlsContext, mDelegate);
         mOsuServerConnection.setEventCallback(mOsuServerCallbacks);
         verify(mTlsContext).init(isNull(), mTrustManagerCaptor.capture(), isNull());
 
         assertTrue(mOsuServerConnection.canValidateServer());
-        assertTrue(mOsuServerConnection.connect(mValidServerUrl, mNetwork));
+        assertTrue(mOsuServerConnection.connect(mServerUrl, mNetwork));
         mLooper.dispatchAll();
 
         verify(mOsuServerCallbacks).onServerConnectionStatus(anyInt(), eq(true));
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java
index c76e2c8..7a81500 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigSharedStoreDataTest.java
@@ -25,6 +25,7 @@
 
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.wifi.WifiConfigStore;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,7 +63,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mConfigStoreData.serializeData(out);
+        mConfigStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -77,7 +78,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mConfigStoreData.deserializeData(in, in.getDepth());
+        mConfigStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java
index 82cdb5a..5278e19 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointConfigUserStoreDataTest.java
@@ -32,6 +32,7 @@
 import com.android.server.wifi.SIMAccessor;
 import com.android.server.wifi.WifiConfigStore;
 import com.android.server.wifi.WifiKeyStore;
+import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -213,7 +214,7 @@
         final XmlSerializer out = new FastXmlSerializer();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
-        mConfigStoreData.serializeData(out);
+        mConfigStoreData.serializeData(out, mock(WifiConfigStoreEncryptionUtil.class));
         out.flush();
         return outputStream.toByteArray();
     }
@@ -228,7 +229,9 @@
         final XmlPullParser in = Xml.newPullParser();
         final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
-        mConfigStoreData.deserializeData(in, in.getDepth());
+        mConfigStoreData.deserializeData(in, in.getDepth(),
+                WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
+                mock(WifiConfigStoreEncryptionUtil.class));
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
index e4b1622..c7d6604 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -93,7 +93,6 @@
 import com.android.server.wifi.hotspot2.anqp.eap.EAPMethod;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.InformationElementUtil.RoamingConsortium;
-import com.android.server.wifi.util.WifiPermissionsUtil;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -177,7 +176,6 @@
     @Mock TelephonyManager mTelephonyManager;
     @Mock TelephonyManager mDataTelephonyManager;
     @Mock SubscriptionManager mSubscriptionManager;
-    @Mock WifiPermissionsUtil mWifiPermissionsUtil;
 
     Handler mHandler;
     TestLooper mLooper;
@@ -204,13 +202,11 @@
                 .thenReturn(mPasspointProvisioner);
         when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
         when(mWifiInjector.getClientModeImpl()).thenReturn(mClientModeImpl);
-        when(mWifiPermissionsUtil.doesUidBelongToCurrentUser(anyInt())).thenReturn(true);
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
         mManager = new PasspointManager(mContext, mWifiInjector, mHandler, mWifiNative,
                 mWifiKeyStore, mClock, mSimAccessor, mObjectFactory, mWifiConfigManager,
-                mWifiConfigStore, mWifiMetrics, mTelephonyManager, mSubscriptionManager,
-                mWifiPermissionsUtil);
+                mWifiConfigStore, mWifiMetrics, mTelephonyManager, mSubscriptionManager);
         ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
                 ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
         verify(mObjectFactory).makePasspointEventHandler(any(WifiNative.class),
@@ -236,7 +232,8 @@
      * @param expectedConfig The expected installed Passpoint configuration
      */
     private void verifyInstalledConfig(PasspointConfiguration expectedConfig) {
-        List<PasspointConfiguration> installedConfigs = mManager.getProviderConfigs();
+        List<PasspointConfiguration> installedConfigs =
+                mManager.getProviderConfigs(TEST_CREATOR_UID, true);
         assertEquals(1, installedConfigs.size());
         assertEquals(expectedConfig, installedConfigs.get(0));
     }
@@ -251,6 +248,7 @@
         PasspointProvider provider = mock(PasspointProvider.class);
         when(provider.installCertsAndKeys()).thenReturn(true);
         lenient().when(provider.getConfig()).thenReturn(config);
+        lenient().when(provider.getCreatorUid()).thenReturn(TEST_CREATOR_UID);
         return provider;
     }
 
@@ -541,14 +539,14 @@
         // Provider index start with 0, should be 1 after adding a provider.
         assertEquals(1, mSharedDataSource.getProviderIndex());
 
-        // Remove the provider.
-        assertTrue(mManager.removeProvider(TEST_CREATOR_UID, TEST_FQDN));
+        // Remove the provider as the creator app.
+        assertTrue(mManager.removeProvider(TEST_CREATOR_UID, false, TEST_FQDN));
         verify(provider).uninstallCertsAndKeys();
         verify(mWifiConfigManager).saveToStore(true);
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess();
         verify(mAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
-        assertTrue(mManager.getProviderConfigs().isEmpty());
+        assertTrue(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty());
 
         // Verify content in the data source.
         assertTrue(mUserDataSource.getProviders().isEmpty());
@@ -584,13 +582,13 @@
         // Provider index start with 0, should be 1 after adding a provider.
         assertEquals(1, mSharedDataSource.getProviderIndex());
 
-        // Remove the provider.
-        assertTrue(mManager.removeProvider(TEST_CREATOR_UID, TEST_FQDN));
+        // Remove the provider as a privileged non-creator app.
+        assertTrue(mManager.removeProvider(TEST_UID, true, TEST_FQDN));
         verify(provider).uninstallCertsAndKeys();
         verify(mWifiConfigManager).saveToStore(true);
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallSuccess();
-        assertTrue(mManager.getProviderConfigs().isEmpty());
+        assertTrue(mManager.getProviderConfigs(TEST_UID, true).isEmpty());
 
         // Verify content in the data source.
         assertTrue(mUserDataSource.getProviders().isEmpty());
@@ -714,7 +712,7 @@
      */
     @Test
     public void removeNonExistingProvider() throws Exception {
-        assertFalse(mManager.removeProvider(TEST_CREATOR_UID, TEST_FQDN));
+        assertFalse(mManager.removeProvider(TEST_CREATOR_UID, true, TEST_FQDN));
         verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
         verify(mWifiMetrics, never()).incrementNumPasspointProviderUninstallSuccess();
     }
@@ -1175,8 +1173,8 @@
         mUserDataSource.setProviders(providers);
 
         // Verify the providers maintained by PasspointManager.
-        assertEquals(1, mManager.getProviderConfigs().size());
-        assertEquals(config, mManager.getProviderConfigs().get(0));
+        assertEquals(1, mManager.getProviderConfigs(TEST_CREATOR_UID, true).size());
+        assertEquals(config, mManager.getProviderConfigs(TEST_CREATOR_UID, true).get(0));
     }
 
     /**
@@ -1521,7 +1519,7 @@
         PasspointManager passpointManager = new PasspointManager(mContext, mWifiInjector,
                 mHandler, mWifiNative, mWifiKeyStore, mClock, mSimAccessor, mObjectFactory,
                 mWifiConfigManager, mWifiConfigStore, mWifiMetrics, mTelephonyManager,
-                mSubscriptionManager, mWifiPermissionsUtil);
+                mSubscriptionManager);
 
         assertNull(passpointManager.createEphemeralPasspointConfigForCarrier(
                 EAPConstants.EAP_TLS));
@@ -1539,7 +1537,7 @@
         PasspointManager passpointManager = new PasspointManager(mContext, mWifiInjector,
                 mHandler, mWifiNative, mWifiKeyStore, mClock, mSimAccessor, mObjectFactory,
                 mWifiConfigManager, mWifiConfigStore, mWifiMetrics, mTelephonyManager,
-                mSubscriptionManager, mWifiPermissionsUtil);
+                mSubscriptionManager);
 
         PasspointConfiguration result =
                 passpointManager.createEphemeralPasspointConfigForCarrier(
@@ -1640,7 +1638,7 @@
             PasspointManager passpointManager = new PasspointManager(mContext, mWifiInjector,
                     mHandler, mWifiNative, mWifiKeyStore, mClock, mSimAccessor, mObjectFactory,
                     mWifiConfigManager, mWifiConfigStore, mWifiMetrics, mTelephonyManager,
-                    mSubscriptionManager, mWifiPermissionsUtil);
+                    mSubscriptionManager);
             assertEquals(EAPConstants.EAP_AKA,
                     passpointManager.findEapMethodFromNAIRealmMatchedWithCarrier(scanDetails));
         } finally {
@@ -1669,7 +1667,7 @@
             PasspointManager passpointManager = new PasspointManager(mContext, mWifiInjector,
                     mHandler, mWifiNative, mWifiKeyStore, mClock, mSimAccessor, mObjectFactory,
                     mWifiConfigManager, mWifiConfigStore, mWifiMetrics, mTelephonyManager,
-                    mSubscriptionManager, mWifiPermissionsUtil);
+                    mSubscriptionManager);
 
             assertEquals(-1,
                     passpointManager.findEapMethodFromNAIRealmMatchedWithCarrier(scanDetails));
@@ -1690,7 +1688,7 @@
 
         verify(mAppOpsManager).startWatchingMode(eq(OPSTR_CHANGE_WIFI_STATE), eq(TEST_PACKAGE),
                 mAppOpChangedListenerCaptor.capture());
-        assertEquals(1, mManager.getProviderConfigs().size());
+        assertEquals(1, mManager.getProviderConfigs(TEST_CREATOR_UID, true).size());
         AppOpsManager.OnOpChangedListener listener = mAppOpChangedListenerCaptor.getValue();
         assertNotNull(listener);
 
@@ -1704,6 +1702,45 @@
 
         verify(mAppOpsManager).stopWatchingMode(mAppOpChangedListenerCaptor.getValue());
         verify(mClientModeImpl).disconnectCommand();
-        assertTrue(mManager.getProviderConfigs().isEmpty());
+        assertTrue(mManager.getProviderConfigs(TEST_CREATOR_UID, true).isEmpty());
+    }
+
+    /**
+     * Verify that removing a provider with a different UID will not succeed.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void removeGetProviderWithDifferentUid() throws Exception {
+        PasspointConfiguration config = createTestConfigWithSimCredential(TEST_FQDN, TEST_IMSI,
+                TEST_REALM);
+        PasspointProvider provider = createMockProvider(config);
+        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
+                eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID), eq(TEST_PACKAGE))).thenReturn(
+                provider);
+        assertTrue(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID, TEST_PACKAGE));
+        verifyInstalledConfig(config);
+        verify(mWifiConfigManager).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallation();
+        verify(mWifiMetrics).incrementNumPasspointProviderInstallSuccess();
+        reset(mWifiMetrics);
+        reset(mWifiConfigManager);
+
+        // no profiles available for TEST_UID
+        assertTrue(mManager.getProviderConfigs(TEST_UID, false).isEmpty());
+        // 1 profile available for TEST_CREATOR_UID
+        assertFalse(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty());
+
+        // Remove the provider as a non-privileged non-creator app.
+        assertFalse(mManager.removeProvider(TEST_UID, false, TEST_FQDN));
+        verify(provider, never()).uninstallCertsAndKeys();
+        verify(mWifiConfigManager, never()).saveToStore(true);
+        verify(mWifiMetrics).incrementNumPasspointProviderUninstallation();
+        verify(mWifiMetrics, never()).incrementNumPasspointProviderUninstallSuccess();
+
+        // no profiles available for TEST_UID
+        assertTrue(mManager.getProviderConfigs(TEST_UID, false).isEmpty());
+        // 1 profile available for TEST_CREATOR_UID
+        assertFalse(mManager.getProviderConfigs(TEST_CREATOR_UID, false).isEmpty());
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
index 31229c5..c35d673 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProviderTest.java
@@ -478,9 +478,10 @@
     }
 
     /**
-     * Verify that there is no match when the provider's FQDN matches a domain name in the
-     * Domain Name ANQP element but the provider's credential doesn't match the authentication
-     * method provided in the NAI realm.
+     * Verify that Home provider is matched even when the provider's FQDN matches a domain name in
+     * the Domain Name ANQP element but the provider's credential doesn't match the authentication
+     * method provided in the NAI realm. This can happen when the infrastructure provider is not
+     * the identity provider, and authentication method matching is not required in the spec.
      *
      * @throws Exception
      */
@@ -509,7 +510,8 @@
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(testRealm, EAPConstants.EAP_TLS, null));
 
-        assertEquals(PasspointMatch.None, mProvider.match(anqpElementMap, mRoamingConsortium));
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium));
     }
 
     /**
@@ -657,8 +659,8 @@
     }
 
     /**
-     * Verify that there is no match when a roaming consortium OI matches an OI
-     * in the roaming consortium ANQP element and but NAI realm is not matched.
+     * Verify that there is Roaming provider match when a roaming consortium OI matches an OI
+     * in the roaming consortium ANQP element and regardless of NAI realm mismatch.
      *
      * @throws Exception
      */
@@ -689,7 +691,7 @@
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(testRealm, EAPConstants.EAP_TLS, null));
 
-        assertEquals(PasspointMatch.None,
+        assertEquals(PasspointMatch.RoamingProvider,
                 mProvider.match(anqpElementMap, mRoamingConsortium));
     }
 
@@ -766,8 +768,14 @@
     }
 
     /**
-     * Verify that there is no match when a roaming consortium OI matches an OI
+     * Verify that there is Roaming provider match when a roaming consortium OI matches an OI
      * in the roaming consortium information element, but NAI realm is not matched.
+     * This can happen in roaming federation where the infrastructure provider is not the
+     * identity provider.
+     * Page 133 in the Hotspot2.0 specification states:
+     * Per subclause 11.25.8 of [2], if the value of HomeOI matches an OI in the Roaming
+     * Consortium advertised by a hotspot operator, successful authentication with that hotspot
+     * is possible.
      *
      * @throws Exception
      */
@@ -799,7 +807,7 @@
         anqpElementMap.put(ANQPElementType.ANQPNAIRealm,
                 createNAIRealmElement(testRealm, EAPConstants.EAP_TLS, null));
 
-        assertEquals(PasspointMatch.None,
+        assertEquals(PasspointMatch.RoamingProvider,
                 mProvider.match(anqpElementMap, mRoamingConsortium));
     }
 
@@ -1354,4 +1362,167 @@
         mProvider.setHasEverConnected(true);
         assertTrue(mProvider.getHasEverConnected());
     }
+
+    /**
+     * Verify that an expected WifiConfiguration will be returned for a Passpoint provider
+     * with a user credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchOtherPartnersDomainName() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1.com");
+        homeSp.setOtherHomePartners(new String [] {"test3.com"});
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+
+        // Setup Domain Name ANQP element to test2.com and test3.com
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {"test2.com", "test3.com"}));
+
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium));
+    }
+
+    /**
+     * Verify that matching Any HomeOI results in a Home Provider match
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAnyHomeOi() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1.com");
+        homeSp.setMatchAnyOis(new long[] {0x1234L, 0x2345L});
+        homeSp.setRoamingConsortiumOis(null);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+        Long[] anqpOis = new Long[] {0x1234L, 0xdeadL, 0xf0cdL};
+
+        // Setup Domain Name ANQP element to test2.com and test3.com
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {"test2.com", "test3.com"}));
+        // Setup RCOIs advertised by the AP
+        anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
+                createRoamingConsortiumElement(anqpOis));
+
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium));
+    }
+
+    /**
+     * Verify that non-matching Any HomeOI results in a None Provider match
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAnyHomeOiNegative() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1.com");
+        homeSp.setMatchAnyOis(new long[] {0x1234L, 0x2345L});
+        homeSp.setRoamingConsortiumOis(null);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+        Long[] anqpOis = new Long[] {0x12a4L, 0xceadL, 0xf0cdL};
+
+        // Setup Domain Name ANQP element to test2.com and test3.com
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {"test2.com", "test3.com"}));
+        // Setup RCOIs advertised by the AP
+        anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
+                createRoamingConsortiumElement(anqpOis));
+
+        assertEquals(PasspointMatch.None,
+                mProvider.match(anqpElementMap, mRoamingConsortium));
+    }
+
+    /**
+     * Verify that matching All HomeOI results in a Home Provider match
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAllHomeOi() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1.com");
+        homeSp.setMatchAllOis(new long[] {0x1234L, 0x2345L});
+        homeSp.setRoamingConsortiumOis(null);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+        Long[] anqpOis = new Long[] {0x1234L, 0x2345L, 0xabcdL, 0xdeadL, 0xf0cdL};
+
+        // Setup Domain Name ANQP element to test2.com and test3.com
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {"test2.com", "test3.com"}));
+        // Setup RCOIs advertised by the AP
+        anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
+                createRoamingConsortiumElement(anqpOis));
+
+        assertEquals(PasspointMatch.HomeProvider,
+                mProvider.match(anqpElementMap, mRoamingConsortium));
+    }
+
+    /**
+     * Verify that non-matching All HomeOI results in a None Provider match
+     *
+     * @throws Exception
+     */
+    @Test
+    public void matchAllHomeOiNegative() throws Exception {
+        // Setup test provider.
+        PasspointConfiguration config = new PasspointConfiguration();
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("test1.com");
+        homeSp.setMatchAllOis(new long[] {0x1234L, 0x2345L});
+        homeSp.setRoamingConsortiumOis(null);
+        config.setHomeSp(homeSp);
+        Credential credential = new Credential();
+        credential.setUserCredential(new Credential.UserCredential());
+        config.setCredential(credential);
+        mProvider = createProvider(config);
+        verifyInstalledConfig(config, true);
+
+        // 0x1234 matches, but 0x2345 does not
+        Long[] anqpOis = new Long[] {0x1234L, 0x5678L, 0xdeadL, 0xf0cdL};
+
+        // Setup Domain Name ANQP element to test2.com and test3.com
+        Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>();
+        anqpElementMap.put(ANQPElementType.ANQPDomName,
+                createDomainNameElement(new String[] {"test2.com", "test3.com"}));
+        // Setup RCOIs advertised by the AP
+        anqpElementMap.put(ANQPElementType.ANQPRoamingConsortium,
+                createRoamingConsortiumElement(anqpOis));
+
+        assertEquals(PasspointMatch.None,
+                mProvider.match(anqpElementMap, mRoamingConsortium));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
index 10ce650..64a7f9e 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
@@ -97,7 +97,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 
 import javax.net.ssl.SSLContext;
@@ -224,8 +223,8 @@
                 mPasspointManager, mWifiMetrics);
         when(mOsuNetworkConnection.connect(any(WifiSsid.class), any(), any())).thenReturn(true);
         when(mOsuServerConnection.connect(any(URL.class), any(Network.class))).thenReturn(true);
-        when(mOsuServerConnection.validateProvider(any(Locale.class),
-                any(String.class))).thenReturn(true);
+        when(mOsuServerConnection.validateProvider(
+                anyMap())).thenReturn(true);
         when(mOsuServerConnection.canValidateServer()).thenReturn(true);
         mPasspointProvisioner.enableVerboseLogging(1);
         mOsuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
@@ -728,8 +727,8 @@
      */
     @Test
     public void verifyProviderVerificationFailure() throws RemoteException {
-        when(mOsuServerConnection.validateProvider(any(Locale.class),
-                any(String.class))).thenReturn(false);
+        when(mOsuServerConnection.validateProvider(
+                anyMap())).thenReturn(false);
         stopAfterStep(STEP_SERVER_CONNECT);
 
         // Wait for OSU server validation callback
diff --git a/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
index 15d9ef9..1091c1d 100644
--- a/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/p2p/WifiP2pServiceImplTest.java
@@ -3812,4 +3812,28 @@
         mLooper.dispatchAll();
         verify(mWifiNative).p2pStopFind();
     }
+
+    /**
+     * Verify a network name which is too long is rejected.
+     */
+    @Test
+    public void testSendConnectMsgWithTooLongNetworkName() throws Exception {
+        mTestWifiP2pFastConnectionConfig.networkName = "DIRECT-xy-abcdefghijklmnopqrstuvw";
+        sendConnectMsg(mClientMessenger, mTestWifiP2pFastConnectionConfig);
+        verify(mClientHandler).sendMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(WifiP2pManager.CONNECT_FAILED, message.what);
+    }
+
+    /**
+     * Verify a network name which is too short is rejected.
+     */
+    @Test
+    public void testSendConnectMsgWithTooShortNetworkName() throws Exception {
+        mTestWifiP2pFastConnectionConfig.networkName = "DIRECT-x";
+        sendConnectMsg(mClientMessenger, mTestWifiP2pFastConnectionConfig);
+        verify(mClientHandler).sendMessage(mMessageCaptor.capture());
+        Message message = mMessageCaptor.getValue();
+        assertEquals(WifiP2pManager.CONNECT_FAILED, message.what);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
index d2f22da..bd0ad32 100644
--- a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
@@ -18,6 +18,7 @@
 package com.android.server.wifi.rtt;
 
 import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -42,6 +43,7 @@
 import android.hardware.wifi.V1_0.WifiStatusCode;
 import android.net.MacAddress;
 import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.ResponderConfig;
 
 import androidx.test.filters.SmallTest;
 
@@ -465,6 +467,28 @@
         }
     }
 
+    /**
+     * Validation ranging with invalid bw and preamble combination will be ignored.
+     */
+    @Test
+    public void testRangingWithInvalidParameterCombination() throws Exception {
+        int cmdId = 88;
+        RangingRequest request = new RangingRequest.Builder().build();
+        ResponderConfig invalidConfig = new ResponderConfig(
+                MacAddress.fromString("08:09:08:07:06:88"), ResponderConfig.RESPONDER_AP, true,
+                ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_HT);
+        ResponderConfig config = new ResponderConfig(MacAddress.fromString("08:09:08:07:06:89"),
+                ResponderConfig.RESPONDER_AP, true,
+                ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_VHT);
+
+        // Add a ResponderConfig with invalid parameter, should be ignored.
+        request.mRttPeers.add(invalidConfig);
+        request.mRttPeers.add(config);
+        mDut.rangeRequest(cmdId, request, true);
+        verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
+        assertEquals(request.mRttPeers.size() - 1, mRttConfigCaptor.getValue().size());
+    }
+
     // Utilities
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/util/DataIntegrityCheckerTest.java b/tests/wifitests/src/com/android/server/wifi/util/DataIntegrityCheckerTest.java
deleted file mode 100644
index b707698..0000000
--- a/tests/wifitests/src/com/android/server/wifi/util/DataIntegrityCheckerTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi.util;
-
-import static org.junit.Assert.*;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import java.io.File;
-import java.security.DigestException;
-
-/**
- * Unit tests for {@link com.android.server.wifi.util.DataIntegrityChecker}.
- */
-public class DataIntegrityCheckerTest {
-    private static byte[] sGoodData = {1, 2, 3, 4};
-    private static byte[] sBadData = {5, 6, 7, 8};
-
-    /**
-     * Verify that updating the integrity token with known data and alias will
-     * pass the integrity test. This test ensure the expected outcome for
-     * unedited data succeeds.
-     *
-     * @throws Exception
-     */
-    @Test
-    @Ignore
-    public void testIntegrityWithKnownDataAndKnownAlias() throws Exception {
-        File integrityFile = File.createTempFile("testIntegrityWithKnownDataAndKnownAlias",
-                ".tmp");
-        DataIntegrityChecker dataIntegrityChecker = new DataIntegrityChecker(
-                integrityFile.getParent());
-        dataIntegrityChecker.update(sGoodData);
-        assertTrue(dataIntegrityChecker.isOk(sGoodData));
-    }
-
-    /**
-     * Verify that checking the integrity of unknown data and a known alias
-     * will fail the integrity test. This test ensure the expected failure for
-     * altered data, in fact, fails.
-     *
-     *
-     * @throws Exception
-     */
-    @Test
-    @Ignore
-    public void testIntegrityWithUnknownDataAndKnownAlias() throws Exception {
-        File integrityFile = File.createTempFile("testIntegrityWithUnknownDataAndKnownAlias",
-                ".tmp");
-        DataIntegrityChecker dataIntegrityChecker = new DataIntegrityChecker(
-                integrityFile.getParent());
-        dataIntegrityChecker.update(sGoodData);
-        assertFalse(dataIntegrityChecker.isOk(sBadData));
-    }
-
-    /**
-     * Verify a corner case where integrity of data that has never been
-     * updated passes and adds the token to the keystore.
-     *
-     * @throws Exception
-     */
-    @Test(expected = DigestException.class)
-    @Ignore
-    public void testIntegrityWithoutUpdate() throws Exception {
-        File tmpFile = File.createTempFile("testIntegrityWithoutUpdate", ".tmp");
-
-        DataIntegrityChecker dataIntegrityChecker = new DataIntegrityChecker(
-                tmpFile.getAbsolutePath());
-
-        // the integrity data is not known, so isOk throws a DigestException
-        assertTrue(dataIntegrityChecker.isOk(sGoodData));
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
index 45adffd..266a2ce 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/ScanResultUtilTest.java
@@ -116,6 +116,82 @@
         assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
     }
 
+    /**
+     * Test that a PSK-SHA256+SAE network is detected as transition mode
+     */
+    @Test
+    public void testPskSha256SaeTransitionModeCheck() {
+        final String ssid = "WPA3-Transition";
+        String caps = "[WPA2-FT/PSK-CCMP][RSN-FT/PSK+PSK-SHA256+SAE+FT/SAE-CCMP][ESS][WPS]";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+
+        input.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        assertTrue(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input));
+    }
+
+    /**
+     * Test that a PSK+SAE network is detected as transition mode
+     */
+    @Test
+    public void testPskSaeTransitionModeCheck() {
+        final String ssid = "WPA3-Transition";
+        String caps = "[WPA2-FT/PSK+PSK+SAE+FT/SAE-CCMP][ESS][WPS]";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+
+        input.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        assertTrue(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input));
+    }
+
+    /**
+     * Test that a PSK network is not detected as transition mode
+     */
+    @Test
+    public void testPskNotInTransitionModeCheck() {
+        final String ssid = "WPA2-Network";
+        String caps = "[WPA2-FT/PSK+PSK][ESS][WPS]";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+
+        input.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        assertFalse(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input));
+    }
+
+    /**
+     * Test that an SAE network is not detected as transition mode
+     */
+    @Test
+    public void testSaeNotInTransitionModeCheck() {
+        final String ssid = "WPA3-Network";
+        String caps = "[WPA2-FT/SAE+SAE][ESS][WPS]";
+
+        ScanResult input = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid), ssid,
+                "ab:cd:01:ef:45:89", 1245, 0, caps, -78, 2450, 1025, 22, 33, 20, 0,
+                0, true);
+
+        input.informationElements = new InformationElement[] {
+                createIE(InformationElement.EID_SSID, ssid.getBytes(StandardCharsets.UTF_8))
+        };
+
+        assertFalse(ScanResultUtil.isScanResultForPskSaeTransitionNetwork(input));
+    }
+
     private static InformationElement createIE(int id, byte[] bytes) {
         InformationElement ie = new InformationElement();
         ie.id = id;
diff --git a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
index 8baacf6..0c9ed26 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
@@ -1148,7 +1148,7 @@
     }
 
     private void setupMocks() throws Exception {
-        when(mMockPkgMgr.getApplicationInfo(TEST_PACKAGE_NAME, 0))
+        when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PACKAGE_NAME), eq(0), anyInt()))
             .thenReturn(mMockApplInfo);
         when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
         when(mMockAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, mUid, TEST_PACKAGE_NAME))
diff --git a/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
index 85b4a93..8f96bc1 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/XmlUtilTest.java
@@ -35,7 +35,9 @@
 import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
 import com.android.server.wifi.util.XmlUtil.WifiEnterpriseConfigXmlUtil;
 
+import org.junit.Before;
 import org.junit.Test;
+import org.mockito.MockitoAnnotations;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -73,6 +75,13 @@
     private static final int TEST_PHASE2_METHOD = WifiEnterpriseConfig.Phase2.MSCHAPV2;
     private final String mXmlDocHeader = "XmlUtilTest";
 
+    private WifiConfigStoreEncryptionUtil mWifiConfigStoreEncryptionUtil = null;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
     /**
      * Verify that a open WifiConfiguration is serialized & deserialized correctly.
      */
@@ -101,6 +110,22 @@
     }
 
     /**
+     * Verify that a psk WifiConfiguration is serialized & deserialized correctly.
+     */
+    @Test
+    public void testPskWifiConfigurationSerializeDeserializeWithEncryption()
+            throws IOException, XmlPullParserException {
+        mWifiConfigStoreEncryptionUtil = mock(WifiConfigStoreEncryptionUtil.class);
+        WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        EncryptedData encryptedData = new EncryptedData(new byte[0], new byte[0]);
+        when(mWifiConfigStoreEncryptionUtil.encrypt(pskNetwork.preSharedKey.getBytes()))
+                .thenReturn(encryptedData);
+        when(mWifiConfigStoreEncryptionUtil.decrypt(encryptedData))
+                .thenReturn(pskNetwork.preSharedKey.getBytes());
+        serializeDeserializeWifiConfiguration(pskNetwork);
+    }
+
+    /**
      * Verify that a psk hidden WifiConfiguration is serialized & deserialized correctly.
      */
     @Test
@@ -382,6 +407,37 @@
     }
 
     /**
+     * Verify that a WifiEnterpriseConfig object is serialized & deserialized correctly.
+     */
+    @Test
+    public void testWifiEnterpriseConfigSerializeDeserializeWithEncryption()
+            throws IOException, XmlPullParserException {
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        config.setFieldValue(WifiEnterpriseConfig.IDENTITY_KEY, TEST_IDENTITY);
+        config.setFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY, TEST_ANON_IDENTITY);
+        config.setFieldValue(WifiEnterpriseConfig.PASSWORD_KEY, TEST_PASSWORD);
+        config.setFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY, TEST_CLIENT_CERT);
+        config.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, TEST_CA_CERT);
+        config.setFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY, TEST_SUBJECT_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.ENGINE_KEY, TEST_ENGINE);
+        config.setFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY, TEST_ENGINE_ID);
+        config.setFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, TEST_PRIVATE_KEY_ID);
+        config.setFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, TEST_ALTSUBJECT_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, TEST_DOM_SUFFIX_MATCH);
+        config.setFieldValue(WifiEnterpriseConfig.CA_PATH_KEY, TEST_CA_PATH);
+        config.setEapMethod(TEST_EAP_METHOD);
+        config.setPhase2Method(TEST_PHASE2_METHOD);
+
+        mWifiConfigStoreEncryptionUtil = mock(WifiConfigStoreEncryptionUtil.class);
+        EncryptedData encryptedData = new EncryptedData(new byte[0], new byte[0]);
+        when(mWifiConfigStoreEncryptionUtil.encrypt(TEST_PASSWORD.getBytes()))
+                .thenReturn(encryptedData);
+        when(mWifiConfigStoreEncryptionUtil.decrypt(encryptedData))
+                .thenReturn(TEST_PASSWORD.getBytes());
+        serializeDeserializeWifiEnterpriseConfig(config);
+    }
+
+    /**
      * Verify that an illegal argument exception is thrown when trying to parse out a corrupted
      * WifiEnterpriseConfig.
      *
@@ -473,7 +529,8 @@
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
         XmlUtil.writeDocumentStart(out, mXmlDocHeader);
-        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, configuration);
+        WifiConfigurationXmlUtil.writeToXmlForConfigStore(
+                out, configuration, mWifiConfigStoreEncryptionUtil);
         XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
         return outputStream.toByteArray();
     }
@@ -485,7 +542,10 @@
         ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
         XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
-        return WifiConfigurationXmlUtil.parseFromXml(in, in.getDepth());
+        return WifiConfigurationXmlUtil.parseFromXml(
+                in, in.getDepth(),
+                mWifiConfigStoreEncryptionUtil != null,
+                mWifiConfigStoreEncryptionUtil);
     }
 
     /**
@@ -593,7 +653,8 @@
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         out.setOutput(outputStream, StandardCharsets.UTF_8.name());
         XmlUtil.writeDocumentStart(out, mXmlDocHeader);
-        WifiEnterpriseConfigXmlUtil.writeToXml(out, config);
+        WifiEnterpriseConfigXmlUtil.writeToXml(
+                out, config, mWifiConfigStoreEncryptionUtil);
         XmlUtil.writeDocumentEnd(out, mXmlDocHeader);
         return outputStream.toByteArray();
     }
@@ -604,7 +665,9 @@
         ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
         in.setInput(inputStream, StandardCharsets.UTF_8.name());
         XmlUtil.gotoDocumentStart(in, mXmlDocHeader);
-        return WifiEnterpriseConfigXmlUtil.parseFromXml(in, in.getDepth());
+        return WifiEnterpriseConfigXmlUtil.parseFromXml(
+                in, in.getDepth(), mWifiConfigStoreEncryptionUtil != null,
+                mWifiConfigStoreEncryptionUtil);
     }
 
     private void serializeDeserializeWifiEnterpriseConfig(WifiEnterpriseConfig config)