Merge "[WifiTrackerLib] Ignore broadcast passpoint wificonfig in Saved Networks" into rvc-dev
diff --git a/service/Android.bp b/service/Android.bp
index 75dff32..d7c16df 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -62,7 +62,7 @@
         // need pre-jarjar symbols so that wifi-service can reference the original class names at
         // compile time
         "framework-wifi-pre-jarjar",
-        "framework-statsd-stubs-module_libs_api",
+        "framework-statsd.stubs.module_lib",
         "unsupportedappusage",
     ],
 
@@ -105,7 +105,7 @@
 
     // need to include `libs` so that Soong doesn't complain about missing classes after jarjaring
     libs: [
-        "framework-wifi",
+        "framework-wifi.impl",
     ],
 
     sdk_version: "system_server_current",
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index e36d379..5b616dd 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -368,7 +368,7 @@
     private String mTargetBssid = SUPPLICANT_BSSID_ANY;
     // This one is used to track the current target network ID. This is used for error
     // handling during connection setup since many error message from supplicant does not report
-    // SSID Once connected, it will be set to invalid
+    // SSID. Once connected, it will be set to invalid
     private int mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
     private long mLastDriverRoamAttempt = 0;
     private WifiConfiguration mTargetWifiConfiguration = null;
@@ -1211,8 +1211,8 @@
     private void connectToUserSelectNetwork(int netId, int uid, boolean forceReconnect) {
         logd("connectToUserSelectNetwork netId " + netId + ", uid " + uid
                 + ", forceReconnect = " + forceReconnect);
-        if (!forceReconnect && mWifiInfo.getNetworkId() == netId) {
-            // We're already connected to the user specified network, don't trigger a
+        if (!forceReconnect && (mLastNetworkId == netId || mTargetNetworkId == netId)) {
+            // We're already connecting/connected to the user specified network, don't trigger a
             // reconnection unless it was forced.
             logi("connectToUserSelectNetwork already connecting/connected=" + netId);
         } else {
@@ -2965,6 +2965,20 @@
             }
         }
 
+        if (configuration != null
+                && configuration.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
+            if (level2FailureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
+                mWifiMetrics.incrementNumOfCarrierWifiConnectionSuccess();
+            } else if (level2FailureCode
+                            == WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE
+                    && level2FailureReason
+                            != WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE) {
+                mWifiMetrics.incrementNumOfCarrierWifiConnectionAuthFailure();
+            } else {
+                mWifiMetrics.incrementNumOfCarrierWifiConnectionNonAuthFailure();
+            }
+        }
+
         boolean isAssociationRejection = level2FailureCode
                 == WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION;
         boolean isAuthenticationFailure = level2FailureCode
@@ -2983,6 +2997,10 @@
             mNetworkFactory.handleConnectionAttemptEnded(level2FailureCode, configuration);
             mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
                     level2FailureCode, configuration, getCurrentBSSID());
+            ScanResult candidate = configuration.getNetworkSelectionStatus().getCandidate();
+            if (candidate != null && !TextUtils.equals(candidate.BSSID, getCurrentBSSID())) {
+                mWifiMetrics.incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
+            }
         }
         handleConnectionAttemptEndForDiagnostics(level2FailureCode);
     }
@@ -4146,7 +4164,13 @@
                                 // We switched from DHCP to static or from static to DHCP, or the
                                 // static IP address has changed.
                                 log("Reconfiguring IP on connection");
-                                transitionTo(mObtainingIpState);
+                                WifiConfiguration currentConfig = getCurrentWifiConfiguration();
+                                if (currentConfig != null) {
+                                    transitionTo(mObtainingIpState);
+                                } else {
+                                    Log.w(TAG, "CMD_SAVE_NETWORK Ip change - but no current "
+                                            + "Wi-Fi config");
+                                }
                             }
                         }
                     } else if (mWifiInfo.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID
diff --git a/service/java/com/android/server/wifi/ClientModeManager.java b/service/java/com/android/server/wifi/ClientModeManager.java
index 66495ae..61df519 100644
--- a/service/java/com/android/server/wifi/ClientModeManager.java
+++ b/service/java/com/android/server/wifi/ClientModeManager.java
@@ -19,6 +19,11 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -28,6 +33,7 @@
 import android.os.UserHandle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsException;
@@ -45,9 +51,11 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.wifi.WifiNative.InterfaceCallback;
 import com.android.server.wifi.util.WifiHandler;
+import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /**
  * Manager WiFi in Client Mode where we connect to configured networks.
@@ -71,6 +79,7 @@
     private @Role int mRole = ROLE_UNSPECIFIED;
     private DeferStopHandler mDeferStopHandler;
     private int mTargetRole = ROLE_UNSPECIFIED;
+    private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     ClientModeManager(Context context, @NonNull Looper looper, Clock clock, WifiNative wifiNative,
             Listener listener, WifiMetrics wifiMetrics, SarManager sarManager,
@@ -120,6 +129,8 @@
         private final Runnable mRunnable = () -> continueToStopWifi();
         private int mMaximumDeferringTimeMillis = 0;
         private long mDeferringStartTimeMillis = 0;
+        private NetworkRequest mImsRequest = null;
+        private ConnectivityManager mConnectivityManager = null;
 
         private RegistrationManager.RegistrationCallback mImsRegistrationCallback =
                 new RegistrationManager.RegistrationCallback() {
@@ -136,10 +147,40 @@
                     @Override
                     public void onUnregistered(ImsReasonInfo imsReasonInfo) {
                         Log.d(TAG, "on IMS unregistered");
-                        if (mIsDeferring) continueToStopWifi();
+                        // Wait for onLost in NetworkCallback
                     }
                 };
 
+        private NetworkCallback mImsNetworkCallback = new NetworkCallback() {
+            private int mRegisteredImsNetworkCount = 0;
+            @Override
+            public void onAvailable(Network network) {
+                synchronized (this) {
+                    Log.d(TAG, "IMS network available: " + network);
+                    mRegisteredImsNetworkCount++;
+                }
+            }
+
+            @Override
+            public void onLost(Network network) {
+                synchronized (this) {
+                    Log.d(TAG, "IMS network lost: " + network
+                            + " ,isDeferring: " + mIsDeferring
+                            + " ,registered IMS network count: " + mRegisteredImsNetworkCount);
+                    mRegisteredImsNetworkCount--;
+                    if (mIsDeferring && mRegisteredImsNetworkCount <= 0) {
+                        mRegisteredImsNetworkCount = 0;
+                        // Add delay for targets where IMS PDN down at modem takes additional delay.
+                        int delay = mContext.getResources()
+                                .getInteger(R.integer.config_wifiDelayDisconnectOnImsLostMs);
+                        if (delay == 0 || !postDelayed(mRunnable, delay)) {
+                            continueToStopWifi();
+                        }
+                    }
+                }
+            }
+        };
+
         DeferStopHandler(String tag, Looper looper) {
             super(tag, looper);
             mLooper = looper;
@@ -156,8 +197,7 @@
                 return;
             }
 
-            mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(
-                    SubscriptionManager.getDefaultVoiceSubscriptionId());
+            mImsMmTelManager = ImsMmTelManager.createForSubscriptionId(mActiveSubId);
             if (mImsMmTelManager == null || !postDelayed(mRunnable, delayMs)) {
                 // if no delay or failed to add runnable, stop Wifi immediately.
                 continueToStopWifi();
@@ -166,7 +206,7 @@
 
             mIsDeferring = true;
             Log.d(TAG, "Start DeferWifiOff handler with deferring time "
-                    + delayMs + " ms.");
+                    + delayMs + " ms for subId: " + mActiveSubId);
             try {
                 mImsMmTelManager.registerImsRegistrationCallback(
                         new HandlerExecutor(new Handler(mLooper)),
@@ -174,7 +214,19 @@
             } catch (RuntimeException | ImsException e) {
                 Log.e(TAG, "registerImsRegistrationCallback failed", e);
                 continueToStopWifi();
+                return;
             }
+
+            mImsRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .build();
+
+            mConnectivityManager =
+                    (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+            mConnectivityManager.registerNetworkCallback(mImsRequest, mImsNetworkCallback,
+                                                         new Handler(mLooper));
         }
 
         private void continueToStopWifi() {
@@ -212,6 +264,11 @@
                     Log.e(TAG, "unregisterImsRegistrationCallback failed", e);
                 }
             }
+
+            if (mConnectivityManager != null) {
+                mConnectivityManager.unregisterNetworkCallback(mImsNetworkCallback);
+            }
+
             mIsDeferring = false;
         }
     }
@@ -220,13 +277,35 @@
      * Get deferring time before turning off WiFi.
      */
     private int getWifiOffDeferringTimeMs() {
-        int defaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
-        if (defaultVoiceSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+        SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        if (subscriptionManager == null) {
             return 0;
         }
 
-        ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(
-                defaultVoiceSubId);
+        List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
+        if (subInfoList == null) {
+            return 0;
+        }
+
+        // Get the maximum delay for the active subscription latched on IWLAN.
+        int maxDelay = 0;
+        for (SubscriptionInfo subInfo : subInfoList) {
+            int curDelay = getWifiOffDeferringTimeMs(subInfo.getSubscriptionId());
+            if (curDelay > maxDelay) {
+                maxDelay = curDelay;
+                mActiveSubId = subInfo.getSubscriptionId();
+            }
+        }
+        return maxDelay;
+    }
+
+    private int getWifiOffDeferringTimeMs(int subId) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return 0;
+        }
+
+        ImsMmTelManager imsMmTelManager = ImsMmTelManager.createForSubscriptionId(subId);
         // If no wifi calling, no delay
         if (!imsMmTelManager.isAvailable(
                     MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
@@ -236,7 +315,7 @@
 
         TelephonyManager defaultVoiceTelephonyManager =
                 mContext.getSystemService(TelephonyManager.class)
-                        .createForSubscriptionId(defaultVoiceSubId);
+                        .createForSubscriptionId(subId);
         // if LTE is available, no delay needed as IMS will be registered over LTE
         if (defaultVoiceTelephonyManager.getVoiceNetworkType()
                 == TelephonyManager.NETWORK_TYPE_LTE) {
@@ -245,7 +324,7 @@
 
         CarrierConfigManager configManager =
                 (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle config = configManager.getConfigForSubId(defaultVoiceSubId);
+        PersistableBundle config = configManager.getConfigForSubId(subId);
         return (config != null)
                 ? config.getInt(CarrierConfigManager.Ims.KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT)
                 : 0;
diff --git a/service/java/com/android/server/wifi/ConfigurationMap.java b/service/java/com/android/server/wifi/ConfigurationMap.java
index 1f21891..cce8958 100644
--- a/service/java/com/android/server/wifi/ConfigurationMap.java
+++ b/service/java/com/android/server/wifi/ConfigurationMap.java
@@ -21,6 +21,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -41,6 +43,15 @@
         mUserManager = userManager;
     }
 
+    /** Dump internal state for debugging. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mPerId=" + mPerID);
+        pw.println("mPerIDForCurrentUser=" + mPerIDForCurrentUser);
+        pw.println("mScanResultMatchInfoMapForCurrentUser="
+                + mScanResultMatchInfoMapForCurrentUser);
+        pw.println("mCurrentUserId=" + mCurrentUserId);
+    }
+
     // RW methods:
     public WifiConfiguration put(WifiConfiguration config) {
         final WifiConfiguration current = mPerID.put(config.networkId, config);
@@ -49,8 +60,12 @@
         if (config.shared || currentUser.equals(creatorUser)
                 || mUserManager.isSameProfileGroup(currentUser, creatorUser)) {
             mPerIDForCurrentUser.put(config.networkId, config);
-            mScanResultMatchInfoMapForCurrentUser.put(
-                    ScanResultMatchInfo.fromWifiConfiguration(config), config);
+            // TODO (b/142035508): Add a more generic fix. This cache should only hold saved
+            // networks.
+            if (!config.fromWifiNetworkSpecifier) {
+                mScanResultMatchInfoMapForCurrentUser.put(
+                        ScanResultMatchInfo.fromWifiConfiguration(config), config);
+            }
         }
         return current;
     }
diff --git a/service/java/com/android/server/wifi/DeviceConfigFacade.java b/service/java/com/android/server/wifi/DeviceConfigFacade.java
index d25a50a..cfd2ad0 100644
--- a/service/java/com/android/server/wifi/DeviceConfigFacade.java
+++ b/service/java/com/android/server/wifi/DeviceConfigFacade.java
@@ -53,10 +53,14 @@
     public static final int DEFAULT_DATA_STALL_TX_PER_THR = 90;
     // Default threshold of CCA level above which to trigger a data stall
     public static final int DEFAULT_DATA_STALL_CCA_LEVEL_THR = CHANNEL_UTILIZATION_SCALE;
-    // Default low threshold of L2 sufficient throughput in Kbps
-    public static final int DEFAULT_TPUT_SUFFICIENT_THR_LOW_KBPS = 1000;
-    // Default high threshold of L2 sufficient throughput in Kbps
-    public static final int DEFAULT_TPUT_SUFFICIENT_THR_HIGH_KBPS = 4000;
+    // Default low threshold of L2 sufficient Tx throughput in Kbps
+    public static final int DEFAULT_TX_TPUT_SUFFICIENT_THR_LOW_KBPS = 1000;
+    // Default high threshold of L2 sufficient Tx throughput in Kbps
+    public static final int DEFAULT_TX_TPUT_SUFFICIENT_THR_HIGH_KBPS = 4000;
+    // Default low threshold of L2 sufficient Rx throughput in Kbps
+    public static final int DEFAULT_RX_TPUT_SUFFICIENT_THR_LOW_KBPS = 1000;
+    // Default high threshold of L2 sufficient Rx throughput in Kbps
+    public static final int DEFAULT_RX_TPUT_SUFFICIENT_THR_HIGH_KBPS = 4000;
     // Numerator part of default threshold of L2 throughput over L3 throughput ratio
     public static final int DEFAULT_TPUT_SUFFICIENT_RATIO_THR_NUM = 2;
     // Denominator part of default threshold of L2 throughput over L3 throughput ratio
@@ -97,6 +101,12 @@
     static final int DEFAULT_BUG_REPORT_THRESHOLD_EXTRA_RATIO = 2;
     // Default overlapping connection duration threshold in ms to trigger bug report
     static final int DEFAULT_OVERLAPPING_CONNECTION_DURATION_THRESHOLD_MS = 75_000;
+    // At low traffic, Tx link speed values below the following threshold
+    // are ignored because it could be due to low rate management frames
+    static final int DEFAULT_TX_LINK_SPEED_LOW_THRESHOLD_MBPS = 9;
+    // At low traffic, Rx link speed values below the following threshold
+    // are ignored because it could be due to low rate management frames
+    static final int DEFAULT_RX_LINK_SPEED_LOW_THRESHOLD_MBPS = 9;
 
     // Cached values of fields updated via updateDeviceConfigFlags()
     private boolean mIsAbnormalConnectionBugreportEnabled;
@@ -106,8 +116,10 @@
     private int mDataStallRxTputThrKbps;
     private int mDataStallTxPerThr;
     private int mDataStallCcaLevelThr;
-    private int mTputSufficientLowThrKbps;
-    private int mTputSufficientHighThrKbps;
+    private int mTxTputSufficientLowThrKbps;
+    private int mTxTputSufficientHighThrKbps;
+    private int mRxTputSufficientLowThrKbps;
+    private int mRxTputSufficientHighThrKbps;
     private int mTputSufficientRatioThrNum;
     private int mTputSufficientRatioThrDen;
     private int mTxPktPerSecondThr;
@@ -136,6 +148,8 @@
     private int mBugReportThresholdExtraRatio;
     private boolean mIsOverlappingConnectionBugreportEnabled;
     private int mOverlappingConnectionDurationThresholdMs;
+    private int mTxLinkSpeedLowThresholdMbps;
+    private int mRxLinkSpeedLowThresholdMbps;
 
     public DeviceConfigFacade(Context context, Handler handler, WifiMetrics wifiMetrics) {
         mContext = context;
@@ -173,10 +187,14 @@
         mWifiMetrics.setDataStallTxPerThr(mDataStallTxPerThr);
         mWifiMetrics.setDataStallCcaLevelThr(mDataStallCcaLevelThr);
 
-        mTputSufficientLowThrKbps = DeviceConfig.getInt(NAMESPACE,
-                "tput_sufficient_low_thr_kbps", DEFAULT_TPUT_SUFFICIENT_THR_LOW_KBPS);
-        mTputSufficientHighThrKbps = DeviceConfig.getInt(NAMESPACE,
-                "tput_sufficient_high_thr_kbps", DEFAULT_TPUT_SUFFICIENT_THR_HIGH_KBPS);
+        mTxTputSufficientLowThrKbps = DeviceConfig.getInt(NAMESPACE,
+                "tput_sufficient_low_thr_kbps", DEFAULT_TX_TPUT_SUFFICIENT_THR_LOW_KBPS);
+        mTxTputSufficientHighThrKbps = DeviceConfig.getInt(NAMESPACE,
+                "tput_sufficient_high_thr_kbps", DEFAULT_TX_TPUT_SUFFICIENT_THR_HIGH_KBPS);
+        mRxTputSufficientLowThrKbps = DeviceConfig.getInt(NAMESPACE,
+                "rx_tput_sufficient_low_thr_kbps", DEFAULT_RX_TPUT_SUFFICIENT_THR_LOW_KBPS);
+        mRxTputSufficientHighThrKbps = DeviceConfig.getInt(NAMESPACE,
+                "rx_tput_sufficient_high_thr_kbps", DEFAULT_RX_TPUT_SUFFICIENT_THR_HIGH_KBPS);
         mTputSufficientRatioThrNum = DeviceConfig.getInt(NAMESPACE,
                 "tput_sufficient_ratio_thr_num", DEFAULT_TPUT_SUFFICIENT_RATIO_THR_NUM);
         mTputSufficientRatioThrDen = DeviceConfig.getInt(NAMESPACE,
@@ -254,6 +272,12 @@
         mOverlappingConnectionDurationThresholdMs = DeviceConfig.getInt(NAMESPACE,
                 "overlapping_connection_duration_threshold_ms",
                 DEFAULT_OVERLAPPING_CONNECTION_DURATION_THRESHOLD_MS);
+        mTxLinkSpeedLowThresholdMbps = DeviceConfig.getInt(NAMESPACE,
+                "tx_link_speed_low_threshold_mbps",
+                DEFAULT_TX_LINK_SPEED_LOW_THRESHOLD_MBPS);
+        mRxLinkSpeedLowThresholdMbps = DeviceConfig.getInt(NAMESPACE,
+                "rx_link_speed_low_threshold_mbps",
+                DEFAULT_RX_LINK_SPEED_LOW_THRESHOLD_MBPS);
     }
 
     private Set<String> getUnmodifiableSetQuoted(String key) {
@@ -321,15 +345,29 @@
     /**
      * Gets the low threshold of L2 throughput below which L2 throughput is always insufficient
      */
-    public int getTputSufficientLowThrKbps() {
-        return mTputSufficientLowThrKbps;
+    public int getTxTputSufficientLowThrKbps() {
+        return mTxTputSufficientLowThrKbps;
     }
 
     /**
      * Gets the high threshold of L2 throughput above which L2 throughput is always sufficient
      */
-    public int getTputSufficientHighThrKbps() {
-        return mTputSufficientHighThrKbps;
+    public int getTxTputSufficientHighThrKbps() {
+        return mTxTputSufficientHighThrKbps;
+    }
+
+    /**
+     * Gets the low threshold of L2 throughput below which L2 Rx throughput is always insufficient
+     */
+    public int getRxTputSufficientLowThrKbps() {
+        return mRxTputSufficientLowThrKbps;
+    }
+
+    /**
+     * Gets the high threshold of L2 throughput above which L2 Rx throughput is always sufficient
+     */
+    public int getRxTputSufficientHighThrKbps() {
+        return mRxTputSufficientHighThrKbps;
     }
 
     /**
@@ -531,4 +569,18 @@
     public int getOverlappingConnectionDurationThresholdMs() {
         return mOverlappingConnectionDurationThresholdMs;
     }
+
+    /**
+     * Gets the threshold of link speed below which Tx link speed is ignored at low traffic
+     */
+    public int getTxLinkSpeedLowThresholdMbps() {
+        return mTxLinkSpeedLowThresholdMbps;
+    }
+
+    /**
+     * Gets the threshold of link speed below which Rx link speed is ignored at low traffic
+     */
+    public int getRxLinkSpeedLowThresholdMbps() {
+        return mRxLinkSpeedLowThresholdMbps;
+    }
 }
diff --git a/service/java/com/android/server/wifi/MemoryStoreImpl.java b/service/java/com/android/server/wifi/MemoryStoreImpl.java
index 1b819d4..a15ae81 100644
--- a/service/java/com/android/server/wifi/MemoryStoreImpl.java
+++ b/service/java/com/android/server/wifi/MemoryStoreImpl.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.net.IpMemoryStore;
 import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.Status;
 import android.util.Log;
 
@@ -127,6 +128,35 @@
         }
     }
 
+    @Override
+    public void setCluster(String key, String cluster) {
+        if (mBroken) return;
+        try {
+            NetworkAttributes attributes = new NetworkAttributes.Builder()
+                    .setCluster(cluster)
+                    .build();
+            mIpMemoryStore.storeNetworkAttributes(key, attributes, status -> {
+                Log.d(TAG, "Set cluster " + cluster + " for " + key + ": " + status);
+            });
+        } catch (RuntimeException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void removeCluster(String cluster) {
+        if (mBroken) return;
+        try {
+            final boolean needWipe = true;
+            mIpMemoryStore.deleteCluster(cluster, needWipe, (status, deletedRecords) -> {
+                Log.d(TAG, "Remove cluster " + cluster + ": " + status
+                        + " deleted: " + deletedRecords);
+            });
+        } catch (RuntimeException e) {
+            handleException(e);
+        }
+    }
+
     /**
      * Starts using IpMemoryStore.
      */
diff --git a/service/java/com/android/server/wifi/WakeupController.java b/service/java/com/android/server/wifi/WakeupController.java
index b003b74..a92bdf7 100644
--- a/service/java/com/android/server/wifi/WakeupController.java
+++ b/service/java/com/android/server/wifi/WakeupController.java
@@ -240,6 +240,10 @@
      */
     public void start() {
         Log.d(TAG, "start()");
+        if (getGoodSavedNetworksAndSuggestions().isEmpty()) {
+            Log.i(TAG, "Ignore wakeup start since there are no good networks.");
+            return;
+        }
         mWifiInjector.getWifiScanner().registerScanListener(
                 new HandlerExecutor(mHandler), mScanListener);
 
@@ -343,7 +347,7 @@
         }
 
         Set<WifiNetworkSuggestion> networkSuggestions =
-                mWifiNetworkSuggestionsManager.getAllNetworkSuggestions();
+                mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions();
         for (WifiNetworkSuggestion suggestion : networkSuggestions) {
             // TODO(b/127799111): Do we need to filter the list similar to saved networks above?
             goodNetworks.add(
diff --git a/service/java/com/android/server/wifi/WifiBackupRestore.java b/service/java/com/android/server/wifi/WifiBackupRestore.java
index c55236e..ab46bb6 100644
--- a/service/java/com/android/server/wifi/WifiBackupRestore.java
+++ b/service/java/com/android/server/wifi/WifiBackupRestore.java
@@ -466,6 +466,8 @@
         public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName;
         public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName;
         public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName;
+        public static final String SUPPLICANT_KEY_AUTH_ALG =
+                WifiConfiguration.AuthAlgorithm.varName;
         public static final String SUPPLICANT_KEY_CLIENT_CERT =
                 WifiEnterpriseConfig.CLIENT_CERT_KEY;
         public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY;
@@ -535,6 +537,7 @@
             private String mParsedSSIDLine;
             private String mParsedHiddenLine;
             private String mParsedKeyMgmtLine;
+            private String mParsedAuthAlgLine;
             private String mParsedPskLine;
             private String[] mParsedWepKeyLines = new String[4];
             private String mParsedWepTxKeyIdxLine;
@@ -581,6 +584,8 @@
                     if (line.contains("EAP")) {
                         isEap = true;
                     }
+                } else if (line.startsWith(SUPPLICANT_KEY_AUTH_ALG + "=")) {
+                    mParsedAuthAlgLine = line;
                 } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT + "=")) {
                     certUsed = true;
                 } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT + "=")) {
@@ -660,6 +665,16 @@
                         }
                     }
                 }
+                if (mParsedAuthAlgLine != null) {
+                    if (mParsedAuthAlgLine.contains("OPEN")) {
+                        configuration.allowedAuthAlgorithms.set(
+                                WifiConfiguration.AuthAlgorithm.OPEN);
+                    }
+                    if (mParsedAuthAlgLine.contains("SHARED")) {
+                        configuration.allowedAuthAlgorithms.set(
+                                WifiConfiguration.AuthAlgorithm.SHARED);
+                    }
+                }
                 if (mParsedPskLine != null) {
                     configuration.preSharedKey =
                             mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1);
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index 209044a..6b88f71 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -2364,6 +2364,9 @@
      * @param scanDetail ScanDetail instance  to use for looking up the network.
      * @return WifiConfiguration object representing the network corresponding to the scanDetail,
      * null if none exists.
+     *
+     * TODO (b/142035508): This should only return saved networks (and rename to
+     * getSavedNetworkForScanDetail()).
      */
     public WifiConfiguration getConfiguredNetworkForScanDetail(ScanDetail scanDetail) {
         ScanResult scanResult = scanDetail.getScanResult();
@@ -2391,6 +2394,8 @@
      * {@link #mScanDetailCaches} for the retrieved network.
      *
      * @param scanDetail input a scanDetail from the scan result
+     * TODO (b/142035508): This should only return saved networks (and rename to
+     * updateScanDetailCacheFromScanDetail()).
      */
     public void updateScanDetailCacheFromScanDetail(ScanDetail scanDetail) {
         WifiConfiguration network = getConfiguredNetworkForScanDetail(scanDetail);
@@ -2407,6 +2412,8 @@
      * @param scanDetail input a scanDetail from the scan result
      * @return WifiConfiguration object representing the network corresponding to the scanDetail,
      * null if none exists.
+     * TODO (b/142035508): This should only return saved networks (and rename to
+     * getSavedNetworkForScanDetailAndCache()).
      */
     public WifiConfiguration getConfiguredNetworkForScanDetailAndCache(ScanDetail scanDetail) {
         WifiConfiguration network = getConfiguredNetworkForScanDetail(scanDetail);
@@ -3222,6 +3229,9 @@
             pw.println(network);
         }
         pw.println("WifiConfigManager - Configured networks End ----");
+        pw.println("WifiConfigManager - ConfigurationMap Begin ----");
+        mConfiguredNetworks.dump(fd, pw, args);
+        pw.println("WifiConfigManager - ConfigurationMap End ----");
         pw.println("WifiConfigManager - Next network ID to be allocated " + mNextNetworkId);
         pw.println("WifiConfigManager - Last selected network ID " + mLastSelectedNetworkId);
         pw.println("WifiConfigManager - PNO scan frequency culling enabled = "
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 8470d0c..2ce0bc0 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -85,16 +85,6 @@
     // it should comply to the minimum scan interval rule.
     private static final boolean SCAN_IMMEDIATELY = true;
     private static final boolean SCAN_ON_SCHEDULE = false;
-    // Initial PNO scan interval in milliseconds when the device is moving. The scan interval backs
-    // off from this initial interval on subsequent scans. This scan is performed when screen is
-    // off and disconnected.
-    @VisibleForTesting
-    static final int MOVING_PNO_SCAN_INTERVAL_MS = 20 * 1000; // 20 seconds
-    // Initial PNO scan interval in milliseconds when the device is stationary. The scan interval
-    // backs off from this initial interval on subsequent scans. This scan is performed when screen
-    // is off and disconnected.
-    @VisibleForTesting
-    static final int STATIONARY_PNO_SCAN_INTERVAL_MS = 60 * 1000; // 1 minute
 
     // PNO scan interval in milli-seconds. This is the scan
     // performed when screen is off and connected.
@@ -208,7 +198,6 @@
     private int[] mCurrentSingleScanScheduleSec;
 
     private int mCurrentSingleScanScheduleIndex;
-    private int mPnoScanIntervalMs;
     private WifiChannelUtilization mWifiChannelUtilization;
     // Cached WifiCandidates used in high mobility state to avoid connecting to APs that are
     // moving relative to the user.
@@ -792,7 +781,6 @@
         mClock = clock;
         mScoringParams = scoringParams;
         mConnectionAttemptTimeStamps = new LinkedList<>();
-        mPnoScanIntervalMs = MOVING_PNO_SCAN_INTERVAL_MS;
 
         // Listen to WifiConfigManager network update events
         mConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener());
@@ -1321,14 +1309,16 @@
         startPeriodicSingleScan();
     }
 
-    private static int deviceMobilityStateToPnoScanIntervalMs(@DeviceMobilityState int state) {
+    private int deviceMobilityStateToPnoScanIntervalMs(@DeviceMobilityState int state) {
         switch (state) {
             case WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN:
             case WifiManager.DEVICE_MOBILITY_STATE_LOW_MVMT:
             case WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT:
-                return MOVING_PNO_SCAN_INTERVAL_MS;
+                return mContext.getResources()
+                        .getInteger(R.integer.config_wifiMovingPnoScanIntervalMillis);
             case WifiManager.DEVICE_MOBILITY_STATE_STATIONARY:
-                return STATIONARY_PNO_SCAN_INTERVAL_MS;
+                return mContext.getResources()
+                        .getInteger(R.integer.config_wifiStationaryPnoScanIntervalMillis);
             default:
                 return -1;
         }
@@ -1343,16 +1333,18 @@
      * @param newState the new device mobility state
      */
     public void setDeviceMobilityState(@DeviceMobilityState int newState) {
-        mDeviceMobilityState = newState;
+        int oldDeviceMobilityState = mDeviceMobilityState;
         localLog("Device mobility state changed. state=" + newState);
-        mWifiChannelUtilization.setDeviceMobilityState(newState);
         int newPnoScanIntervalMs = deviceMobilityStateToPnoScanIntervalMs(newState);
         if (newPnoScanIntervalMs < 0) {
             Log.e(TAG, "Invalid device mobility state: " + newState);
             return;
         }
+        mDeviceMobilityState = newState;
+        mWifiChannelUtilization.setDeviceMobilityState(newState);
 
-        if (newPnoScanIntervalMs == mPnoScanIntervalMs) {
+        int oldPnoScanIntervalMs = deviceMobilityStateToPnoScanIntervalMs(oldDeviceMobilityState);
+        if (newPnoScanIntervalMs == oldPnoScanIntervalMs) {
             if (mPnoScanStarted) {
                 mWifiMetrics.logPnoScanStop();
                 mWifiMetrics.enterDeviceMobilityState(newState);
@@ -1361,8 +1353,7 @@
                 mWifiMetrics.enterDeviceMobilityState(newState);
             }
         } else {
-            mPnoScanIntervalMs = newPnoScanIntervalMs;
-            Log.d(TAG, "PNO Scan Interval changed to " + mPnoScanIntervalMs + " ms.");
+            Log.d(TAG, "PNO Scan Interval changed to " + newPnoScanIntervalMs + " ms.");
 
             if (mPnoScanStarted) {
                 Log.d(TAG, "Restarting PNO Scan with new scan interval");
@@ -1400,7 +1391,7 @@
         scanSettings.band = getScanBand();
         scanSettings.reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH;
         scanSettings.numBssidsPerScan = 0;
-        scanSettings.periodInMs = mPnoScanIntervalMs;
+        scanSettings.periodInMs = deviceMobilityStateToPnoScanIntervalMs(mDeviceMobilityState);
 
         mPnoScanListener.clearScanDetails();
 
@@ -1635,7 +1626,7 @@
         }
 
         Set<WifiNetworkSuggestion> suggestionsNetworks =
-                mWifiNetworkSuggestionsManager.getAllNetworkSuggestions();
+                mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions();
         // If total size not equal to 1, then no need to proceed
         if (passpointNetworks.size() + savedNetworks.size() + suggestionsNetworks.size() != 1) {
             return false;
@@ -1856,6 +1847,7 @@
         if (!mWifiEnabled) return;
         localLog("forceConnectivityScan in request of " + workSource);
 
+        clearConnectionAttemptTimeStamps();
         mWaitForFullBandScanResults = true;
         startForcedSingleScan(true, workSource);
     }
diff --git a/service/java/com/android/server/wifi/WifiDataStall.java b/service/java/com/android/server/wifi/WifiDataStall.java
index 8dea0df..87b9a1b 100644
--- a/service/java/com/android/server/wifi/WifiDataStall.java
+++ b/service/java/com/android/server/wifi/WifiDataStall.java
@@ -41,9 +41,6 @@
     private static final String TAG = "WifiDataStall";
     private boolean mVerboseLoggingEnabled = false;
     public static final int INVALID_THROUGHPUT = -1;
-    // At low traffic, link speed values below the following threshold
-    // are ignored because it could be due to low rate management frames
-    public static final int LINK_SPEED_LOW_THRESHOLD_MBPS = 9;
     // Maximum time gap between two WifiLinkLayerStats to trigger a data stall
     public static final int MAX_MS_DELTA_FOR_DATA_STALL = 60 * 1000; // 1 minute
     // Maximum time that a data stall start time stays valid.
@@ -306,7 +303,8 @@
 
         if (txLinkSpeedMbps > 0) {
             // Exclude update with low rate management frames
-            if (isTxTrafficHigh || txLinkSpeedMbps > LINK_SPEED_LOW_THRESHOLD_MBPS) {
+            if (isTxTrafficHigh
+                    || txLinkSpeedMbps > mDeviceConfigFacade.getTxLinkSpeedLowThresholdMbps()) {
                 mTxTputKbps = (int) ((long) txLinkSpeedMbps * 1000 * (100 - txPer) / 100
                         * (CHANNEL_UTILIZATION_SCALE  - ccaLevel) / CHANNEL_UTILIZATION_SCALE);
             }
@@ -317,7 +315,8 @@
 
         if (rxLinkSpeedMbps > 0) {
             // Exclude update with low rate management frames
-            if (isRxTrafficHigh || rxLinkSpeedMbps > LINK_SPEED_LOW_THRESHOLD_MBPS) {
+            if (isRxTrafficHigh
+                    || rxLinkSpeedMbps > mDeviceConfigFacade.getRxLinkSpeedLowThresholdMbps()) {
                 mRxTputKbps = (int) ((long) rxLinkSpeedMbps * 1000
                         * (CHANNEL_UTILIZATION_SCALE  - ccaLevel) / CHANNEL_UTILIZATION_SCALE);
             }
@@ -438,8 +437,8 @@
         mLastTxBytes = txBytes;
         mLastRxBytes = rxBytes;
 
-        boolean isTxTputSufficient = isL2ThroughputSufficient(l2TxTputKbps, l3TxTputKbps);
-        boolean isRxTputSufficient = isL2ThroughputSufficient(l2RxTputKbps, l3RxTputKbps);
+        boolean isTxTputSufficient = isL2ThroughputSufficient(l2TxTputKbps, l3TxTputKbps, false);
+        boolean isRxTputSufficient = isL2ThroughputSufficient(l2RxTputKbps, l3RxTputKbps, true);
         isTxTputSufficient = detectAndOverrideFalseInSufficient(
                 isTxTputSufficient, isTxTrafficHigh, mIsThroughputSufficient);
         isRxTputSufficient = detectAndOverrideFalseInSufficient(
@@ -466,17 +465,20 @@
      * 3) L3 tput is not low and L2 tput is above its high threshold
      * 4) L2 tput is invalid
      */
-    private boolean isL2ThroughputSufficient(int l2TputKbps, int l3TputKbps) {
+    private boolean isL2ThroughputSufficient(int l2TputKbps, int l3TputKbps, boolean isForRxTput) {
         if (l2TputKbps == INVALID_THROUGHPUT) return true;
+        int tputSufficientLowThrKbps = mDeviceConfigFacade.getTxTputSufficientLowThrKbps();
+        int tputSufficientHighThrKbps = mDeviceConfigFacade.getTxTputSufficientHighThrKbps();
+        if (isForRxTput) {
+            tputSufficientLowThrKbps = mDeviceConfigFacade.getRxTputSufficientLowThrKbps();
+            tputSufficientHighThrKbps = mDeviceConfigFacade.getRxTputSufficientHighThrKbps();
+        }
         boolean isL3TputLow = (l3TputKbps * mDeviceConfigFacade.getTputSufficientRatioThrDen())
-                < (mDeviceConfigFacade.getTputSufficientLowThrKbps()
-                * mDeviceConfigFacade.getTputSufficientRatioThrNum());
-        boolean isL2TputAboveLowThr =
-                l2TputKbps >= mDeviceConfigFacade.getTputSufficientLowThrKbps();
+                < (tputSufficientLowThrKbps * mDeviceConfigFacade.getTputSufficientRatioThrNum());
+        boolean isL2TputAboveLowThr = l2TputKbps >= tputSufficientLowThrKbps;
         if (isL3TputLow) return isL2TputAboveLowThr;
 
-        boolean isL2TputAboveHighThr =
-                l2TputKbps >= mDeviceConfigFacade.getTputSufficientHighThrKbps();
+        boolean isL2TputAboveHighThr = l2TputKbps >= tputSufficientHighThrKbps;
         boolean isL2L3TputRatioAboveThr =
                 (l2TputKbps * mDeviceConfigFacade.getTputSufficientRatioThrDen())
                 >= (l3TputKbps * mDeviceConfigFacade.getTputSufficientRatioThrNum());
diff --git a/service/java/com/android/server/wifi/WifiDiagnostics.java b/service/java/com/android/server/wifi/WifiDiagnostics.java
index 4ac9820..2ba90c9 100644
--- a/service/java/com/android/server/wifi/WifiDiagnostics.java
+++ b/service/java/com/android/server/wifi/WifiDiagnostics.java
@@ -44,6 +44,7 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.zip.Deflater;
 
@@ -107,6 +108,9 @@
     /** Minimum dump period with same error code */
     public static final long MIN_DUMP_TIME_WINDOW_MILLIS = 10 * 60 * 1000; // 10 mins
 
+    // Timeout for logcat
+    private static final int LOGCAT_TIMEOUT_MILLIS = 500;
+
     private long mLastBugReportTime;
 
     @VisibleForTesting public static final String FIRMWARE_DUMP_SECTION_HEADER =
@@ -720,7 +724,7 @@
             while ((line = reader.readLine()) != null) {
                 lines.add(line);
             }
-            process.waitFor();
+            process.waitFor(LOGCAT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         } catch (InterruptedException|IOException e) {
             mLog.dump("Exception while capturing logcat: %").c(e.toString()).flush();
         }
diff --git a/service/java/com/android/server/wifi/WifiHealthMonitor.java b/service/java/com/android/server/wifi/WifiHealthMonitor.java
index 27167dc..8ab3a74 100644
--- a/service/java/com/android/server/wifi/WifiHealthMonitor.java
+++ b/service/java/com/android/server/wifi/WifiHealthMonitor.java
@@ -326,6 +326,15 @@
         pw.println("WifiHealthMonitor - Log End ----");
     }
 
+    /**
+     * Get current wifi mainline module long version code
+     * @Return a non-zero value if version code is available, 0 otherwise.
+     */
+    public long getWifiStackVersion() {
+        WifiSoftwareBuildInfo currentBuild = getWifiSystemInfoStats().getCurrSoftwareBuildInfo();
+        return (currentBuild == null) ? 0 : currentBuild.getWifiStackVersion();
+    }
+
     private synchronized void dailyDetectionHandler() {
         logd("Run daily detection");
         // Clear daily detection result
@@ -533,12 +542,12 @@
             return null;
         }
         PackageManager packageManager = mContext.getPackageManager();
-        int wifiStackVersion = 0;
+        long wifiStackVersion = 0;
         try {
             wifiStackVersion = packageManager.getPackageInfo(
-                    WIFI_APK_PACKAGE_NAME, PackageManager.MATCH_APEX).versionCode;
+                    WIFI_APK_PACKAGE_NAME, PackageManager.MATCH_APEX).getLongVersionCode();
         } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, " hit PackageManager nameNotFoundException");
+            Log.e(TAG, " Hit PackageManager exception", e);
         }
         String osBuildVersion = replaceNullByEmptyString(Build.DISPLAY);
         if (mWifiNative == null) {
@@ -629,10 +638,10 @@
      */
     final class WifiSoftwareBuildInfo {
         private String mOsBuildVersion;
-        private int mWifiStackVersion;
+        private long mWifiStackVersion;
         private String mWifiDriverVersion;
         private String mWifiFirmwareVersion;
-        WifiSoftwareBuildInfo(@NonNull String osBuildVersion, int wifiStackVersion,
+        WifiSoftwareBuildInfo(@NonNull String osBuildVersion, long wifiStackVersion,
                 @NonNull String wifiDriverVersion, @NonNull String wifiFirmwareVersion) {
             mOsBuildVersion = osBuildVersion;
             mWifiStackVersion = wifiStackVersion;
@@ -648,7 +657,7 @@
         String getOsBuildVersion() {
             return mOsBuildVersion;
         }
-        int getWifiStackVersion() {
+        long getWifiStackVersion() {
             return mWifiStackVersion;
         }
         String getWifiDriverVersion() {
@@ -842,7 +851,7 @@
                 @NonNull SoftwareBuildInfo softwareBuildInfo) {
             String osBuildVersion = softwareBuildInfo.hasOsBuildVersion()
                     ? softwareBuildInfo.getOsBuildVersion() : "NA";
-            int stackVersion = softwareBuildInfo.hasWifiStackVersion()
+            long stackVersion = softwareBuildInfo.hasWifiStackVersion()
                     ? softwareBuildInfo.getWifiStackVersion() : 0;
             String driverVersion = softwareBuildInfo.hasWifiDriverVersion()
                     ? softwareBuildInfo.getWifiDriverVersion() : "NA";
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 383659e..e23779e 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -62,6 +62,7 @@
 import com.android.server.wifi.p2p.WifiP2pMetrics;
 import com.android.server.wifi.proto.WifiStatsLog;
 import com.android.server.wifi.proto.nano.WifiMetricsProto;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.CarrierWifiMetrics;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.DeviceMobilityStatePnoScanStats;
 import com.android.server.wifi.proto.nano.WifiMetricsProto.ExperimentValues;
@@ -500,6 +501,9 @@
     private final SoftApConfigLimitationMetrics mSoftApConfigLimitationMetrics =
             new SoftApConfigLimitationMetrics();
 
+    private final CarrierWifiMetrics mCarrierWifiMetrics =
+            new CarrierWifiMetrics();
+
     @VisibleForTesting
     static class NetworkSelectionExperimentResults {
         public static final int MAX_CHOICES = 10;
@@ -1185,6 +1189,38 @@
         }
     }
 
+    class CarrierWifiMetrics {
+        public int numConnectionSuccess = 0;
+        public int numConnectionAuthFailure = 0;
+        public int numConnectionNonAuthFailure = 0;
+
+        public WifiMetricsProto.CarrierWifiMetrics toProto() {
+            WifiMetricsProto.CarrierWifiMetrics proto =
+                    new WifiMetricsProto.CarrierWifiMetrics();
+            proto.numConnectionSuccess = numConnectionSuccess;
+            proto.numConnectionAuthFailure = numConnectionAuthFailure;
+            proto.numConnectionNonAuthFailure = numConnectionNonAuthFailure;
+            return proto;
+        }
+
+        public void clear() {
+            numConnectionSuccess = 0;
+            numConnectionAuthFailure = 0;
+            numConnectionNonAuthFailure = 0;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("numConnectionSuccess=")
+                    .append(numConnectionSuccess)
+                    .append(", numConnectionAuthFailure=")
+                    .append(numConnectionAuthFailure)
+                    .append(", numConnectionNonAuthFailure")
+                    .append(numConnectionNonAuthFailure);
+            return sb.toString();
+        }
+    }
 
     public WifiMetrics(Context context, FrameworkFacade facade, Clock clock, Looper looper,
             WifiAwareMetrics awareMetrics, RttMetrics rttMetrics,
@@ -3646,6 +3682,8 @@
                         + mTxThroughputMbpsHistogramAbove2G);
                 pw.println("mRxThroughputMbpsHistogramAbove2G:\n"
                         + mRxThroughputMbpsHistogramAbove2G);
+                pw.println("mCarrierWifiMetrics:\n"
+                        + mCarrierWifiMetrics);
 
                 dumpInitPartialScanMetrics(pw);
             }
@@ -4321,6 +4359,9 @@
             initialPartialScanStats.failedScanChannelCountHistogram =
                     mInitPartialScanFailureHistogram.toProto();
             mWifiLogProto.initPartialScanStats = initialPartialScanStats;
+            mWifiLogProto.carrierWifiMetrics = mCarrierWifiMetrics.toProto();
+            mWifiLogProto.mainlineModuleVersion = mWifiHealthMonitor.getWifiStackVersion();
+
         }
     }
 
@@ -4538,6 +4579,7 @@
             mInitPartialScanFailureCount = 0;
             mInitPartialScanSuccessHistogram.clear();
             mInitPartialScanFailureHistogram.clear();
+            mCarrierWifiMetrics.clear();
         }
     }
 
@@ -4696,6 +4738,9 @@
         staEvent.totalTxBytes = mFacade.getTotalTxBytes();
         staEvent.totalRxBytes = mFacade.getTotalRxBytes();
         staEvent.screenOn = mScreenOn;
+        if (mWifiDataStall != null) {
+            staEvent.isCellularDataAvailable = mWifiDataStall.isCellularDataAvailable();
+        }
         mSupplicantStateChangeBitmask = 0;
         mLastPollRssi = -127;
         mLastPollFreq = -1;
@@ -4911,6 +4956,7 @@
         if (event.totalTxBytes > 0) sb.append(" totalTxBytes=").append(event.totalTxBytes);
         if (event.totalRxBytes > 0) sb.append(" totalRxBytes=").append(event.totalRxBytes);
         sb.append(" screenOn=").append(event.screenOn);
+        sb.append(" cellularData=").append(event.isCellularDataAvailable);
         if (event.supplicantStateChangesBitmask != 0) {
             sb.append(", ").append(supplicantStateChangesBitmaskToString(
                     event.supplicantStateChangesBitmask));
@@ -6448,4 +6494,40 @@
     public void noteSoftApClientBlocked(int maxClient) {
         mSoftApConfigLimitationMetrics.maxClientSettingWhenReachHistogram.increment(maxClient);
     }
+
+    /**
+     * Increment number of connection with different BSSID between framework and firmware selection.
+     */
+    public void incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware() {
+        synchronized (mLock) {
+            mWifiLogProto.numBssidDifferentSelectionBetweenFrameworkAndFirmware++;
+        }
+    }
+
+    /**
+     * Note the carrier wifi network connected successfully.
+     */
+    public void incrementNumOfCarrierWifiConnectionSuccess() {
+        synchronized (mLock) {
+            mCarrierWifiMetrics.numConnectionSuccess++;
+        }
+    }
+
+    /**
+     * Note the carrier wifi network connection authentication failure.
+     */
+    public void incrementNumOfCarrierWifiConnectionAuthFailure() {
+        synchronized (mLock) {
+            mCarrierWifiMetrics.numConnectionAuthFailure++;
+        }
+    }
+
+    /**
+     * Note the carrier wifi network connection non-authentication failure.
+     */
+    public void incrementNumOfCarrierWifiConnectionNonAuthFailure() {
+        synchronized (mLock) {
+            mCarrierWifiMetrics.numConnectionNonAuthFailure++;
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiNetworkFactory.java b/service/java/com/android/server/wifi/WifiNetworkFactory.java
index 98ba5bc..9dc89ad 100644
--- a/service/java/com/android/server/wifi/WifiNetworkFactory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkFactory.java
@@ -146,6 +146,7 @@
     private boolean mVerboseLoggingEnabled = false;
     private boolean mPeriodicScanTimerSet = false;
     private boolean mConnectionTimeoutSet = false;
+    private boolean mIsPeriodicScanEnabled = false;
     private boolean mIsPeriodicScanPaused = false;
     // We sent a new connection request and are waiting for connection success.
     private boolean mPendingConnectionSuccess = false;
@@ -256,6 +257,7 @@
         public void onAlarm() {
             // Trigger the next scan.
             startScan();
+            mPeriodicScanTimerSet = false;
         }
     }
 
@@ -264,6 +266,7 @@
         public void onAlarm() {
             Log.e(TAG, "Timed-out connecting to network");
             handleNetworkConnectionFailure(mUserSelectedNetwork);
+            mConnectionTimeoutSet = false;
         }
     }
 
@@ -819,6 +822,7 @@
 
         // Cancel the ongoing scans after user selection.
         cancelPeriodicScans();
+        mIsPeriodicScanEnabled = false;
 
         // Trigger connection attempts.
         handleConnectToNetworkUserSelectionInternal(network);
@@ -918,13 +922,13 @@
     public void handleScreenStateChanged(boolean screenOn) {
         // If there is no active request or if the user has already selected a network,
         // ignore screen state changes.
-        if (mActiveSpecificNetworkRequest == null || mUserSelectedNetwork != null) return;
+        if (mActiveSpecificNetworkRequest == null || !mIsPeriodicScanEnabled) return;
 
         // Pause periodic scans when the screen is off & resume when the screen is on.
         if (screenOn) {
             if (mVerboseLoggingEnabled) Log.v(TAG, "Resuming scans on screen on");
-            startScan();
             mIsPeriodicScanPaused = false;
+            startScan();
         } else {
             if (mVerboseLoggingEnabled) Log.v(TAG, "Pausing scans on screen off");
             cancelPeriodicScans();
@@ -971,6 +975,7 @@
         mActiveSpecificNetworkRequestSpecifier = null;
         mUserSelectedNetwork = null;
         mUserSelectedNetworkConnectRetryCount = 0;
+        mIsPeriodicScanEnabled = false;
         mIsPeriodicScanPaused = false;
         mActiveMatchedScanResults = null;
         mPendingConnectionSuccess = false;
@@ -1075,6 +1080,7 @@
             mScanSettings.hiddenNetworks.add(new WifiScanner.ScanSettings.HiddenNetwork(
                     addEnclosingQuotes(wns.ssidPatternMatcher.getPath())));
         }
+        mIsPeriodicScanEnabled = true;
         startScan();
     }
 
@@ -1103,6 +1109,10 @@
             Log.e(TAG, "Scan triggered when there is no active network request. Ignoring...");
             return;
         }
+        if (!mIsPeriodicScanEnabled) {
+            Log.e(TAG, "Scan triggered after user selected network. Ignoring...");
+            return;
+        }
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Starting the next scan for " + mActiveSpecificNetworkRequestSpecifier);
         }
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index 7e95fba..f59a50f 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -940,6 +940,9 @@
         }
         WifiConfiguration config =
                 mWifiConfigManager.getConfiguredNetwork(choice.candidateKey.networkId);
+        if (config == null) {
+            return;
+        }
         if (config.isPasspoint()) {
             config.SSID = choice.candidateKey.matchInfo.networkSsid;
             mWifiConfigManager.addOrUpdateNetwork(config, config.creatorUid, config.creatorName);
diff --git a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
index f470039..5d5a7d7 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
@@ -1254,6 +1254,19 @@
     }
 
     /**
+     * Returns a set of all network suggestions across all apps that have been approved by user.
+     */
+    public Set<WifiNetworkSuggestion> getAllApprovedNetworkSuggestions() {
+        final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
+        return mActiveNetworkSuggestionsPerApp.values()
+                .stream()
+                .filter(e -> e.isApproved(activeScorerPackage))
+                .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions)
+                        .stream())
+                .collect(Collectors.toSet());
+    }
+
+    /**
      * Get all user approved, non-passpoint networks from suggestion.
      */
     public List<WifiConfiguration> getAllScanOptimizationSuggestionNetworks() {
diff --git a/service/java/com/android/server/wifi/WifiScoreCard.java b/service/java/com/android/server/wifi/WifiScoreCard.java
index 346a8fe..ca77e83 100644
--- a/service/java/com/android/server/wifi/WifiScoreCard.java
+++ b/service/java/com/android/server/wifi/WifiScoreCard.java
@@ -136,6 +136,10 @@
         void read(String key, String name, BlobListener blobListener);
         /** Requests a write, does not wait for completion */
         void write(String key, String name, byte[] value);
+        /** Sets the cluster identifier */
+        void setCluster(String key, String cluster);
+        /** Requests removal of all entries matching the cluster */
+        void removeCluster(String cluster);
     }
     /** Asynchronous response to a read request */
     public interface BlobListener {
@@ -249,9 +253,15 @@
         if (perBssid == mDummyPerBssid) {
             return new Pair<>(null, null);
         }
-        final long groupIdHash = computeHashLong(
-                perBssid.ssid, mDummyPerBssid.bssid, mL2KeySeed);
-        return new Pair<>(perBssid.getL2Key(), groupHintFromLong(groupIdHash));
+        return new Pair<>(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid));
+    }
+
+    /**
+     * Computes the GroupHint associated with the given ssid.
+     */
+    public @NonNull String groupHintFromSsid(String ssid) {
+        final long groupIdHash = computeHashLong(ssid, mDummyPerBssid.bssid, mL2KeySeed);
+        return groupHintFromLong(groupIdHash);
     }
 
     /**
@@ -1447,6 +1457,7 @@
                 Log.e(TAG, "More answers than we expected!");
             }
         }
+
         /**
          * Handles (when convenient) the arrival of previously stored data.
          *
@@ -1459,7 +1470,6 @@
             return mPendingReadFromStore.getAndSet(null);
         }
 
-
         int idFromLong() {
             return (int) mHash & 0x7fffffff;
         }
@@ -1552,10 +1562,9 @@
             return;
         }
         mApForNetwork.remove(ssid);
+        mApForBssid.entrySet().removeIf(entry -> ssid.equals(entry.getValue().ssid));
         if (mMemoryStore == null) return;
-        PerNetwork ans = new PerNetwork(ssid);
-        byte[] serialized = {};
-        mMemoryStore.write(ans.getL2Key(), PER_NETWORK_DATA_NAME, serialized);
+        mMemoryStore.removeCluster(groupHintFromSsid(ssid));
     }
 
     void requestReadNetwork(final PerNetwork perNetwork) {
@@ -1586,7 +1595,9 @@
             if (perBssid.changed) {
                 perBssid.finishPendingRead();
                 byte[] serialized = perBssid.toAccessPoint(/* No BSSID */ true).toByteArray();
+                mMemoryStore.setCluster(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid));
                 mMemoryStore.write(perBssid.getL2Key(), PER_BSSID_DATA_NAME, serialized);
+
                 perBssid.changed = false;
                 count++;
                 bytes += serialized.length;
@@ -1606,6 +1617,7 @@
             if (perNetwork.changed) {
                 perNetwork.finishPendingRead();
                 byte[] serialized = perNetwork.toNetworkStats().toByteArray();
+                mMemoryStore.setCluster(perNetwork.getL2Key(), groupHintFromSsid(perNetwork.ssid));
                 mMemoryStore.write(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME, serialized);
                 perNetwork.changed = false;
                 count++;
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 0772b4c..9d12aa0 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -879,6 +879,14 @@
 
         mLog.info("startSoftAp uid=%").c(Binder.getCallingUid()).flush();
 
+        SoftApConfiguration softApConfig = null;
+        if (wifiConfig != null) {
+            softApConfig = ApConfigUtil.fromWifiConfiguration(wifiConfig);
+            if (softApConfig == null) {
+                return false;
+            }
+        }
+
         if (!mTetheredSoftApTracker.setEnablingIfAllowed()) {
             mLog.err("Tethering is already active.").flush();
             return false;
@@ -890,17 +898,14 @@
             mLohsSoftApTracker.stopAll();
         }
 
-        if (wifiConfig != null) {
-            SoftApConfiguration softApConfig = ApConfigUtil.fromWifiConfiguration(wifiConfig);
-            if (softApConfig == null) return false;
-            return startSoftApInternal(new SoftApModeConfiguration(
-                    WifiManager.IFACE_IP_MODE_TETHERED,
-                    softApConfig, mTetheredSoftApTracker.getSoftApCapability()));
-        } else {
-            return startSoftApInternal(new SoftApModeConfiguration(
-                    WifiManager.IFACE_IP_MODE_TETHERED, null,
-                    mTetheredSoftApTracker.getSoftApCapability()));
+        if (!startSoftApInternal(new SoftApModeConfiguration(
+                WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
+                mTetheredSoftApTracker.getSoftApCapability()))) {
+            mTetheredSoftApTracker.setFailedWhileEnabling();
+            return false;
         }
+
+        return true;
     }
 
     private boolean validateSoftApBand(int apBand) {
@@ -951,11 +956,15 @@
             mLohsSoftApTracker.stopAll();
         }
 
-        return startSoftApInternal(new SoftApModeConfiguration(
+        if (!startSoftApInternal(new SoftApModeConfiguration(
                 WifiManager.IFACE_IP_MODE_TETHERED, softApConfig,
-                mTetheredSoftApTracker.getSoftApCapability()));
-    }
+                mTetheredSoftApTracker.getSoftApCapability()))) {
+            mTetheredSoftApTracker.setFailedWhileEnabling();
+            return false;
+        }
 
+        return true;
+    }
 
     /**
      * Internal method to start softap mode. Callers of this method should have already checked
@@ -1053,6 +1062,14 @@
             }
         }
 
+        public void setFailedWhileEnabling() {
+            synchronized (mLock) {
+                if (mTetheredSoftApState == WIFI_AP_STATE_ENABLING) {
+                    mTetheredSoftApState = WIFI_AP_STATE_FAILED;
+                }
+            }
+        }
+
         public List<WifiClient> getConnectedClients() {
             synchronized (mLock) {
                 return mTetheredSoftApConnectedClients;
diff --git a/service/java/com/android/server/wifi/WifiShellCommand.java b/service/java/com/android/server/wifi/WifiShellCommand.java
index 842493c..41933ab 100644
--- a/service/java/com/android/server/wifi/WifiShellCommand.java
+++ b/service/java/com/android/server/wifi/WifiShellCommand.java
@@ -372,10 +372,10 @@
                     if (ApConfigUtil.isWpa3SaeSupported(mContext)) {
                         pw.println("wifi_softap_wpa3_sae_supported");
                     }
-                    break;
+                    return 0;
                 case "settings-reset":
                     mWifiService.factoryReset(SHELL_PACKAGE_NAME);
-                    break;
+                    return 0;
                 case "list-scan-results":
                     List<ScanResult> scanResults =
                             mWifiService.getScanResults(SHELL_PACKAGE_NAME, null);
@@ -385,10 +385,10 @@
                         ScanResultUtil.dumpScanResults(pw, scanResults,
                                 SystemClock.elapsedRealtime());
                     }
-                    break;
+                    return 0;
                 case "start-scan":
                     mWifiService.startScan(SHELL_PACKAGE_NAME, null);
-                    break;
+                    return 0;
                 case "list-networks":
                     ParceledListSlice<WifiConfiguration> networks =
                             mWifiService.getConfiguredNetworks(SHELL_PACKAGE_NAME, null);
@@ -414,7 +414,7 @@
                                     securityType));
                         }
                     }
-                    break;
+                    return 0;
                 case "connect-network": {
                     CountDownLatch countDownLatch = new CountDownLatch(1);
                     IActionListener.Stub actionListener = new IActionListener.Stub() {
@@ -436,7 +436,7 @@
                     // wait for status.
                     countDownLatch.await(500, TimeUnit.MILLISECONDS);
                     setAutoJoin(pw, config.SSID, config.allowAutojoin);
-                    break;
+                    return 0;
                 }
                 case "add-network": {
                     CountDownLatch countDownLatch = new CountDownLatch(1);
@@ -459,7 +459,7 @@
                     // wait for status.
                     countDownLatch.await(500, TimeUnit.MILLISECONDS);
                     setAutoJoin(pw, config.SSID, config.allowAutojoin);
-                    break;
+                    return 0;
                 }
                 case "forget-network": {
                     String networkId = getNextArgRequired();
@@ -482,21 +482,21 @@
                             actionListener.hashCode());
                     // wait for status.
                     countDownLatch.await(500, TimeUnit.MILLISECONDS);
-                    break;
+                    return 0;
                 }
                 case "status":
                     printStatus(pw);
-                    break;
+                    return 0;
                 case "set-verbose-logging": {
                     boolean enabled = getNextArgRequiredTrueOrFalse("enabled", "disabled");
                     mWifiService.enableVerboseLogging(enabled ? 1 : 0);
-                    break;
+                    return 0;
                 }
                 case "add-suggestion": {
                     WifiNetworkSuggestion suggestion = buildSuggestion(pw);
                     mWifiService.addNetworkSuggestions(
                             Arrays.asList(suggestion), SHELL_PACKAGE_NAME, null);
-                    break;
+                    return 0;
                 }
                 case "remove-suggestion": {
                     String ssid = getNextArgRequired();
@@ -512,12 +512,12 @@
                     }
                     mWifiService.removeNetworkSuggestions(
                             Arrays.asList(suggestion), SHELL_PACKAGE_NAME);
-                    break;
+                    return 0;
                 }
                 case "remove-all-suggestions":
                     mWifiService.removeNetworkSuggestions(
                             Collections.emptyList(), SHELL_PACKAGE_NAME);
-                    break;
+                    return 0;
                 case "list-suggestions": {
                     List<WifiNetworkSuggestion> suggestions =
                             mWifiService.getNetworkSuggestions(SHELL_PACKAGE_NAME);
@@ -548,7 +548,7 @@
                                     securityType));
                         }
                     }
-                    break;
+                    return 0;
                 }
                 case "add-request": {
                     NetworkRequest networkRequest = buildNetworkRequest(pw);
@@ -558,7 +558,7 @@
                     mConnectivityManager.requestNetwork(networkRequest, networkCallback);
                     String ssid = getAllArgs()[1];
                     sActiveRequests.put(ssid, Pair.create(networkRequest, networkCallback));
-                    break;
+                    return 0;
                 }
                 case "remove-request": {
                     String ssid = getNextArgRequired();
@@ -570,7 +570,7 @@
                     }
                     pw.println("Removing request: " + nrAndNc.first);
                     mConnectivityManager.unregisterNetworkCallback(nrAndNc.second);
-                    break;
+                    return 0;
                 }
                 case "remove-all-requests":
                     if (sActiveRequests.isEmpty()) {
@@ -583,7 +583,7 @@
                         mConnectivityManager.unregisterNetworkCallback(nrAndNc.second);
                     }
                     sActiveRequests.clear();
-                    break;
+                    return 0;
                 case "list-requests":
                     if (sActiveRequests.isEmpty()) {
                         pw.println("No active requests");
@@ -596,7 +596,7 @@
                                     entry.getKey(), entry.getValue().first));
                         }
                     }
-                    break;
+                    return 0;
                 case "network-requests-set-user-approved": {
                     String packageName = getNextArgRequired();
                     boolean approved = getNextArgRequiredTrueOrFalse("yes", "no");
@@ -667,11 +667,12 @@
             }
         } catch (IllegalArgumentException e) {
             pw.println("Invalid args for " + cmd + ": " + e);
+            return -1;
         } catch (Exception e) {
             pw.println("Exception while executing WifiShellCommand: ");
             e.printStackTrace(pw);
+            return -1;
         }
-        return -1;
     }
 
     private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString)
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index 49ce8ad..e190a24 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -568,12 +568,7 @@
             nnri.peerDataMac = mac;
             nnri.channelInfo = channelInfo;
 
-            final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(
-                    sNetworkCapabilitiesFilter);
-            LinkProperties linkProperties = new LinkProperties();
-
-            boolean interfaceUsedByAnotherNdp = isInterfaceUpAndUsedByAnotherNdp(nnri);
-            if (!interfaceUsedByAnotherNdp) {
+            if (!isInterfaceUpAndUsedByAnotherNdp(nnri)) {
                 try {
                     mNetdWrapper.setInterfaceUp(nnri.interfaceName);
                     mNetdWrapper.enableIpv6(nnri.interfaceName);
@@ -609,59 +604,8 @@
                 }
             }
 
-            try {
-                if (nnri.peerIpv6Override == null) {
-                    nnri.peerIpv6 = Inet6Address.getByAddress(null,
-                            MacAddress.fromBytes(mac).getLinkLocalIpv6FromEui48Mac().getAddress(),
-                            NetworkInterface.getByName(nnri.interfaceName));
-                } else {
-                    byte[] addr = new byte[16];
-
-                    addr[0] = (byte) 0xfe;
-                    addr[1] = (byte) 0x80;
-                    addr[8] = nnri.peerIpv6Override[0];
-                    addr[9] = nnri.peerIpv6Override[1];
-                    addr[10] = nnri.peerIpv6Override[2];
-                    addr[11] = nnri.peerIpv6Override[3];
-                    addr[12] = nnri.peerIpv6Override[4];
-                    addr[13] = nnri.peerIpv6Override[5];
-                    addr[14] = nnri.peerIpv6Override[6];
-                    addr[15] = nnri.peerIpv6Override[7];
-
-                    nnri.peerIpv6 = Inet6Address.getByAddress(null, addr,
-                            NetworkInterface.getByName(nnri.interfaceName));
-                }
-            } catch (SocketException | UnknownHostException e) {
-                Log.e(TAG, "onDataPathConfirm: error obtaining scoped IPv6 address -- " + e);
-                nnri.peerIpv6 = null;
-            }
-
-            if (nnri.peerIpv6 != null) {
-                final WifiAwareNetworkInfo ni = new WifiAwareNetworkInfo(
-                        nnri.peerIpv6, nnri.peerPort, nnri.peerTransportProtocol);
-                ncBuilder.setTransportInfo(ni);
-                if (VDBG) {
-                    Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo=" + ni);
-                }
-            }
-
-            if (!mNiWrapper.configureAgentProperties(nnri, nnri.equivalentRequests, ndpId,
-                    ncBuilder, linkProperties)) {
-                declareUnfullfillableAndEndDp(nnri, ndpId);
-                return networkSpecifier;
-            }
-
-            final NetworkAgentConfig naConfig = new NetworkAgentConfig.Builder()
-                    .setLegacyType(ConnectivityManager.TYPE_NONE)
-                    .setLegacyTypeName(NETWORK_TAG)
-                    .build();
-
-            nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext,
-                    AGENT_TAG_PREFIX + nnri.ndpId, ncBuilder.build(),
-                    linkProperties, NETWORK_FACTORY_SCORE_AVAIL,
-                    naConfig, mNetworkFactory.getProvider(), nnri);
             nnri.startValidationTimestamp = mClock.getElapsedSinceBootMillis();
-            handleAddressValidation(nnri, linkProperties, ndpId, networkSpecifier.isOutOfBand());
+            handleAddressValidation(nnri, ndpId, networkSpecifier.isOutOfBand(), mac);
         } else {
             if (VDBG) {
                 Log.v(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier
@@ -676,29 +620,81 @@
         return networkSpecifier;
     }
 
-    private void handleAddressValidation(AwareNetworkRequestInformation nnri,
-            LinkProperties linkProperties, int ndpId, boolean isOutOfBand) {
-        if (mNiWrapper.isAddressUsable(linkProperties)) {
-            mNiWrapper.setConnected(nnri.networkAgent);
-
-            mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, isOutOfBand, nnri.startTimestamp);
-            nnri.startTimestamp = mClock.getElapsedSinceBootMillis(); // update time-stamp
-            mAwareMetrics.recordNdpCreation(nnri.uid, nnri.packageName, mNetworkRequestsCache);
-        } else {
-            if (mClock.getElapsedSinceBootMillis() - nnri.startValidationTimestamp
-                    > ADDRESS_VALIDATION_TIMEOUT_MS) {
-                Log.e(TAG, "Timed-out while waiting for IPv6 address to be usable");
-
-                declareUnfullfillableAndEndDp(nnri, ndpId);
-                return;
+    private void getInet6Address(AwareNetworkRequestInformation nnri, byte[] mac) {
+        try {
+            byte[] addr;
+            if (nnri.peerIpv6Override == null) {
+                addr = MacAddress.fromBytes(mac).getLinkLocalIpv6FromEui48Mac().getAddress();
+            } else {
+                addr = new byte[16];
+                addr[0] = (byte) 0xfe;
+                addr[1] = (byte) 0x80;
+                addr[8] = nnri.peerIpv6Override[0];
+                addr[9] = nnri.peerIpv6Override[1];
+                addr[10] = nnri.peerIpv6Override[2];
+                addr[11] = nnri.peerIpv6Override[3];
+                addr[12] = nnri.peerIpv6Override[4];
+                addr[13] = nnri.peerIpv6Override[5];
+                addr[14] = nnri.peerIpv6Override[6];
+                addr[15] = nnri.peerIpv6Override[7];
             }
+            nnri.peerIpv6 = Inet6Address.getByAddress(null, addr,
+                    NetworkInterface.getByName(nnri.interfaceName));
+        } catch (SocketException | UnknownHostException e) {
+            if (mDbg) {
+                Log.d(TAG, "onDataPathConfirm: error obtaining scoped IPv6 address -- " + e);
+            }
+            nnri.peerIpv6 = null;
+        }
+    }
+
+    private void handleAddressValidation(AwareNetworkRequestInformation nnri, int ndpId,
+            boolean isOutOfBand, byte[] mac) {
+        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(
+                sNetworkCapabilitiesFilter);
+        LinkProperties linkProperties = new LinkProperties();
+        getInet6Address(nnri, mac);
+        if (nnri.peerIpv6 != null) {
+            final WifiAwareNetworkInfo ni = new WifiAwareNetworkInfo(
+                    nnri.peerIpv6, nnri.peerPort, nnri.peerTransportProtocol);
+            ncBuilder.setTransportInfo(ni);
+            if (VDBG) {
+                Log.v(TAG, "onDataPathConfirm: AwareNetworkInfo=" + ni);
+            }
+        }
+        if (!(mNiWrapper.configureAgentProperties(nnri, nnri.equivalentRequests, ndpId,
+                ncBuilder, linkProperties) && mNiWrapper.isAddressUsable(linkProperties))) {
             if (VDBG) {
                 Log.d(TAG, "Failed address validation");
             }
-            mHandler.postDelayed(() -> {
-                handleAddressValidation(nnri, linkProperties, ndpId, isOutOfBand);
-            }, ADDRESS_VALIDATION_RETRY_INTERVAL_MS);
+            if (!isAddressValidationExpired(nnri, ndpId)) {
+                mHandler.postDelayed(() -> {
+                    handleAddressValidation(nnri, ndpId, isOutOfBand, mac);
+                }, ADDRESS_VALIDATION_RETRY_INTERVAL_MS);
+            }
+            return;
         }
+        final NetworkAgentConfig naConfig = new NetworkAgentConfig.Builder()
+                .setLegacyType(ConnectivityManager.TYPE_NONE)
+                .setLegacyTypeName(NETWORK_TAG)
+                .build();
+        nnri.networkAgent = new WifiAwareNetworkAgent(mLooper, mContext,
+                AGENT_TAG_PREFIX + nnri.ndpId, ncBuilder.build(), linkProperties,
+                NETWORK_FACTORY_SCORE_AVAIL, naConfig, mNetworkFactory.getProvider(), nnri);
+        mNiWrapper.setConnected(nnri.networkAgent);
+        mAwareMetrics.recordNdpStatus(NanStatusType.SUCCESS, isOutOfBand, nnri.startTimestamp);
+        nnri.startTimestamp = mClock.getElapsedSinceBootMillis(); // update time-stamp
+        mAwareMetrics.recordNdpCreation(nnri.uid, nnri.packageName, mNetworkRequestsCache);
+    }
+
+    private boolean isAddressValidationExpired(AwareNetworkRequestInformation nnri, int ndpId) {
+        if (mClock.getElapsedSinceBootMillis() - nnri.startValidationTimestamp
+                > ADDRESS_VALIDATION_TIMEOUT_MS) {
+            Log.e(TAG, "Timed-out while waiting for IPv6 address to be usable");
+            declareUnfullfillableAndEndDp(nnri, ndpId);
+            return true;
+        }
+        return false;
     }
 
     private void declareUnfullfillableAndEndDp(AwareNetworkRequestInformation nnri, int ndpId) {
diff --git a/service/proto/src/metrics.proto b/service/proto/src/metrics.proto
index ad52af1..349543d 100644
--- a/service/proto/src/metrics.proto
+++ b/service/proto/src/metrics.proto
@@ -706,6 +706,15 @@
 
   // User reaction to the carrier or suggestion app approval UI.
   optional UserReactionToApprovalUiEvent user_reaction_to_approval_ui_event = 198;
+
+  // Number of connection with different BSSID between framework and firmware selection.
+  optional int32 num_bssid_different_selection_between_framework_and_firmware = 199;
+
+  // Metrics about carrier wifi network.
+  optional CarrierWifiMetrics carrier_wifi_metrics = 200;
+
+  // Long version code of wifi mainline module, 0 means not available.
+  optional int64 mainline_module_version = 201;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -1438,6 +1447,9 @@
 
   // Whether screen is on when the event happens
   optional bool screen_on = 24;
+
+  // Whether cellular data network is available
+  optional bool is_cellular_data_available = 25;
 }
 
 // Wi-Fi Aware metrics
@@ -3289,3 +3301,11 @@
   // Event of user reaction to the carrier approval UI.
   repeated UserReaction user_approval_carrier_ui_reaction = 2;
 }
+
+message CarrierWifiMetrics {
+  // Number of successful network connection using Carrier ID
+  optional int32 num_connection_success = 1;
+  // Number of failed authentication from network connection using Carrier ID
+  optional int32 num_connection_auth_failure = 2;
+  optional int32 num_connection_non_auth_failure = 3;
+}
diff --git a/service/proto/src/scorecard.proto b/service/proto/src/scorecard.proto
index ad5822c..d351c56 100644
--- a/service/proto/src/scorecard.proto
+++ b/service/proto/src/scorecard.proto
@@ -146,7 +146,7 @@
   // Android OS build version
   optional string os_build_version = 1;
   // WiFi stack APK version, 0 means not available.
-  optional int32 wifi_stack_version = 2;
+  optional int64 wifi_stack_version = 2;
   // WiFi driver version
   optional string wifi_driver_version = 3;
   // WiFi firmware version
diff --git a/service/res/values-el/strings.xml b/service/res/values-el/strings.xml
index 9fa523b..0e3dd9e 100644
--- a/service/res/values-el/strings.xml
+++ b/service/res/values-el/strings.xml
@@ -47,7 +47,7 @@
     <string name="wifi_watchdog_network_disabled" msgid="688248897654073438">"Δεν είναι δυνατή η σύνδεση στο Wi-Fi"</string>
     <string name="wifi_watchdog_network_disabled_detailed" msgid="2946200607682633112">" έχει κακή σύνδεση Διαδικτύου."</string>
     <string name="wifi_connect_alert_title" msgid="6144470472092017636">"Να επιτρέπεται η σύνδεση;"</string>
-    <string name="wifi_connect_alert_message" msgid="3123801378559831220">"Η εφαρμογή %1$s θα ήθελε να συνδεθεί με το δίκτυο WiFi %2$s"</string>
+    <string name="wifi_connect_alert_message" msgid="3123801378559831220">"Η εφαρμογή %1$s θα ήθελε να συνδεθεί με το δίκτυο Wi-Fi %2$s"</string>
     <string name="wifi_connect_default_application" msgid="6164721692891325243">"Μια εφαρμογή"</string>
     <string name="accept" msgid="5931271886782610829">"Αποδοχή"</string>
     <string name="decline" msgid="6874256900873707640">"Απόρριψη"</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index dc2538c..8813738 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -417,4 +417,20 @@
 
     <!-- Wifi driver Automatic channel selection (ACS) for softap to include DFS channels -->
     <bool translatable="false" name="config_wifiSoftapAcsIncludeDfs">false</bool>
+
+    <!-- Initial PNO scan interval, in milliseconds, when the device is moving (i.e.
+         WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN, WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT, or
+         WifiManager.DEVICE_MOBILITY_STATE_LOW_MVMT).
+         The scan interval backs off from this initial interval on subsequent scans.
+         This scan is performed when screen is off and disconnected. -->
+    <integer translatable="false" name="config_wifiMovingPnoScanIntervalMillis">20000</integer>
+
+    <!-- Initial PNO scan interval, in milliseconds, when the device is stationary (i.e.
+         WifiManager.DEVICE_MOBILITY_STATE_STATIONARY).
+         The scan interval backs off from this initial interval on subsequent scans.
+         This scan is performed when screen is off and disconnected. -->
+    <integer translatable="false" name="config_wifiStationaryPnoScanIntervalMillis">60000</integer>
+
+    <!-- integer indicating additional disconnect delay (in ms) after IMS onLost() indication is received -->
+    <integer translatable="false" name="config_wifiDelayDisconnectOnImsLostMs">0</integer>
 </resources>
diff --git a/service/res/values/overlayable.xml b/service/res/values/overlayable.xml
index 52ef09e..7b77da5 100644
--- a/service/res/values/overlayable.xml
+++ b/service/res/values/overlayable.xml
@@ -131,6 +131,9 @@
           <item type="integer" name="config_wifiMaxNativeFailureSelfRecoveryPerHour" />
           <item type="bool" name="config_wifiIgnoreOpenSavedNetworkWhenSecureSuggestionAvailable" />
           <item type="bool" name="config_wifiSoftapAcsIncludeDfs" />
+          <item type="integer" name="config_wifiMovingPnoScanIntervalMillis" />
+          <item type="integer" name="config_wifiStationaryPnoScanIntervalMillis" />
+          <item type="integer" name="config_wifiDelayDisconnectOnImsLostMs" />
           <!-- Params from config.xml that can be overlayed -->
 
           <!-- Params from strings.xml that can be overlayed -->
diff --git a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
index 8195117..bc29d34 100644
--- a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java
@@ -997,6 +997,8 @@
     @Test
     public void connect() throws Exception {
         triggerConnect();
+        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID);
+        config.carrierId = CARRIER_ID_1;
         when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID))
                 .thenReturn(mScanDetailCache);
 
@@ -1045,6 +1047,7 @@
         assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity());
         verify(mWifiStateTracker).updateState(eq(WifiStateTracker.CONNECTED));
         assertEquals("ConnectedState", getCurrentState().getName());
+        verify(mWifiMetrics).incrementNumOfCarrierWifiConnectionSuccess();
         verify(mWifiLockManager).updateWifiClientConnected(true);
         verify(mWifiNative).getConnectionCapabilities(any());
         verify(mThroughputPredictor).predictMaxTxThroughput(any());
@@ -1535,6 +1538,60 @@
     }
 
     /**
+     * If caller tries to connect to a network that is already connecting, the connection request
+     * should succeed.
+     *
+     * Test: Create and trigger connect to a network, then try to reconnect to the same network.
+     * Verify that connection request returns with CONNECT_NETWORK_SUCCEEDED and did not trigger a
+     * new connection.
+     */
+    @Test
+    public void reconnectToConnectingNetwork() throws Exception {
+        triggerConnect();
+
+        // try to reconnect to the same network (before connection is established).
+        IActionListener connectActionListener = mock(IActionListener.class);
+        mCmi.connect(null, FRAMEWORK_NETWORK_ID, mock(Binder.class), connectActionListener, 0,
+                Binder.getCallingUid());
+        mLooper.dispatchAll();
+        verify(connectActionListener).onSuccess();
+
+        // Verify that we didn't trigger a second connection.
+        verify(mWifiNative, times(1)).connectToNetwork(eq(WIFI_IFACE_NAME), any());
+    }
+
+    /**
+     * If caller tries to connect to a network that is already connecting, the connection request
+     * should succeed.
+     *
+     * Test: Create and trigger connect to a network, then try to reconnect to the same network.
+     * Verify that connection request returns with CONNECT_NETWORK_SUCCEEDED and did trigger a new
+     * connection.
+     */
+    @Test
+    public void reconnectToConnectingNetworkWithCredentialChange() throws Exception {
+        triggerConnect();
+
+        // try to reconnect to the same network with a credential changed (before connection is
+        // established).
+        WifiConfiguration config = new WifiConfiguration();
+        config.networkId = FRAMEWORK_NETWORK_ID;
+        NetworkUpdateResult networkUpdateResult =
+                new NetworkUpdateResult(false /* ip */, false /* proxy */, true /* credential */);
+        networkUpdateResult.setNetworkId(FRAMEWORK_NETWORK_ID);
+        when(mWifiConfigManager.addOrUpdateNetwork(eq(config), anyInt()))
+                .thenReturn(networkUpdateResult);
+        IActionListener connectActionListener = mock(IActionListener.class);
+        mCmi.connect(config, WifiConfiguration.INVALID_NETWORK_ID, mock(Binder.class),
+                connectActionListener, 0, Binder.getCallingUid());
+        mLooper.dispatchAll();
+        verify(connectActionListener).onSuccess();
+
+        // Verify that we triggered a second connection.
+        verify(mWifiNative, times(2)).connectToNetwork(eq(WIFI_IFACE_NAME), any());
+    }
+
+    /**
      * If caller tries to connect to a new network while still provisioning the current one,
      * the connection attempt should succeed.
      */
@@ -1703,6 +1760,7 @@
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = sSSID;
         config.getNetworkSelectionStatus().setHasEverConnected(false);
+        config.carrierId = CARRIER_ID_1;
         when(mWifiConfigManager.getConfiguredNetwork(anyInt())).thenReturn(config);
 
         mCmi.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
@@ -1712,7 +1770,7 @@
         verify(mWrongPasswordNotifier).onWrongPasswordError(eq(sSSID));
         verify(mWifiConfigManager).updateNetworkSelectionStatus(anyInt(),
                 eq(WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD));
-
+        verify(mWifiMetrics).incrementNumOfCarrierWifiConnectionAuthFailure();
         assertEquals("DisconnectedState", getCurrentState().getName());
     }
 
@@ -2944,6 +3002,8 @@
      */
     @Test
     public void testReportConnectionEventIsCalledAfterAssociationFailure() throws Exception {
+        mConnectedNetwork.getNetworkSelectionStatus()
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
         mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
@@ -2962,6 +3022,8 @@
         verify(mWifiNetworkSuggestionsManager).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION),
                 any(WifiConfiguration.class), eq(null));
+        verify(mWifiMetrics, never())
+                .incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
         verifyConnectionEventTimeoutDoesNotOccur();
     }
 
@@ -2973,6 +3035,8 @@
      */
     @Test
     public void testReportConnectionEventIsCalledAfterAuthenticationFailure() throws Exception {
+        mConnectedNetwork.getNetworkSelectionStatus()
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
         // Setup CONNECT_MODE & a WifiConfiguration
         initializeAndAddNetworkAndVerifySuccess();
         mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
@@ -2991,6 +3055,8 @@
         verify(mWifiNetworkSuggestionsManager).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE),
                 any(WifiConfiguration.class), eq(null));
+        verify(mWifiMetrics, never())
+                .incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
         verifyConnectionEventTimeoutDoesNotOccur();
     }
 
@@ -3069,6 +3135,8 @@
     @Test
     public void testAssociationRejectionUpdatesWatchdog() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
+        WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(FRAMEWORK_NETWORK_ID);
+        config.carrierId = CARRIER_ID_1;
         mCmi.sendMessage(ClientModeImpl.CMD_START_CONNECT, 0, 0, sBSSID);
         mCmi.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT, 0, 0, sBSSID);
         mLooper.dispatchAll();
@@ -3076,6 +3144,7 @@
                 anyString(), anyString(), anyInt());
         verify(mBssidBlocklistMonitor).handleBssidConnectionFailure(sBSSID, sSSID,
                 BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION, false);
+        verify(mWifiMetrics).incrementNumOfCarrierWifiConnectionNonAuthFailure();
     }
 
     /**
@@ -3165,6 +3234,8 @@
      */
     @Test
     public void testReportConnectionEventIsCalledAfterDhcpFailure() throws Exception {
+        mConnectedNetwork.getNetworkSelectionStatus()
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult());
         testDhcpFailure();
         verify(mWifiDiagnostics, atLeastOnce()).reportConnectionEvent(
                 eq(WifiDiagnostics.CONNECTION_EVENT_FAILED));
@@ -3175,6 +3246,8 @@
         verify(mWifiNetworkSuggestionsManager, atLeastOnce()).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_DHCP), any(WifiConfiguration.class),
                 any(String.class));
+        verify(mWifiMetrics, never())
+                .incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
         verifyConnectionEventTimeoutDoesNotOccur();
     }
 
@@ -3186,8 +3259,9 @@
      */
     @Test
     public void testReportConnectionEventIsCalledAfterSuccessfulConnection() throws Exception {
+        mConnectedNetwork.getNetworkSelectionStatus()
+                .setCandidate(getGoogleGuestScanDetail(TEST_RSSI, sBSSID1, sFreq).getScanResult());
         connect();
-
         ArgumentCaptor<Messenger> messengerCaptor = ArgumentCaptor.forClass(Messenger.class);
         verify(mConnectivityManager).registerNetworkAgent(messengerCaptor.capture(),
                 any(NetworkInfo.class), any(LinkProperties.class), any(NetworkCapabilities.class),
@@ -3209,6 +3283,8 @@
         verify(mWifiNetworkSuggestionsManager).handleConnectionAttemptEnded(
                 eq(WifiMetrics.ConnectionEvent.FAILURE_NONE), any(WifiConfiguration.class),
                 any(String.class));
+        // BSSID different, record this connection.
+        verify(mWifiMetrics).incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
         verifyConnectionEventTimeoutDoesNotOccur();
     }
 
@@ -3235,8 +3311,8 @@
      */
     @Test
     public void testScoreCardNoteConnectionComplete() throws Exception {
-        Pair<String, String> l2KeyAndGroupHint = Pair.create("Wad", "Gab");
-        when(mWifiScoreCard.getL2KeyAndGroupHint(any())).thenReturn(l2KeyAndGroupHint);
+        Pair<String, String> l2KeyAndCluster = Pair.create("Wad", "Gab");
+        when(mWifiScoreCard.getL2KeyAndGroupHint(any())).thenReturn(l2KeyAndCluster);
         connect();
         mLooper.dispatchAll();
         verify(mWifiScoreCard).noteIpConfiguration(any());
@@ -3245,7 +3321,7 @@
         verify(mIpClient, atLeastOnce()).updateLayer2Information(captor.capture());
         final Layer2InformationParcelable info = captor.getValue();
         assertEquals(info.l2Key, "Wad");
-        assertEquals(info.groupHint, "Gab");
+        assertEquals(info.cluster, "Gab");
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java b/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
index 182e8e4..b733ec0 100644
--- a/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
@@ -40,11 +40,16 @@
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.NetworkRequest;
+import android.os.Handler;
 import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsMmTelManager;
@@ -52,6 +57,8 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
+import com.android.wifi.resources.R;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -61,6 +68,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -73,6 +81,7 @@
     private static final String TEST_INTERFACE_NAME = "testif0";
     private static final String OTHER_INTERFACE_NAME = "notTestIf";
     private static final int TEST_WIFI_OFF_DEFERRING_TIME_MS = 4000;
+    private static final int TEST_ACTIVE_SUBSCRIPTION_ID = 1;
 
     TestLooper mLooper;
 
@@ -90,12 +99,20 @@
     @Mock CarrierConfigManager mCarrierConfigManager;
     @Mock PersistableBundle mCarrierConfigBundle;
     @Mock ImsMmTelManager mImsMmTelManager;
+    @Mock ConnectivityManager mConnectivityManager;
+    @Mock SubscriptionManager mSubscriptionManager;
+    @Mock SubscriptionInfo mActiveSubscriptionInfo;
     private RegistrationManager.RegistrationCallback mImsMmTelManagerRegistrationCallback = null;
     private @RegistrationManager.ImsRegistrationState int mCurrentImsRegistrationState =
             RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
     private @AccessNetworkConstants.TransportType int mCurrentImsConnectionType =
             AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+    private NetworkRequest mImsRequest = null;
+    private NetworkCallback mImsNetworkCallback = null;
+    private Handler mImsNetworkCallbackHandler = null;
     private long mElapsedSinceBootMillis = 0L;
+    private List<SubscriptionInfo> mSubscriptionInfoList = new ArrayList<>();
+    private MockResources mResources;
 
     private MockitoSession mStaticMockSession = null;
 
@@ -110,6 +127,11 @@
         when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
         when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
                 .thenReturn(mCarrierConfigManager);
+        when(mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
+                .thenReturn(mSubscriptionManager);
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE))
+                .thenReturn(mConnectivityManager);
+        when(mContext.getResources()).thenReturn(mResources);
     }
 
     /*
@@ -124,6 +146,12 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
+        // Prepare data
+        mResources = new MockResources();
+        mResources.setInteger(R.integer.config_wifiDelayDisconnectOnImsLostMs, 0);
+        mSubscriptionInfoList.add(mActiveSubscriptionInfo);
+
         setUpSystemServiceForContext();
 
         /**
@@ -136,11 +164,9 @@
             .mockStatic(ImsMmTelManager.class)
             .mockStatic(SubscriptionManager.class)
             .startMocking();
-        lenient().when(ImsMmTelManager.createForSubscriptionId(anyInt()))
+        lenient().when(ImsMmTelManager.createForSubscriptionId(eq(TEST_ACTIVE_SUBSCRIPTION_ID)))
                 .thenReturn(mImsMmTelManager);
-        lenient().when(SubscriptionManager.getDefaultVoiceSubscriptionId())
-                .thenReturn(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
-        lenient().when(SubscriptionManager.isValidSubscriptionId(anyInt()))
+        lenient().when(SubscriptionManager.isValidSubscriptionId(eq(TEST_ACTIVE_SUBSCRIPTION_ID)))
                 .thenReturn(true);
         doAnswer(new AnswerWithArguments() {
             public void answer(Executor executor, RegistrationManager.RegistrationCallback c) {
@@ -169,7 +195,11 @@
                 any(RegistrationManager.RegistrationCallback.class));
         when(mImsMmTelManager.isAvailable(anyInt(), anyInt())).thenReturn(false);
 
-        when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
+        when(mActiveSubscriptionInfo.getSubscriptionId()).thenReturn(TEST_ACTIVE_SUBSCRIPTION_ID);
+        when(mSubscriptionManager.getActiveSubscriptionInfoList())
+                .thenReturn(mSubscriptionInfoList);
+        when(mTelephonyManager.createForSubscriptionId(eq(TEST_ACTIVE_SUBSCRIPTION_ID)))
+                .thenReturn(mTelephonyManager);
         when(mTelephonyManager.getVoiceNetworkType())
                 .thenReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfigBundle);
@@ -177,6 +207,18 @@
                 .getInt(eq(CarrierConfigManager.Ims.KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT)))
                 .thenReturn(0);
         doAnswer(new AnswerWithArguments() {
+            public void answer(NetworkRequest req, NetworkCallback callback, Handler handler) {
+                mImsRequest = req;
+                mImsNetworkCallback = callback;
+                mImsNetworkCallbackHandler = handler;
+            }
+        }).when(mConnectivityManager).registerNetworkCallback(any(), any(), any());
+        doAnswer(new AnswerWithArguments() {
+            public void answer(NetworkCallback callback) {
+                if (mImsNetworkCallback == callback) mImsNetworkCallback = null;
+            }
+        }).when(mConnectivityManager).unregisterNetworkCallback(any(NetworkCallback.class));
+        doAnswer(new AnswerWithArguments() {
             public long answer() {
                 return mElapsedSinceBootMillis;
             }
@@ -369,6 +411,7 @@
                 ClientModeImpl.SCAN_ONLY_MODE, TEST_INTERFACE_NAME);
         verify(mSarManager, times(2)).setScanOnlyWifiState(WIFI_STATE_ENABLED);
 
+        verify(mContext).getSystemService(anyString());
         verify(mImsMmTelManager, never()).registerImsRegistrationCallback(any(), any());
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
 
@@ -686,12 +729,15 @@
         // Notify wifi service IMS service is de-registered.
         assertNotNull(mImsMmTelManagerRegistrationCallback);
         mImsMmTelManagerRegistrationCallback.onUnregistered(null);
+        assertNotNull(mImsNetworkCallback);
+        mImsNetworkCallback.onLost(null);
         mLooper.dispatchAll();
 
         // Now Wifi could be turned off actually.
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
+        assertNull(mImsNetworkCallback);
         verify(mListener).onStopped();
         verify(mWifiMetrics).noteWifiOff(eq(true), eq(false), anyInt());
 
@@ -1003,6 +1049,8 @@
         // Notify wifi service IMS service is de-registered.
         assertNotNull(mImsMmTelManagerRegistrationCallback);
         mImsMmTelManagerRegistrationCallback.onUnregistered(null);
+        assertNotNull(mImsNetworkCallback);
+        mImsNetworkCallback.onLost(null);
         mLooper.dispatchAll();
 
         // Now Wifi could be switched to scan mode actually.
@@ -1010,6 +1058,7 @@
         verify(mImsMmTelManager).unregisterImsRegistrationCallback(
                 any(RegistrationManager.RegistrationCallback.class));
         assertNull(mImsMmTelManagerRegistrationCallback);
+        assertNull(mImsNetworkCallback);
         verify(mWifiMetrics).noteWifiOff(eq(true), eq(false), anyInt());
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
index b433d94..02994ec 100644
--- a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
@@ -333,4 +333,23 @@
         mConfigs.clear();
         assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
     }
+
+    @Test
+    public void testScanResultDoesNotMatchForWifiNetworkSpecifier() {
+        // Add regular saved network, this should create a scan result match info cache entry.
+        WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork();
+        ScanResult scanResult = createScanResultForNetwork(config);
+        config.networkId = 5;
+        mConfigs.put(config);
+        assertNotNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+
+        mConfigs.clear();
+
+        // Create WifiNetworkSpecifier network, this should not create a scan result match info
+        // cache entry.
+        config.ephemeral = true;
+        config.fromWifiNetworkSpecifier = true;
+        mConfigs.put(config);
+        assertNull(mConfigs.getByScanResultForCurrentUser(scanResult));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java b/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java
index e6b1699..ce3eed0 100644
--- a/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/DeviceConfigFacadeTest.java
@@ -124,10 +124,14 @@
                 mDeviceConfigFacade.getDataStallTxPerThr());
         assertEquals(DeviceConfigFacade.DEFAULT_DATA_STALL_CCA_LEVEL_THR,
                 mDeviceConfigFacade.getDataStallCcaLevelThr());
-        assertEquals(DeviceConfigFacade.DEFAULT_TPUT_SUFFICIENT_THR_LOW_KBPS,
-                mDeviceConfigFacade.getTputSufficientLowThrKbps());
-        assertEquals(DeviceConfigFacade.DEFAULT_TPUT_SUFFICIENT_THR_HIGH_KBPS,
-                mDeviceConfigFacade.getTputSufficientHighThrKbps());
+        assertEquals(DeviceConfigFacade.DEFAULT_TX_TPUT_SUFFICIENT_THR_LOW_KBPS,
+                mDeviceConfigFacade.getTxTputSufficientLowThrKbps());
+        assertEquals(DeviceConfigFacade.DEFAULT_TX_TPUT_SUFFICIENT_THR_HIGH_KBPS,
+                mDeviceConfigFacade.getTxTputSufficientHighThrKbps());
+        assertEquals(DeviceConfigFacade.DEFAULT_RX_TPUT_SUFFICIENT_THR_LOW_KBPS,
+                mDeviceConfigFacade.getRxTputSufficientLowThrKbps());
+        assertEquals(DeviceConfigFacade.DEFAULT_RX_TPUT_SUFFICIENT_THR_HIGH_KBPS,
+                mDeviceConfigFacade.getRxTputSufficientHighThrKbps());
         assertEquals(DeviceConfigFacade.DEFAULT_TPUT_SUFFICIENT_RATIO_THR_NUM,
                 mDeviceConfigFacade.getTputSufficientRatioThrNum());
         assertEquals(DeviceConfigFacade.DEFAULT_TPUT_SUFFICIENT_RATIO_THR_DEN,
@@ -179,6 +183,10 @@
         assertEquals(false, mDeviceConfigFacade.isOverlappingConnectionBugreportEnabled());
         assertEquals(DeviceConfigFacade.DEFAULT_OVERLAPPING_CONNECTION_DURATION_THRESHOLD_MS,
                 mDeviceConfigFacade.getOverlappingConnectionDurationThresholdMs());
+        assertEquals(DeviceConfigFacade.DEFAULT_TX_LINK_SPEED_LOW_THRESHOLD_MBPS,
+                mDeviceConfigFacade.getTxLinkSpeedLowThresholdMbps());
+        assertEquals(DeviceConfigFacade.DEFAULT_RX_LINK_SPEED_LOW_THRESHOLD_MBPS,
+                mDeviceConfigFacade.getRxLinkSpeedLowThresholdMbps());
     }
 
     /**
@@ -205,6 +213,10 @@
                 anyInt())).thenReturn(4000);
         when(DeviceConfig.getInt(anyString(), eq("tput_sufficient_high_thr_kbps"),
                 anyInt())).thenReturn(8000);
+        when(DeviceConfig.getInt(anyString(), eq("rx_tput_sufficient_low_thr_kbps"),
+                anyInt())).thenReturn(5000);
+        when(DeviceConfig.getInt(anyString(), eq("rx_tput_sufficient_high_thr_kbps"),
+                anyInt())).thenReturn(9000);
         when(DeviceConfig.getInt(anyString(), eq("tput_sufficient_ratio_thr_num"),
                 anyInt())).thenReturn(3);
         when(DeviceConfig.getInt(anyString(), eq("tput_sufficient_ratio_thr_den"),
@@ -261,6 +273,10 @@
                 anyBoolean())).thenReturn(true);
         when(DeviceConfig.getInt(anyString(), eq("overlapping_connection_duration_threshold_ms"),
                 anyInt())).thenReturn(50000);
+        when(DeviceConfig.getInt(anyString(), eq("tx_link_speed_low_threshold_mbps"),
+                anyInt())).thenReturn(9);
+        when(DeviceConfig.getInt(anyString(), eq("rx_link_speed_low_threshold_mbps"),
+                anyInt())).thenReturn(10);
 
         mOnPropertiesChangedListenerCaptor.getValue().onPropertiesChanged(null);
 
@@ -275,8 +291,10 @@
         assertEquals(1500, mDeviceConfigFacade.getDataStallRxTputThrKbps());
         assertEquals(95, mDeviceConfigFacade.getDataStallTxPerThr());
         assertEquals(80, mDeviceConfigFacade.getDataStallCcaLevelThr());
-        assertEquals(4000, mDeviceConfigFacade.getTputSufficientLowThrKbps());
-        assertEquals(8000, mDeviceConfigFacade.getTputSufficientHighThrKbps());
+        assertEquals(4000, mDeviceConfigFacade.getTxTputSufficientLowThrKbps());
+        assertEquals(8000, mDeviceConfigFacade.getTxTputSufficientHighThrKbps());
+        assertEquals(5000, mDeviceConfigFacade.getRxTputSufficientLowThrKbps());
+        assertEquals(9000, mDeviceConfigFacade.getRxTputSufficientHighThrKbps());
         assertEquals(3, mDeviceConfigFacade.getTputSufficientRatioThrNum());
         assertEquals(2, mDeviceConfigFacade.getTputSufficientRatioThrDen());
         assertEquals(10, mDeviceConfigFacade.getTxPktPerSecondThr());
@@ -306,5 +324,7 @@
         assertEquals(1000, mDeviceConfigFacade.getBugReportMinWindowMs());
         assertEquals(true, mDeviceConfigFacade.isOverlappingConnectionBugreportEnabled());
         assertEquals(50000, mDeviceConfigFacade.getOverlappingConnectionDurationThresholdMs());
+        assertEquals(9, mDeviceConfigFacade.getTxLinkSpeedLowThresholdMbps());
+        assertEquals(10, mDeviceConfigFacade.getRxLinkSpeedLowThresholdMbps());
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
index ed2ccce..3ac4adc 100644
--- a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
@@ -103,6 +103,10 @@
                 .thenReturn(new int[]{DFS_CHANNEL_FREQ});
 
         when(mWifiSettingsStore.handleWifiToggled(anyBoolean())).thenReturn(true);
+        // Saved network needed to start wake.
+        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
+        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
+        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Arrays.asList(openNetwork));
 
         mLooper = new TestLooper();
 
@@ -255,6 +259,19 @@
         verify(mWifiWakeMetrics, never()).recordStartEvent(anyInt());
     }
 
+
+    /**
+     * Verify that start does not set the wakeup lock when feature is disabled.
+     */
+    @Test
+    public void startDoesNotSetWakeupLockWhenNoSavedNetworksOrSuggestions() {
+        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Collections.emptyList());
+        initializeWakeupController(false /* enabled */);
+        mWakeupController.start();
+        verify(mWakeupLock, never()).setLock(any());
+        verify(mWifiWakeMetrics, never()).recordStartEvent(anyInt());
+    }
+
     /**
      * If the controller is already active, verify that start() is ignored and no setup is done.
      */
@@ -373,7 +390,7 @@
         WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
         WifiNetworkSuggestion wepNetworkSuggestion =
                 new WifiNetworkSuggestion(wepNetwork, null, false, false, true, true);
-        when(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions())
+        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>(Arrays.asList(
                         openNetworkSuggestion, wepNetworkSuggestion)));
 
@@ -415,7 +432,7 @@
         WifiConfiguration oweNetwork = WifiConfigurationTestUtil.createOweNetwork(quotedSsid2);
         WifiNetworkSuggestion oweNetworkSuggestion =
                 new WifiNetworkSuggestion(oweNetwork, null, false, false, true, true);
-        when(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions())
+        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>(Arrays.asList(oweNetworkSuggestion)));
 
         // scan results from most recent scan
@@ -513,7 +530,7 @@
                 .createOpenNetwork(ScanResultUtil.createQuotedSSID(SAVED_SSID));
         WifiNetworkSuggestion openNetworkSuggestion =
                 new WifiNetworkSuggestion(openNetwork, null, false, false, true, true);
-        when(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions())
+        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>(Collections.singletonList(openNetworkSuggestion)));
 
         initializeWakeupController(true /* enabled */);
@@ -537,8 +554,7 @@
      */
     @Test
     public void onResultsUpdatesWakeupLockWithOnlySavedNetworks() {
-        // no saved configs
-        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Collections.emptyList());
+        // no matching saved configs
 
         initializeWakeupController(true /* enabled */);
         mWakeupController.start();
@@ -713,7 +729,6 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L,
                 (long) (0.8 * WakeupController.LAST_DISCONNECT_TIMEOUT_MILLIS));
         ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
-        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Collections.emptyList());
         when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList());
         initializeWakeupController(true);
 
@@ -739,7 +754,6 @@
         when(mClock.getElapsedSinceBootMillis()).thenReturn(0L,
                 (long) (1.2 * WakeupController.LAST_DISCONNECT_TIMEOUT_MILLIS));
         ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
-        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Collections.emptyList());
         when(mWifiScanner.getSingleScanResults()).thenReturn(Collections.emptyList());
         initializeWakeupController(true);
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
index a2a1a45..6b01f8e 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiBackupRestoreTest.java
@@ -832,11 +832,13 @@
         List<WifiConfiguration> configurations = new ArrayList<>();
 
         WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
+        wepNetwork.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
         wepNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createDHCPIpConfigurationWithPacProxy());
         configurations.add(wepNetwork);
 
         WifiConfiguration pskNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        pskNetwork.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
         pskNetwork.setIpConfiguration(
                 WifiConfigurationTestUtil.createStaticIpConfigurationWithPacProxy());
         configurations.add(pskNetwork);
@@ -1113,10 +1115,10 @@
             throws IOException {
         out.write("network={\n");
         out.write("        " + "ssid=" + configuration.SSID + "\n");
-        String allowedKeyManagement = "";
         if (configuration.hiddenSSID) {
             out.write("        " + "scan_ssid=1" + "\n");
         }
+        String allowedKeyManagement = "";
         if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
             allowedKeyManagement += "NONE";
         }
@@ -1130,6 +1132,14 @@
             allowedKeyManagement += "IEEE8021X ";
         }
         out.write("        " + "key_mgmt=" + allowedKeyManagement + "\n");
+        String allowedAuthAlgorithm = "";
+        if (configuration.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN)) {
+            allowedAuthAlgorithm += "OPEN ";
+        }
+        if (configuration.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.SHARED)) {
+            allowedAuthAlgorithm += "SHARED ";
+        }
+        out.write("        " + "auth_alg=" + allowedAuthAlgorithm + "\n");
         if (configuration.preSharedKey != null) {
             out.write("        " + "psk=" + configuration.preSharedKey + "\n");
         }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index b01f2be..ba1c4f9 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -109,7 +109,7 @@
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
         when(mWifiNetworkSuggestionsManager.retrieveHiddenNetworkList())
                 .thenReturn(new ArrayList<>());
-        when(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions())
+        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(new HashSet<>());
         when(mWifiInjector.getBssidBlocklistMonitor()).thenReturn(mBssidBlocklistMonitor);
         when(mWifiInjector.getWifiChannelUtilizationScan()).thenReturn(mWifiChannelUtilization);
@@ -160,6 +160,10 @@
                 HIGH_MVMT_RSSI_DELTA);
         resources.setInteger(R.integer.config_wifiInitialPartialScanChannelCacheAgeMins,
                 CHANNEL_CACHE_AGE_MINS);
+        resources.setInteger(R.integer.config_wifiMovingPnoScanIntervalMillis,
+                MOVING_PNO_SCAN_INTERVAL_MILLIS);
+        resources.setInteger(R.integer.config_wifiStationaryPnoScanIntervalMillis,
+                STATIONARY_PNO_SCAN_INTERVAL_MILLIS);
     }
 
     /**
@@ -244,6 +248,8 @@
     private static final int TEMP_BSSID_BLOCK_DURATION_MS = 10 * 1000; // 10 seconds
     private static final int TEST_CONNECTED_NETWORK_ID = 55;
     private static final int CHANNEL_CACHE_AGE_MINS = 14400;
+    private static final int MOVING_PNO_SCAN_INTERVAL_MILLIS = 20_000;
+    private static final int STATIONARY_PNO_SCAN_INTERVAL_MILLIS = 60_000;
 
     Context mockContext() {
         Context context = mock(Context.class);
@@ -601,6 +607,50 @@
     }
 
     /**
+     * Multiple back to back connection attempts after a force connectivity scan should not be rate
+     * limited.
+     *
+     * Expected behavior: WifiConnectivityManager calls ClientModeImpl.startConnectToNetwork()
+     * with the expected candidate network ID and BSSID for only the expected number of times within
+     * the given interval.
+     */
+    @Test
+    public void connectionAttemptNotRateLimitedWhenScreenOffForceConnectivityScan() {
+        int maxAttemptRate = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_RATE;
+        int timeInterval = WifiConnectivityManager.MAX_CONNECTION_ATTEMPTS_TIME_INTERVAL_MS;
+        int numAttempts = 0;
+        int connectionAttemptIntervals = timeInterval / maxAttemptRate;
+
+        mWifiConnectivityManager.handleScreenStateChanged(false);
+
+        // First attempt the max rate number of connections within the rate interval.
+        long currentTimeStamp = 0;
+        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
+            currentTimeStamp += connectionAttemptIntervals;
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+            // Set WiFi to disconnected state to trigger PNO scan
+            mWifiConnectivityManager.handleConnectionStateChanged(
+                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+            numAttempts++;
+        }
+
+        mWifiConnectivityManager.forceConnectivityScan(new WorkSource());
+
+        for (int attempt = 0; attempt < maxAttemptRate; attempt++) {
+            currentTimeStamp += connectionAttemptIntervals;
+            when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+            // Set WiFi to disconnected state to trigger PNO scan
+            mWifiConnectivityManager.handleConnectionStateChanged(
+                    WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+            numAttempts++;
+        }
+
+        // Verify that all the connection attempts went through
+        verify(mClientModeImpl, times(numAttempts)).startConnectToNetwork(
+                CANDIDATE_NETWORK_ID, Process.WIFI_UID, CANDIDATE_BSSID);
+    }
+
+    /**
      * Multiple back to back connection attempts after a user selection should not be rate limited.
      *
      * Expected behavior: WifiConnectivityManager calls ClientModeImpl.startConnectToNetwork()
@@ -1497,7 +1547,7 @@
         when(mWifiNetworkSuggestion.getWifiConfiguration()).thenReturn(mSuggestionConfig);
         Set<WifiNetworkSuggestion> suggestionNetworks = new HashSet<WifiNetworkSuggestion>();
         suggestionNetworks.add(mWifiNetworkSuggestion);
-        when(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions())
+        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(suggestionNetworks);
 
         // Prepare for no saved networks
@@ -1548,7 +1598,7 @@
         when(mWifiNetworkSuggestion.getWifiConfiguration()).thenReturn(mSuggestionConfig);
         Set<WifiNetworkSuggestion> suggestionNetworks = new HashSet<WifiNetworkSuggestion>();
         suggestionNetworks.add(mWifiNetworkSuggestion);
-        when(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions())
+        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                 .thenReturn(suggestionNetworks);
 
         // Prepare for a single passpoint network
@@ -2730,8 +2780,7 @@
 
         inOrder.verify(mWifiScanner).startDisconnectedPnoScan(
                 scanSettingsCaptor.capture(), any(), any(), any());
-        assertEquals(scanSettingsCaptor.getValue().periodInMs,
-                WifiConnectivityManager.MOVING_PNO_SCAN_INTERVAL_MS);
+        assertEquals(scanSettingsCaptor.getValue().periodInMs, MOVING_PNO_SCAN_INTERVAL_MILLIS);
 
         // initial connectivity state uses moving PNO scan interval, now set it to stationary
         mWifiConnectivityManager.setDeviceMobilityState(
@@ -2740,8 +2789,7 @@
         inOrder.verify(mWifiScanner).stopPnoScan(any());
         inOrder.verify(mWifiScanner).startDisconnectedPnoScan(
                 scanSettingsCaptor.capture(), any(), any(), any());
-        assertEquals(scanSettingsCaptor.getValue().periodInMs,
-                WifiConnectivityManager.STATIONARY_PNO_SCAN_INTERVAL_MS);
+        assertEquals(scanSettingsCaptor.getValue().periodInMs, STATIONARY_PNO_SCAN_INTERVAL_MILLIS);
         verify(mScoringParams, times(2)).getEntryRssi(ScanResult.BAND_6_GHZ_START_FREQ_MHZ);
         verify(mScoringParams, times(2)).getEntryRssi(ScanResult.BAND_5_GHZ_START_FREQ_MHZ);
         verify(mScoringParams, times(2)).getEntryRssi(ScanResult.BAND_24_GHZ_START_FREQ_MHZ);
@@ -2766,8 +2814,7 @@
         inOrder.verify(mWifiScanner, never()).stopPnoScan(any());
         inOrder.verify(mWifiScanner).startDisconnectedPnoScan(
                 scanSettingsCaptor.capture(), any(), any(), any());
-        assertEquals(scanSettingsCaptor.getValue().periodInMs,
-                WifiConnectivityManager.MOVING_PNO_SCAN_INTERVAL_MS);
+        assertEquals(scanSettingsCaptor.getValue().periodInMs, MOVING_PNO_SCAN_INTERVAL_MILLIS);
 
         mWifiConnectivityManager.setDeviceMobilityState(
                 WifiManager.DEVICE_MOBILITY_STATE_LOW_MVMT);
@@ -2805,8 +2852,7 @@
                 scanSettingsCaptor.capture(), any(), any(), any());
         // check that now the PNO scan uses the stationary interval, even though it was set before
         // the PNO scan started
-        assertEquals(scanSettingsCaptor.getValue().periodInMs,
-                WifiConnectivityManager.STATIONARY_PNO_SCAN_INTERVAL_MS);
+        assertEquals(scanSettingsCaptor.getValue().periodInMs, STATIONARY_PNO_SCAN_INTERVAL_MILLIS);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java b/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
index cfe1ee8..e374a90 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiDataStallTest.java
@@ -107,10 +107,14 @@
                 DeviceConfigFacade.DEFAULT_DATA_STALL_TX_PER_THR);
         when(mDeviceConfigFacade.getDataStallCcaLevelThr()).thenReturn(
                 DeviceConfigFacade.DEFAULT_DATA_STALL_CCA_LEVEL_THR);
-        when(mDeviceConfigFacade.getTputSufficientLowThrKbps()).thenReturn(
-                DeviceConfigFacade.DEFAULT_TPUT_SUFFICIENT_THR_LOW_KBPS);
-        when(mDeviceConfigFacade.getTputSufficientHighThrKbps()).thenReturn(
-                DeviceConfigFacade.DEFAULT_TPUT_SUFFICIENT_THR_HIGH_KBPS);
+        when(mDeviceConfigFacade.getTxTputSufficientLowThrKbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_TX_TPUT_SUFFICIENT_THR_LOW_KBPS);
+        when(mDeviceConfigFacade.getTxTputSufficientHighThrKbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_TX_TPUT_SUFFICIENT_THR_HIGH_KBPS);
+        when(mDeviceConfigFacade.getRxTputSufficientLowThrKbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_RX_TPUT_SUFFICIENT_THR_LOW_KBPS);
+        when(mDeviceConfigFacade.getRxTputSufficientHighThrKbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_RX_TPUT_SUFFICIENT_THR_HIGH_KBPS);
         when(mDeviceConfigFacade.getTputSufficientRatioThrNum()).thenReturn(
                 DeviceConfigFacade.DEFAULT_TPUT_SUFFICIENT_RATIO_THR_NUM);
         when(mDeviceConfigFacade.getTputSufficientRatioThrDen()).thenReturn(
@@ -119,6 +123,10 @@
                 DeviceConfigFacade.DEFAULT_TX_PACKET_PER_SECOND_THR);
         when(mDeviceConfigFacade.getRxPktPerSecondThr()).thenReturn(
                 DeviceConfigFacade.DEFAULT_RX_PACKET_PER_SECOND_THR);
+        when(mDeviceConfigFacade.getTxLinkSpeedLowThresholdMbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_TX_LINK_SPEED_LOW_THRESHOLD_MBPS);
+        when(mDeviceConfigFacade.getRxLinkSpeedLowThresholdMbps()).thenReturn(
+                DeviceConfigFacade.DEFAULT_RX_LINK_SPEED_LOW_THRESHOLD_MBPS);
 
         when(mWifiInfo.getLinkSpeed()).thenReturn(10);
         when(mWifiInfo.getRxLinkSpeedMbps()).thenReturn(10);
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java
index 0660e5e..264fe90 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiHealthMonitorTest.java
@@ -110,6 +110,7 @@
     private WifiConfiguration mWifiConfig;
     private String mDriverVersion;
     private String mFirmwareVersion;
+    private static final long MODULE_VERSION = 1L;
     private TestAlarmManager mAlarmManager;
     private TestLooper mLooper = new TestLooper();
     private List<WifiConfiguration> mConfiguredNetworks;
@@ -152,7 +153,7 @@
 
         mDriverVersion = "build 1.1";
         mFirmwareVersion = "HW 1.1";
-        mPackageInfo.versionCode = 1;
+        when(mPackageInfo.getLongVersionCode()).thenReturn(MODULE_VERSION);
         when(mContext.getPackageName()).thenReturn("WifiAPK");
         when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(mPackageInfo);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
@@ -310,6 +311,7 @@
         // trigger extractCurrentSoftwareBuildInfo() call to update currSoftwareBuildInfo
         mWifiHealthMonitor.installMemoryStoreSetUpDetectionAlarm(mMemoryStore);
         mWifiHealthMonitor.setWifiEnabled(true);
+        assertEquals(0, mWifiHealthMonitor.getWifiStackVersion());
         millisecondsPass(5000);
         mWifiScanner.startScan(mScanSettings, mScanListener);
         mAlarmManager.dispatch(WifiHealthMonitor.POST_BOOT_DETECTION_TIMER_TAG);
@@ -332,6 +334,14 @@
                 mKeys.add(key);
                 mBlobs.add(value);
             }
+
+            @Override
+            public void setCluster(String key, String cluster) {
+            }
+
+            @Override
+            public void removeCluster(String cluster) {
+            }
         });
         mBlobListeners.get(0).onBlobRetrieved(serialized);
 
@@ -357,6 +367,7 @@
                 .getWifiFirmwareVersion());
         assertEquals(mFirmwareVersion, wifiSystemInfoStats.getPrevSoftwareBuildInfo()
                 .getWifiFirmwareVersion());
+        assertEquals(MODULE_VERSION, mWifiHealthMonitor.getWifiStackVersion());
 
         // Check write
         String writtenHex = hexStringFromByteArray(mBlobs.get(mKeys.size() - 1));
@@ -387,8 +398,7 @@
         SystemInfoStats systemInfoStats = SystemInfoStats.parseFrom(serialized);
         WifiSoftwareBuildInfo currSoftwareBuildInfoFromMemory = wifiSystemInfoStats
                 .fromSoftwareBuildInfo(systemInfoStats.getCurrSoftwareBuildInfo());
-        assertEquals(mPackageInfo.versionCode,
-                currSoftwareBuildInfoFromMemory.getWifiStackVersion());
+        assertEquals(MODULE_VERSION, currSoftwareBuildInfoFromMemory.getWifiStackVersion());
         assertEquals(mDriverVersion, currSoftwareBuildInfoFromMemory.getWifiDriverVersion());
         assertEquals(mFirmwareVersion, currSoftwareBuildInfoFromMemory.getWifiFirmwareVersion());
         assertEquals(Build.DISPLAY, currSoftwareBuildInfoFromMemory.getOsBuildVersion());
@@ -688,11 +698,20 @@
             public void read(String key, String name, WifiScoreCard.BlobListener listener) {
                 mBlobListeners.add(listener);
             }
+
             @Override
             public void write(String key, String name, byte[] value) {
                 mKeys.add(key);
                 mBlobs.add(value);
             }
+
+            @Override
+            public void setCluster(String key, String cluster) {
+            }
+
+            @Override
+            public void removeCluster(String cluster) {
+            }
         });
         mBlobListeners.get(0).onBlobRetrieved(serialized);
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 04730bb..ca5ae26 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -440,6 +440,8 @@
     private static final int NUM_NETWORK_ABNORMAL_ASSOC_REJECTION = 2;
     private static final int NUM_NETWORK_SUFFICIENT_RECENT_STATS_ONLY = 4;
     private static final int NUM_NETWORK_SUFFICIENT_RECENT_PREV_STATS = 5;
+    private static final int NUM_BSSID_SELECTION_DIFFERENT_BETWEEN_FRAMEWORK_FIRMWARE = 3;
+    private static final long WIFI_MAINLINE_MODULE_VERSION = 123456L;
 
     /** Number of notifications per "Connect to Network" notification type. */
     private static final int[] NUM_CONNECT_TO_NETWORK_NOTIFICATIONS = {0, 10, 20, 30, 40};
@@ -909,6 +911,9 @@
         for (int i = 0; i < NUM_PNO_FOUND_NETWORK_EVENTS; i++) {
             mWifiMetrics.incrementPnoFoundNetworkEventCount();
         }
+        for (int i = 0; i < NUM_BSSID_SELECTION_DIFFERENT_BETWEEN_FRAMEWORK_FIRMWARE; i++) {
+            mWifiMetrics.incrementNumBssidDifferentSelectionBetweenFrameworkAndFirmware();
+        }
 
         // set and increment "connect to network" notification metrics
         for (int i = 0; i < NUM_CONNECT_TO_NETWORK_NOTIFICATIONS.length; i++) {
@@ -1006,6 +1011,7 @@
         metrics.numNetworkSufficientRecentStatsOnly = NUM_NETWORK_SUFFICIENT_RECENT_STATS_ONLY;
         metrics.numNetworkSufficientRecentPrevStats = NUM_NETWORK_SUFFICIENT_RECENT_PREV_STATS;
         when(mWifiHealthMonitor.buildProto()).thenReturn(metrics);
+        when(mWifiHealthMonitor.getWifiStackVersion()).thenReturn(WIFI_MAINLINE_MODULE_VERSION);
     }
 
     private void addSoftApEventsToMetrics() {
@@ -1324,6 +1330,8 @@
                 mDecodedProto.numPasspointProviderWithSelfSignedRootCa);
         assertEquals(NUM_PASSPOINT_PROVIDERS_WITH_EXPIRATION_DATE,
                 mDecodedProto.numPasspointProviderWithSubscriptionExpiration);
+        assertEquals(NUM_BSSID_SELECTION_DIFFERENT_BETWEEN_FRAMEWORK_FIRMWARE,
+                mDecodedProto.numBssidDifferentSelectionBetweenFrameworkAndFirmware);
 
         assertEquals(NUM_RADIO_MODE_CHANGE_TO_MCC, mDecodedProto.numRadioModeChangeToMcc);
         assertEquals(NUM_RADIO_MODE_CHANGE_TO_SCC, mDecodedProto.numRadioModeChangeToScc);
@@ -1406,6 +1414,7 @@
                 mDecodedProto.numConnectRequestWithFilsAkm);
         assertEquals(NUM_L2_CONNECTION_THROUGH_FILS_AUTHENTICATION,
                 mDecodedProto.numL2ConnectionThroughFilsAuthentication);
+        assertEquals(WIFI_MAINLINE_MODULE_VERSION, mDecodedProto.mainlineModuleVersion);
 
     }
 
@@ -2285,6 +2294,7 @@
         }
         mTestLooper.dispatchAll();
         wifiMetrics.setScreenState(true);
+        when(mWifiDataStall.isCellularDataAvailable()).thenReturn(true);
         for (int i = 0; i < mTestStaLogInts.length; i++) {
             int[] lia = mTestStaLogInts[i];
             wifiMetrics.logStaEvent(lia[0], lia[1], lia[2] == 1 ? mTestWifiConfig : null);
@@ -2324,6 +2334,7 @@
             assertConfigInfoEqualsWifiConfig(
                     evs[7] == 1 ? mTestWifiConfig : null, event.configInfo);
             assertEquals(true, event.screenOn);
+            assertEquals(true, event.isCellularDataAvailable);
             j++;
         }
         assertEquals(mExpectedValues.length, j);
@@ -4967,4 +4978,21 @@
         assertEquals(0, mWifiMetrics.startConnectionEvent(mTestWifiConfig, "TestNetwork",
                 WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE));
     }
+
+    @Test
+    public void testCarrierWifiConnectionEvent() throws Exception {
+        mWifiMetrics.incrementNumOfCarrierWifiConnectionSuccess();
+        for (int i = 0; i < 2; i++) {
+            mWifiMetrics.incrementNumOfCarrierWifiConnectionAuthFailure();
+        }
+        for (int i = 0; i < 3; i++) {
+            mWifiMetrics.incrementNumOfCarrierWifiConnectionNonAuthFailure();
+        }
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(1, mDecodedProto.carrierWifiMetrics.numConnectionSuccess);
+        assertEquals(2, mDecodedProto.carrierWifiMetrics.numConnectionAuthFailure);
+        assertEquals(3, mDecodedProto.carrierWifiMetrics.numConnectionNonAuthFailure);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
index 25aaeff..5f9e033 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkFactoryTest.java
@@ -644,6 +644,35 @@
     }
 
     /**
+     * Verify the periodic scan back off to find a network matching the network specifier
+     * is cancelled when the user selects a network.
+     */
+    @Test
+    public void testPeriodicScanCancelOnUserSelectNetwork() throws Exception {
+        attachDefaultWifiNetworkSpecifierAndAppInfo(TEST_UID_1, false);
+        mWifiNetworkFactory.needNetworkFor(mNetworkRequest, 0);
+
+        mWifiNetworkFactory.addCallback(mAppBinder, mNetworkRequestMatchCallback,
+                TEST_CALLBACK_IDENTIFIER);
+        verify(mNetworkRequestMatchCallback).onUserSelectionCallbackRegistration(
+                mNetworkRequestUserSelectionCallback.capture());
+
+        verifyPeriodicScans(0,
+                PERIODIC_SCAN_INTERVAL_MS,     // 10s
+                PERIODIC_SCAN_INTERVAL_MS);    // 10s
+
+        // Now trigger user selection to one of the network.
+        mSelectedNetwork = WifiConfigurationTestUtil.createPskNetwork();
+        mSelectedNetwork.SSID = "\"" + TEST_SSID_1 + "\"";
+        sendUserSelectionSelect(mNetworkRequestUserSelectionCallback.getValue(), mSelectedNetwork);
+        mLooper.dispatchAll();
+
+        // Cancel the alarm set for the next scan.
+        verify(mAlarmManager).cancel(mPeriodicScanListenerArgumentCaptor.getValue());
+    }
+
+
+    /**
      * Verify callback registration/unregistration.
      */
     @Test
@@ -1345,7 +1374,7 @@
         }
 
         mInOrder = inOrder(mAlarmManager, mClientModeImpl);
-        validateConnectionRetryAttempts();
+        validateConnectionRetryAttempts(true);
 
         // Fail the request after all the retries are exhausted.
         verify(mNetworkRequestMatchCallback).onAbort();
@@ -1373,7 +1402,7 @@
         mLooper.dispatchAll();
 
         mInOrder = inOrder(mAlarmManager, mClientModeImpl);
-        validateConnectionRetryAttempts();
+        validateConnectionRetryAttempts(false);
 
         // Fail the request after all the retries are exhausted.
         verify(mNetworkRequestMatchCallback).onAbort();
@@ -1407,7 +1436,7 @@
         }
 
         mInOrder = inOrder(mAlarmManager, mClientModeImpl);
-        validateConnectionRetryAttempts();
+        validateConnectionRetryAttempts(false);
 
         verify(mNetworkRequestMatchCallback).onAbort();
         // Verify that we sent the connection failure callback.
@@ -1453,7 +1482,7 @@
         }
 
         mInOrder = inOrder(mAlarmManager, mClientModeImpl);
-        validateConnectionRetryAttempts();
+        validateConnectionRetryAttempts(false);
 
         // Verify that we sent the connection failure callback.
         verify(mNetworkRequestMatchCallback).onUserSelectionConnectFailure(
@@ -2783,11 +2812,13 @@
         }
     }
 
-    private void validateConnectionRetryAttempts() {
+    private void validateConnectionRetryAttempts(boolean onTimeout) {
         for (int i = 0; i < WifiNetworkFactory.USER_SELECTED_NETWORK_CONNECT_RETRY_MAX; i++) {
-            // Cancel the existing connection timeout.
-            mInOrder.verify(mAlarmManager).cancel(
-                    mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+            if (!onTimeout) {
+                // Cancel the existing connection timeout.
+                mInOrder.verify(mAlarmManager).cancel(
+                        mConnectionTimeoutAlarmListenerArgumentCaptor.getValue());
+            }
 
             // Trigger new connection.
             mInOrder.verify(mClientModeImpl).connect(eq(null), eq(TEST_NETWORK_ID_1),
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
index 897dca3..e7e8660 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
@@ -4018,6 +4018,53 @@
     }
 
     /**
+     * Verify that we only return user approved suggestions.
+     */
+    @Test
+    public void testGetApprovedNetworkSuggestions() {
+        WifiConfiguration wifiConfiguration = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true);
+        // Reuse the same network credentials to ensure they both match.
+        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
+                wifiConfiguration, null, false, false, true, true);
+
+        List<WifiNetworkSuggestion> networkSuggestionList1 =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                }};
+        List<WifiNetworkSuggestion> networkSuggestionList2 =
+                new ArrayList<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion2);
+                }};
+
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList1, TEST_UID_1,
+                        TEST_PACKAGE_1, TEST_FEATURE));
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager.add(networkSuggestionList2, TEST_UID_2,
+                        TEST_PACKAGE_2, TEST_FEATURE));
+
+        // nothing approved, return empty.
+        assertTrue(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions().isEmpty());
+
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        // only app 1 approved.
+        assertEquals(new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                }},
+                mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions());
+
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_2);
+        // both app 1 & 2 approved.
+        assertEquals(new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                    add(networkSuggestion2);
+                }},
+                mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions());
+    }
+
+    /**
      * Helper function for creating a test configuration with user credential.
      *
      * @return {@link PasspointConfiguration}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java b/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java
index e5525c4..1ad767c 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiScoreCardTest.java
@@ -688,6 +688,14 @@
             public void write(String key, String name, byte[] value) {
                 // ignore for now
             }
+            @Override
+            public void setCluster(String key, String cluster) {
+                // ignore for now
+            }
+            @Override
+            public void removeCluster(String cluster) {
+                // ignore for now
+            }
         });
 
         // Now make some changes
@@ -724,6 +732,13 @@
                 mKeys.add(key);
                 mBlobs.add(value);
             }
+            @Override
+            public void setCluster(String key, String cluster) {
+            }
+            @Override
+            public void removeCluster(String cluster) {
+                // ignore for now
+            }
         });
 
         // Make some changes
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index d91dffb..603a4d8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -1324,6 +1324,24 @@
     }
 
     /**
+     * Verify that startSoftAp() with valid config succeeds after a failed call
+     */
+    @Test
+    public void testStartSoftApWithValidConfigSucceedsAfterFailure() {
+        // First initiate a failed call
+        assertFalse(mWifiServiceImpl.startSoftAp(mApConfig));
+        verify(mActiveModeWarden, never()).startSoftAp(any());
+
+        // Next attempt a valid config
+        WifiConfiguration config = createValidWifiApConfiguration();
+        assertTrue(mWifiServiceImpl.startSoftAp(config));
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        WifiConfigurationTestUtil.assertConfigurationEqualForSoftAp(
+                config,
+                mSoftApModeConfigCaptor.getValue().getSoftApConfiguration().toWifiConfiguration());
+    }
+
+    /**
      * Verify caller with proper permission can call startTetheredHotspot.
      */
     @Test
@@ -1497,6 +1515,22 @@
     }
 
     /**
+     * Verify a valied call to startTetheredHotspot succeeds after a failed call.
+     */
+    @Test
+    public void testStartTetheredHotspotWithValidConfigSucceedsAfterFailedCall() {
+        // First issue an invalid call
+        assertFalse(mWifiServiceImpl.startTetheredHotspot(
+                new SoftApConfiguration.Builder().build()));
+        verify(mActiveModeWarden, never()).startSoftAp(any());
+
+        // Now attempt a successful call
+        assertTrue(mWifiServiceImpl.startTetheredHotspot(null));
+        verify(mActiveModeWarden).startSoftAp(mSoftApModeConfigCaptor.capture());
+        assertNull(mSoftApModeConfigCaptor.getValue().getSoftApConfiguration());
+    }
+
+    /**
      * Verify caller with proper permission can call stopSoftAp.
      */
     @Test
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 df74173..f496b1c 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -1394,9 +1394,15 @@
         mMockLooper.dispatchAll();
 
         // (2) get confirmation OR timeout
+        boolean timeout = false;
         if (getConfirmation) {
+            int numConfigureAgentPropertiesFail = 0;
             if (numAddrValidationRetries > 0) {
                 when(mMockNetworkInterface.isAddressUsable(any())).thenReturn(false);
+                when(mMockNetworkInterface.configureAgentProperties(any(), any(), anyInt(),
+                        any(), any())).thenReturn(false);
+                // First retry will be ConfigureAgentProperties failure.
+                numConfigureAgentPropertiesFail = 1;
             }
             when(mClock.getElapsedSinceBootMillis()).thenReturn(0L);
 
@@ -1406,35 +1412,41 @@
             inOrder.verify(mMockNetdWrapper).enableIpv6(anyString());
             inOrder.verify(mMockNetworkInterface).configureAgentProperties(any(), any(), anyInt(),
                     any(), any());
-            inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(), any(),
-                    netCapCaptor.capture(), anyInt(), any(), anyInt());
-
-            inOrder.verify(mMockNetworkInterface).isAddressUsable(any());
-            boolean timedout = false;
+            if (numAddrValidationRetries <= 0) {
+                inOrder.verify(mMockNetworkInterface).isAddressUsable(any());
+            }
             for (int i = 0; i < numAddrValidationRetries; ++i) {
+                if (i == numConfigureAgentPropertiesFail) {
+                    when(mMockNetworkInterface.configureAgentProperties(any(), any(), anyInt(),
+                            any(), any())).thenReturn(true);
+                }
                 if (i == numAddrValidationRetries - 1) {
                     when(mMockNetworkInterface.isAddressUsable(any())).thenReturn(true);
                 }
-
                 long currentTime = (i + 1L)
                         * WifiAwareDataPathStateManager.ADDRESS_VALIDATION_RETRY_INTERVAL_MS;
                 when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime);
                 mMockLooper.moveTimeForward(
                         WifiAwareDataPathStateManager.ADDRESS_VALIDATION_RETRY_INTERVAL_MS + 1);
                 mMockLooper.dispatchAll();
+                inOrder.verify(mMockNetworkInterface).configureAgentProperties(any(), any(),
+                        anyInt(), any(), any());
+                if (i < numConfigureAgentPropertiesFail) {
+                    continue;
+                }
                 inOrder.verify(mMockNetworkInterface).isAddressUsable(any());
-
                 if (currentTime > WifiAwareDataPathStateManager.ADDRESS_VALIDATION_TIMEOUT_MS) {
-                    timedout = true;
+                    timeout = true;
                     break;
                 }
             }
-
-            if (timedout) {
+            if (timeout) {
                 verifyRequestDeclaredUnfullfillable(nr);
                 inOrder.verify(mMockNative).endDataPath(transactionId.capture(), eq(ndpId));
                 mDut.onEndDataPathResponse(transactionId.getValue(), true, 0);
             } else {
+                inOrder.verify(mMockCm).registerNetworkAgent(messengerCaptor.capture(), any(),
+                        any(), netCapCaptor.capture(), anyInt(), any(), anyInt());
                 inOrder.verify(mMockNetworkInterface).setConnected(any());
                 inOrderM.verify(mAwareMetricsMock).recordNdpStatus(eq(NanStatusType.SUCCESS),
                         eq(useDirect), anyLong());
@@ -1458,7 +1470,7 @@
         }
 
         // (3) end data-path (unless didn't get confirmation)
-        if (getConfirmation) {
+        if (getConfirmation && !timeout) {
             Message endNetworkReqMsg = Message.obtain();
             endNetworkReqMsg.what = NetworkFactory.CMD_CANCEL_REQUEST;
             endNetworkReqMsg.obj = nr;